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

130 lines
4.5 KiB
Python

"""
tests/test_regression.py
------------------------
Regression tests for the compose pipeline.
For each downloaded template in downloads/, run compose_template() and
compare the output against the snapshot in tests/fixtures/expected/.
These tests require no live API calls. They verify that changes to the
compose pipeline don't silently break existing template conversions.
To update snapshots after an intentional change:
pytest tests/test_regression.py --update-snapshots
"""
import json
import os
import sys
import tempfile
import pytest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from compose_docusign_template import compose_template
DOWNLOADS_DIR = os.path.join(os.path.dirname(__file__), "..", "downloads")
FIXTURES_DIR = os.path.join(os.path.dirname(__file__), "fixtures", "expected")
# Templates with real downloaded data to test against
REGRESSION_TEMPLATES = [
"David Tag Demo Form__CBJCHBCA",
"_DEMO USE ONLY_ NDA__CBJCHBCA",
"Rob Test__CBJCHBCA",
]
@pytest.fixture
def update_snapshots(request):
return request.config.getoption("--update-snapshots", default=False)
@pytest.mark.parametrize("template_name", REGRESSION_TEMPLATES)
def test_compose_regression(template_name, update_snapshots):
"""
Compose output for each template must match the stored snapshot.
Run with --update-snapshots to regenerate.
"""
template_dir = os.path.join(DOWNLOADS_DIR, template_name)
if not os.path.isdir(template_dir):
pytest.skip(f"Downloaded template not found: {template_name}")
snapshot_path = os.path.join(FIXTURES_DIR, f"{template_name}.json")
with tempfile.NamedTemporaryFile(suffix=".json", delete=False, mode="w") as tf:
output_path = tf.name
try:
result, warnings = compose_template(template_dir, output_path)
if update_snapshots:
os.makedirs(FIXTURES_DIR, exist_ok=True)
with open(snapshot_path, "w") as f:
json.dump(result, f, indent=2)
pytest.skip(f"Snapshot updated for {template_name}")
if not os.path.exists(snapshot_path):
pytest.fail(
f"No snapshot for '{template_name}'. "
f"Run with --update-snapshots to create it."
)
with open(snapshot_path) as f:
expected = json.load(f)
# Compare key structural properties
assert result.get("name") == expected.get("name"), \
f"Template name mismatch for {template_name}"
# Recipients
result_roles = sorted([r.get("roleName", "") for r in result.get("recipients", {}).get("signers", [])])
expected_roles = sorted([r.get("roleName", "") for r in expected.get("recipients", {}).get("signers", [])])
assert result_roles == expected_roles, \
f"Recipient roles changed for {template_name}: {result_roles} != {expected_roles}"
# Tab counts per type — must not regress
result_tabs = _count_tabs(result)
expected_tabs = _count_tabs(expected)
for tab_type, count in expected_tabs.items():
actual = result_tabs.get(tab_type, 0)
assert actual == count, (
f"Tab count regression in {template_name}: "
f"{tab_type} expected {count}, got {actual}"
)
finally:
if os.path.exists(output_path):
os.unlink(output_path)
def _count_tabs(template: dict) -> dict:
"""Count total tabs of each type across all signers."""
counts = {}
for signer in template.get("recipients", {}).get("signers", []):
tabs = signer.get("tabs", {})
for tab_type, items in tabs.items():
if isinstance(items, list):
counts[tab_type] = counts.get(tab_type, 0) + len(items)
return counts
def test_no_tabs_lost_on_recompose():
"""
Sanity check: every downloaded template must produce at least one tab.
Catches complete compose failures silently returning empty output.
"""
for template_name in REGRESSION_TEMPLATES:
template_dir = os.path.join(DOWNLOADS_DIR, template_name)
if not os.path.isdir(template_dir):
continue
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as tf:
output_path = tf.name
try:
result, _ = compose_template(template_dir, output_path)
total_tabs = sum(_count_tabs(result).values())
assert total_tabs > 0, f"No tabs produced for {template_name}"
finally:
if os.path.exists(output_path):
os.unlink(output_path)