adobe-to-docusign-migrator/tests/test_e2e.py

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