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

134 lines
4.3 KiB
Python

"""
tests/test_api_verify.py
------------------------
Tests for /api/verify/* endpoints (send test envelope, status, void).
All DocuSign API calls are mocked with respx.
"""
import pytest
import respx
import httpx
from fastapi.testclient import TestClient
from web.app import app
from web.session import _serializer, _COOKIE_NAME
client = TestClient(app, raise_server_exceptions=True)
DS_BASE = "https://demo.docusign.net/restapi"
DS_ACCOUNT = "verify-account-id"
TEMPLATE_ID = "tpl-verify-001"
ENVELOPE_ID = "env-abc-123"
@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)
def _full_session():
return _serializer.dumps({
"adobe_access_token": "adobe-tok",
"docusign_access_token": "ds-tok",
})
def _ds_session():
return _serializer.dumps({"docusign_access_token": "ds-tok"})
class TestVerifySend:
def test_send_requires_auth(self):
"""No session → 401."""
resp = client.post(
"/api/verify/send",
json={"template_id": TEMPLATE_ID, "recipient_name": "Alice", "recipient_email": "alice@example.com"},
cookies={},
)
assert resp.status_code == 401
@respx.mock
def test_send_returns_envelope_id(self):
"""Authenticated + valid template → envelope_id returned."""
respx.post(
f"{DS_BASE}/v2.1/accounts/{DS_ACCOUNT}/envelopes"
).mock(return_value=httpx.Response(201, json={"envelopeId": ENVELOPE_ID}))
resp = client.post(
"/api/verify/send",
json={
"template_id": TEMPLATE_ID,
"recipient_name": "Alice Test",
"recipient_email": "alice@example.com",
},
cookies={_COOKIE_NAME: _ds_session()},
)
assert resp.status_code == 200
assert resp.json()["envelope_id"] == ENVELOPE_ID
@respx.mock
def test_send_propagates_docusign_error(self):
"""DocuSign 400 → 502 with error detail."""
respx.post(
f"{DS_BASE}/v2.1/accounts/{DS_ACCOUNT}/envelopes"
).mock(return_value=httpx.Response(400, json={"message": "Invalid templateId"}))
resp = client.post(
"/api/verify/send",
json={"template_id": "bad-id", "recipient_name": "X", "recipient_email": "x@x.com"},
cookies={_COOKIE_NAME: _ds_session()},
)
assert resp.status_code == 502
class TestVerifyStatus:
def test_status_requires_auth(self):
resp = client.get(f"/api/verify/status/{ENVELOPE_ID}", cookies={})
assert resp.status_code == 401
@respx.mock
def test_status_returns_envelope_state(self):
"""Authenticated → status and sent_at returned."""
respx.get(
f"{DS_BASE}/v2.1/accounts/{DS_ACCOUNT}/envelopes/{ENVELOPE_ID}"
).mock(return_value=httpx.Response(200, json={
"envelopeId": ENVELOPE_ID,
"status": "sent",
"sentDateTime": "2026-04-21T12:00:00Z",
"completedDateTime": None,
}))
resp = client.get(
f"/api/verify/status/{ENVELOPE_ID}",
cookies={_COOKIE_NAME: _ds_session()},
)
assert resp.status_code == 200
data = resp.json()
assert data["status"] == "sent"
assert data["envelope_id"] == ENVELOPE_ID
assert data["sent_at"] == "2026-04-21T12:00:00Z"
class TestVerifyVoid:
def test_void_requires_auth(self):
resp = client.post(f"/api/verify/void/{ENVELOPE_ID}", json={"reason": "test"}, cookies={})
assert resp.status_code == 401
@respx.mock
def test_void_calls_docusign(self):
"""Authenticated → PUT envelope status to voided → voided: true."""
respx.put(
f"{DS_BASE}/v2.1/accounts/{DS_ACCOUNT}/envelopes/{ENVELOPE_ID}"
).mock(return_value=httpx.Response(200, json={}))
resp = client.post(
f"/api/verify/void/{ENVELOPE_ID}",
json={"reason": "Verification complete"},
cookies={_COOKIE_NAME: _ds_session()},
)
assert resp.status_code == 200
assert resp.json()["voided"] is True
assert resp.json()["envelope_id"] == ENVELOPE_ID