193 lines
7.4 KiB
Python
193 lines
7.4 KiB
Python
"""
|
|
tests/test_e2e.py
|
|
-----------------
|
|
End-to-end test: full migration pipeline through the web API.
|
|
|
|
Simulates:
|
|
1. Connect Adobe Sign (mock OAuth callback)
|
|
2. Connect DocuSign (mock OAuth callback)
|
|
3. GET /api/templates/status → at least one template shown
|
|
4. POST /api/migrate → status created
|
|
5. GET /api/templates/status → same template now migrated
|
|
6. POST /api/migrate again → status updated
|
|
7. GET /api/migrate/history → two records for the same template
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
import respx
|
|
import httpx
|
|
from fastapi.testclient import TestClient
|
|
|
|
from web.app import app
|
|
import web.routers.migrate as migrate_module
|
|
|
|
ADOBE_BASE = "https://api.eu2.adobesign.com/api/rest/v6"
|
|
DS_BASE = "https://demo.docusign.net/restapi"
|
|
DS_ACCOUNT = "e2e-account-id"
|
|
TEMPLATE_NAME = "E2E NDA"
|
|
ADOBE_ID = "e2e-adobe-001"
|
|
DS_CREATED_ID = "e2e-ds-created"
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def patch_settings(monkeypatch):
|
|
import web.config as cfg
|
|
monkeypatch.setattr(cfg.settings, "docusign_account_id", DS_ACCOUNT)
|
|
monkeypatch.setattr(cfg.settings, "docusign_base_url", DS_BASE)
|
|
monkeypatch.setattr(cfg.settings, "adobe_sign_base_url", ADOBE_BASE)
|
|
monkeypatch.setattr(cfg.settings, "adobe_client_id", "test-client")
|
|
monkeypatch.setattr(cfg.settings, "adobe_client_secret", "test-secret")
|
|
monkeypatch.setattr(cfg.settings, "docusign_client_id", "test-ds-client")
|
|
monkeypatch.setattr(cfg.settings, "docusign_client_secret", "test-ds-secret")
|
|
monkeypatch.setattr(cfg.settings, "docusign_auth_server", "account-d.docusign.com")
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def temp_history(tmp_path, monkeypatch):
|
|
history_path = str(tmp_path / ".history.json")
|
|
monkeypatch.setattr(migrate_module, "_HISTORY_FILE", history_path)
|
|
return history_path
|
|
|
|
|
|
def _mock_compose(template_dir, output_path):
|
|
with open(output_path, "w") as f:
|
|
json.dump({"name": TEMPLATE_NAME}, f)
|
|
|
|
|
|
def _mock_download(template_id, access_token, output_dir):
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
with open(os.path.join(output_dir, "metadata.json"), "w") as f:
|
|
json.dump({"name": TEMPLATE_NAME, "id": template_id}, f)
|
|
with open(os.path.join(output_dir, "form_fields.json"), "w") as f:
|
|
json.dump({"fields": []}, f)
|
|
with open(os.path.join(output_dir, "documents.json"), "w") as f:
|
|
json.dump({"documents": []}, f)
|
|
return True
|
|
|
|
|
|
async def _async_mock_download(*args, **kwargs):
|
|
return _mock_download(*args, **kwargs)
|
|
|
|
|
|
@respx.mock
|
|
def test_full_migration_flow(temp_history):
|
|
"""Full 7-step end-to-end pipeline test."""
|
|
from web.session import _serializer, _COOKIE_NAME
|
|
from web.config import settings
|
|
|
|
test_client = TestClient(app, raise_server_exceptions=True)
|
|
|
|
# ── Step 1 & 2: Simulate already-authenticated session ──────────────────
|
|
# (OAuth callback tested in test_api_auth.py; here we inject the session directly)
|
|
session_cookie = _serializer.dumps({
|
|
"adobe_access_token": "e2e-adobe-tok",
|
|
"docusign_access_token": "e2e-ds-tok",
|
|
})
|
|
|
|
# ── Step 3: GET /api/templates/status → template visible ────────────────
|
|
respx.get(f"{ADOBE_BASE}/libraryDocuments").mock(
|
|
return_value=httpx.Response(200, json={
|
|
"libraryDocumentList": [
|
|
{"id": ADOBE_ID, "name": TEMPLATE_NAME, "modifiedDate": "2026-04-17T10:00:00Z"},
|
|
]
|
|
})
|
|
)
|
|
respx.get(f"{DS_BASE}/v2.1/accounts/{DS_ACCOUNT}/templates").mock(
|
|
return_value=httpx.Response(200, json={"envelopeTemplates": []})
|
|
)
|
|
|
|
status_resp = test_client.get(
|
|
"/api/templates/status",
|
|
cookies={_COOKIE_NAME: session_cookie}
|
|
)
|
|
assert status_resp.status_code == 200
|
|
templates = status_resp.json()["templates"]
|
|
assert any(t["adobe_id"] == ADOBE_ID for t in templates)
|
|
adobe_t = next(t for t in templates if t["adobe_id"] == ADOBE_ID)
|
|
assert adobe_t["status"] == "not_migrated"
|
|
|
|
# ── Step 4: POST /api/migrate → created ─────────────────────────────────
|
|
respx.get(f"{DS_BASE}/v2.1/accounts/{DS_ACCOUNT}/templates", name="list1").mock(
|
|
return_value=httpx.Response(200, json={"envelopeTemplates": []})
|
|
)
|
|
respx.post(f"{DS_BASE}/v2.1/accounts/{DS_ACCOUNT}/templates").mock(
|
|
return_value=httpx.Response(201, json={"templateId": DS_CREATED_ID})
|
|
)
|
|
|
|
with (
|
|
patch.object(migrate_module, "_download_adobe_template", new=_async_mock_download),
|
|
patch.object(migrate_module, "_load_compose", return_value=_mock_compose),
|
|
):
|
|
migrate_resp = test_client.post(
|
|
"/api/migrate",
|
|
json={"adobe_template_ids": [ADOBE_ID]},
|
|
cookies={_COOKIE_NAME: session_cookie},
|
|
)
|
|
|
|
assert migrate_resp.status_code == 200
|
|
result = migrate_resp.json()["results"][0]
|
|
assert result["status"] == "success"
|
|
assert result["action"] == "created"
|
|
assert result["docusign_template_id"] == DS_CREATED_ID
|
|
|
|
# ── Step 5: GET /api/templates/status → now migrated ────────────────────
|
|
respx.get(f"{ADOBE_BASE}/libraryDocuments").mock(
|
|
return_value=httpx.Response(200, json={
|
|
"libraryDocumentList": [
|
|
{"id": ADOBE_ID, "name": TEMPLATE_NAME, "modifiedDate": "2026-04-17T10:00:00Z"},
|
|
]
|
|
})
|
|
)
|
|
respx.get(f"{DS_BASE}/v2.1/accounts/{DS_ACCOUNT}/templates").mock(
|
|
return_value=httpx.Response(200, json={
|
|
"envelopeTemplates": [
|
|
{"templateId": DS_CREATED_ID, "name": TEMPLATE_NAME, "lastModified": "2026-04-17T12:00:00Z"},
|
|
]
|
|
})
|
|
)
|
|
status_resp2 = test_client.get(
|
|
"/api/templates/status",
|
|
cookies={_COOKIE_NAME: session_cookie}
|
|
)
|
|
templates2 = status_resp2.json()["templates"]
|
|
t2 = next(t for t in templates2 if t["adobe_id"] == ADOBE_ID)
|
|
assert t2["status"] == "migrated"
|
|
|
|
# ── Step 6: POST /api/migrate again → updated ───────────────────────────
|
|
respx.get(f"{DS_BASE}/v2.1/accounts/{DS_ACCOUNT}/templates").mock(
|
|
return_value=httpx.Response(200, json={
|
|
"envelopeTemplates": [
|
|
{"templateId": DS_CREATED_ID, "name": TEMPLATE_NAME, "lastModified": "2026-04-17T12:00:00Z"},
|
|
]
|
|
})
|
|
)
|
|
respx.put(f"{DS_BASE}/v2.1/accounts/{DS_ACCOUNT}/templates/{DS_CREATED_ID}").mock(
|
|
return_value=httpx.Response(200, json={})
|
|
)
|
|
|
|
with (
|
|
patch.object(migrate_module, "_download_adobe_template", new=_async_mock_download),
|
|
patch.object(migrate_module, "_load_compose", return_value=_mock_compose),
|
|
):
|
|
migrate_resp2 = test_client.post(
|
|
"/api/migrate",
|
|
json={"adobe_template_ids": [ADOBE_ID]},
|
|
cookies={_COOKIE_NAME: session_cookie},
|
|
)
|
|
|
|
result2 = migrate_resp2.json()["results"][0]
|
|
assert result2["action"] == "updated"
|
|
assert result2["docusign_template_id"] == DS_CREATED_ID
|
|
|
|
# ── Step 7: GET /api/migrate/history → two records ──────────────────────
|
|
history_resp = test_client.get("/api/migrate/history")
|
|
history = history_resp.json()["history"]
|
|
assert len(history) == 2
|
|
actions = [r["action"] for r in history]
|
|
assert "created" in actions
|
|
assert "updated" in actions
|