""" 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", # overwrite_if_exists=True so the second run updates the existing template json={"adobe_template_ids": [ADOBE_ID], "options": {"overwrite_if_exists": True}}, 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