finishing QDRO section
This commit is contained in:
@@ -20,6 +20,7 @@ class _User:
|
||||
self.is_active = True
|
||||
self.first_name = "Test"
|
||||
self.last_name = "User"
|
||||
self.is_approver = is_admin
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@@ -168,3 +169,79 @@ def test_printer_setup_crud(client_admin: TestClient):
|
||||
assert "TestPrinter" not in names
|
||||
|
||||
|
||||
def test_qdro_notification_routes_admin_crud(client_admin: TestClient):
|
||||
# Initially list should succeed
|
||||
resp = client_admin.get("/api/admin/qdro/notification-routes")
|
||||
assert resp.status_code == 200
|
||||
assert "items" in resp.json()
|
||||
|
||||
# Create a per-file route
|
||||
file_no = "ROUTE-123"
|
||||
payload = {
|
||||
"scope": "file",
|
||||
"identifier": file_no,
|
||||
"email_to": "a@example.com,b@example.com",
|
||||
"webhook_url": "https://hooks.example.com/qdro",
|
||||
"webhook_secret": "sekret",
|
||||
}
|
||||
resp = client_admin.post("/api/admin/qdro/notification-routes", json=payload)
|
||||
assert resp.status_code == 200, resp.text
|
||||
|
||||
# Verify appears in list
|
||||
resp = client_admin.get("/api/admin/qdro/notification-routes?scope=file")
|
||||
assert resp.status_code == 200
|
||||
items = resp.json().get("items")
|
||||
assert any(it["identifier"] == file_no and it["email_to"] for it in items)
|
||||
|
||||
# Delete route
|
||||
resp = client_admin.delete(f"/api/admin/qdro/notification-routes/file/{file_no}")
|
||||
assert resp.status_code == 200
|
||||
# Verify gone
|
||||
resp = client_admin.get("/api/admin/qdro/notification-routes?scope=file")
|
||||
assert resp.status_code == 200
|
||||
items = resp.json().get("items")
|
||||
assert not any(it["identifier"] == file_no for it in items)
|
||||
|
||||
def test_approver_toggle_admin_only(client_admin: TestClient):
|
||||
# Create a user
|
||||
uname = f"u_{uuid.uuid4().hex[:6]}"
|
||||
resp = client_admin.post(
|
||||
"/api/admin/users",
|
||||
json={
|
||||
"username": uname,
|
||||
"email": f"{uname}@example.com",
|
||||
"password": "secret123",
|
||||
"first_name": "A",
|
||||
"last_name": "B",
|
||||
"is_admin": False,
|
||||
"is_active": True,
|
||||
"is_approver": False,
|
||||
},
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
user_id = resp.json()["id"]
|
||||
|
||||
# Toggle approver on
|
||||
resp = client_admin.post(f"/api/admin/users/{user_id}/approver", json={"is_approver": True})
|
||||
assert resp.status_code == 200, resp.text
|
||||
assert resp.json()["is_approver"] is True
|
||||
|
||||
# Toggle approver off
|
||||
resp = client_admin.post(f"/api/admin/users/{user_id}/approver", json={"is_approver": False})
|
||||
assert resp.status_code == 200, resp.text
|
||||
assert resp.json()["is_approver"] is False
|
||||
|
||||
# Non-admin should be forbidden
|
||||
app.dependency_overrides[get_current_user] = lambda: _User(False)
|
||||
# Ensure admin override is not present so permission is enforced
|
||||
prev_admin_override = app.dependency_overrides.pop(get_admin_user, None)
|
||||
try:
|
||||
c = TestClient(app)
|
||||
resp = c.post(f"/api/admin/users/{user_id}/approver", json={"is_approver": True})
|
||||
assert_http_error(resp, 403, "Not enough permissions")
|
||||
finally:
|
||||
if prev_admin_override is not None:
|
||||
app.dependency_overrides[get_admin_user] = prev_admin_override
|
||||
app.dependency_overrides.pop(get_current_user, None)
|
||||
|
||||
|
||||
|
||||
171
tests/test_qdro_notifications.py
Normal file
171
tests/test_qdro_notifications.py
Normal file
@@ -0,0 +1,171 @@
|
||||
import os
|
||||
import uuid
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
# Ensure required env vars before importing app
|
||||
os.environ.setdefault("SECRET_KEY", "x" * 32)
|
||||
os.environ.setdefault("DATABASE_URL", "sqlite:////tmp/delphi_test.sqlite")
|
||||
|
||||
from app.main import app # noqa: E402
|
||||
from app.auth.security import get_current_user # noqa: E402
|
||||
import app.api.qdros as qdros_module # noqa: E402
|
||||
from sqlalchemy.orm import Session # noqa: E402
|
||||
from app.database.base import get_db # noqa: E402
|
||||
from app.models.lookups import SystemSetup # noqa: E402
|
||||
from tests.test_qdros_api import _create_customer_and_file # noqa: E402
|
||||
|
||||
|
||||
class _User:
|
||||
def __init__(self):
|
||||
self.id = 1
|
||||
self.username = "tester"
|
||||
self.is_admin = True
|
||||
self.is_active = True
|
||||
self.is_approver = True
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def client():
|
||||
app.dependency_overrides[get_current_user] = lambda: _User()
|
||||
try:
|
||||
yield TestClient(app)
|
||||
finally:
|
||||
app.dependency_overrides.pop(get_current_user, None)
|
||||
|
||||
|
||||
class DummyNotification:
|
||||
def __init__(self):
|
||||
self.events = []
|
||||
|
||||
def emit(self, event_type: str, payload: dict):
|
||||
self.events.append((event_type, payload))
|
||||
|
||||
|
||||
def test_qdro_transition_emits_notification_when_notify_true(client: TestClient, monkeypatch):
|
||||
dummy = DummyNotification()
|
||||
# Patch the module-level service reference used by endpoints
|
||||
monkeypatch.setattr(qdros_module, "notification_service", dummy, raising=False)
|
||||
|
||||
# Arrange: create file and qdro
|
||||
_, file_no = _create_customer_and_file(client)
|
||||
resp = client.post("/api/qdros", json={"file_no": file_no})
|
||||
assert resp.status_code == 200, resp.text
|
||||
qid = resp.json()["id"]
|
||||
|
||||
# Act: submit for approval with notify=True
|
||||
resp = client.post(
|
||||
f"/api/qdros/{qid}/submit-for-approval",
|
||||
json={"reason": "send to approver", "notify": True, "effective_date": date.today().isoformat()},
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
|
||||
# Assert: one event captured with expected shape
|
||||
assert dummy.events, "Expected a notification event to be emitted"
|
||||
event_type, payload = dummy.events[-1]
|
||||
assert event_type == "QDRO_STATUS_CHANGED"
|
||||
assert payload.get("qdro_id") == qid
|
||||
assert payload.get("file_no") == file_no
|
||||
assert payload.get("from") == "DRAFT"
|
||||
assert payload.get("to") == "APPROVAL_PENDING"
|
||||
|
||||
|
||||
def test_qdro_transition_no_notification_when_notify_false(client: TestClient, monkeypatch):
|
||||
dummy = DummyNotification()
|
||||
monkeypatch.setattr(qdros_module, "notification_service", dummy, raising=False)
|
||||
|
||||
_, file_no = _create_customer_and_file(client)
|
||||
resp = client.post("/api/qdros", json={"file_no": file_no})
|
||||
assert resp.status_code == 200, resp.text
|
||||
qid = resp.json()["id"]
|
||||
|
||||
# notify omitted/false
|
||||
resp = client.post(f"/api/qdros/{qid}/submit-for-approval", json={"reason": "send"})
|
||||
assert resp.status_code == 200, resp.text
|
||||
|
||||
assert dummy.events == []
|
||||
|
||||
|
||||
def _upsert_system_setting(db: Session, key: str, value: str):
|
||||
row = db.query(SystemSetup).filter(SystemSetup.setting_key == key).first()
|
||||
if row:
|
||||
row.setting_value = value
|
||||
else:
|
||||
row = SystemSetup(setting_key=key, setting_value=value)
|
||||
db.add(row)
|
||||
db.commit()
|
||||
|
||||
|
||||
def test_per_file_routing_overrides_email_and_webhook(client: TestClient, monkeypatch):
|
||||
dummy = DummyNotification()
|
||||
monkeypatch.setattr(qdros_module, "notification_service", dummy, raising=False)
|
||||
|
||||
# Create file and qdro
|
||||
_, file_no = _create_customer_and_file(client)
|
||||
# Get DB session from dependency directly
|
||||
db_gen = get_db()
|
||||
db = next(db_gen)
|
||||
|
||||
# Configure per-file routes
|
||||
_upsert_system_setting(db, f"notifications.qdro.email.to.file.{file_no}", "lawyer@example.com, clerk@example.com")
|
||||
_upsert_system_setting(db, f"notifications.qdro.webhook.url.file.{file_no}", "https://hooks.example.com/qdro-test")
|
||||
_upsert_system_setting(db, f"notifications.qdro.webhook.secret.file.{file_no}", "s3cr3t")
|
||||
|
||||
resp = client.post("/api/qdros", json={"file_no": file_no, "plan_id": "PL-ABC"})
|
||||
assert resp.status_code == 200, resp.text
|
||||
qid = resp.json()["id"]
|
||||
|
||||
resp = client.post(
|
||||
f"/api/qdros/{qid}/submit-for-approval",
|
||||
json={"reason": "send", "notify": True, "effective_date": date.today().isoformat()},
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
|
||||
# Last event should include override markers
|
||||
assert dummy.events, "Expected a notification"
|
||||
_, payload = dummy.events[-1]
|
||||
assert payload.get("__notify_override") is True
|
||||
assert "lawyer@example.com" in str(payload.get("__notify_to"))
|
||||
assert payload.get("__webhook_override") is True
|
||||
assert payload.get("__webhook_url") == "https://hooks.example.com/qdro-test"
|
||||
assert payload.get("__webhook_secret") == "s3cr3t"
|
||||
# Close session
|
||||
try:
|
||||
db_gen.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def test_plan_routing_applies_when_no_file_override(client: TestClient, monkeypatch):
|
||||
dummy = DummyNotification()
|
||||
monkeypatch.setattr(qdros_module, "notification_service", dummy, raising=False)
|
||||
|
||||
# Create file + qdro with plan
|
||||
_, file_no = _create_customer_and_file(client)
|
||||
db_gen = get_db()
|
||||
db = next(db_gen)
|
||||
plan_id = "PL-RTE"
|
||||
_upsert_system_setting(db, f"notifications.qdro.email.to.plan.{plan_id}", "plan@example.com")
|
||||
|
||||
resp = client.post("/api/qdros", json={"file_no": file_no, "plan_id": plan_id})
|
||||
assert resp.status_code == 200, resp.text
|
||||
qid = resp.json()["id"]
|
||||
|
||||
resp = client.post(
|
||||
f"/api/qdros/{qid}/submit-for-approval",
|
||||
json={"reason": "send", "notify": True, "effective_date": date.today().isoformat()},
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
|
||||
_, payload = dummy.events[-1]
|
||||
assert payload.get("__notify_override") is True
|
||||
assert payload.get("__notify_to") == "plan@example.com"
|
||||
try:
|
||||
db_gen.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
257
tests/test_qdros_api.py
Normal file
257
tests/test_qdros_api.py
Normal file
@@ -0,0 +1,257 @@
|
||||
import os
|
||||
import io
|
||||
import uuid
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
# Ensure required env vars before importing app
|
||||
os.environ.setdefault("SECRET_KEY", "x" * 32)
|
||||
os.environ.setdefault("DATABASE_URL", "sqlite:////tmp/delphi_test.sqlite")
|
||||
|
||||
from app.main import app # noqa: E402
|
||||
from app.auth.security import get_current_user # noqa: E402
|
||||
from tests.helpers import assert_http_error, assert_validation_error # noqa: E402
|
||||
|
||||
|
||||
class _User:
|
||||
def __init__(self):
|
||||
self.id = 1
|
||||
self.username = "tester"
|
||||
self.is_admin = True
|
||||
self.is_active = True
|
||||
self.is_approver = True
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def client():
|
||||
app.dependency_overrides[get_current_user] = lambda: _User()
|
||||
try:
|
||||
yield TestClient(app)
|
||||
finally:
|
||||
app.dependency_overrides.pop(get_current_user, None)
|
||||
|
||||
|
||||
def _create_customer_and_file(client: TestClient) -> tuple[str, str]:
|
||||
cust_id = f"Q-{uuid.uuid4().hex[:8]}"
|
||||
resp = client.post("/api/customers/", json={"id": cust_id, "last": "QDRO", "email": "q@example.com"})
|
||||
assert resp.status_code == 200, resp.text
|
||||
file_no = f"QF-{uuid.uuid4().hex[:6]}"
|
||||
payload = {
|
||||
"file_no": file_no,
|
||||
"id": cust_id,
|
||||
"regarding": "QDRO matter",
|
||||
"empl_num": "E01",
|
||||
"file_type": "CIVIL",
|
||||
"opened": date.today().isoformat(),
|
||||
"status": "ACTIVE",
|
||||
"rate_per_hour": 150.0,
|
||||
}
|
||||
resp = client.post("/api/files/", json=payload)
|
||||
assert resp.status_code == 200, resp.text
|
||||
return cust_id, file_no
|
||||
|
||||
|
||||
def _dummy_docx_bytes() -> bytes:
|
||||
try:
|
||||
from docx import Document
|
||||
except Exception:
|
||||
return b"PK\x03\x04"
|
||||
d = Document()
|
||||
p = d.add_paragraph()
|
||||
p.add_run("QDRO for ")
|
||||
p.add_run("{{CLIENT_FULL}}")
|
||||
p = d.add_paragraph()
|
||||
p.add_run("File ")
|
||||
p.add_run("{{FILE_NO}}")
|
||||
buf = io.BytesIO()
|
||||
d.save(buf)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
def test_qdro_crud_and_list_by_file(client: TestClient):
|
||||
_, file_no = _create_customer_and_file(client)
|
||||
|
||||
# 404 when file missing on create
|
||||
resp = client.post("/api/qdros", json={"file_no": "NOFILE"})
|
||||
assert_http_error(resp, 404, "File not found")
|
||||
|
||||
# Create
|
||||
create = {
|
||||
"file_no": file_no,
|
||||
"version": "01",
|
||||
"status": "DRAFT",
|
||||
"case_number": "2025-1234",
|
||||
"pet": "Alice",
|
||||
"res": "Bob",
|
||||
"percent_awarded": "50%",
|
||||
}
|
||||
resp = client.post("/api/qdros", json=create)
|
||||
assert resp.status_code == 200, resp.text
|
||||
q = resp.json()
|
||||
qid = q["id"]
|
||||
assert q["file_no"] == file_no
|
||||
|
||||
# List by file
|
||||
resp = client.get(f"/api/qdros/{file_no}")
|
||||
assert resp.status_code == 200
|
||||
assert any(item["id"] == qid for item in resp.json())
|
||||
|
||||
# Get by id
|
||||
resp = client.get(f"/api/qdros/item/{qid}")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["id"] == qid
|
||||
|
||||
# Update
|
||||
resp = client.put(f"/api/qdros/{qid}", json={"status": "APPROVED"})
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["status"] == "APPROVED"
|
||||
|
||||
# Delete
|
||||
resp = client.delete(f"/api/qdros/{qid}")
|
||||
assert resp.status_code == 200
|
||||
# Now 404
|
||||
resp = client.get(f"/api/qdros/item/{qid}")
|
||||
assert_http_error(resp, 404, "QDRO not found")
|
||||
|
||||
|
||||
def test_qdro_division_calculation_and_persist_percent(client: TestClient):
|
||||
_, file_no = _create_customer_and_file(client)
|
||||
resp = client.post("/api/qdros", json={"file_no": file_no})
|
||||
qid = resp.json()["id"]
|
||||
|
||||
# percent -> amount
|
||||
resp = client.post(f"/api/qdros/{qid}/calculate-division", json={"account_balance": 10000.0, "percent": 25.0})
|
||||
assert resp.status_code == 200, resp.text
|
||||
assert resp.json()["amount"] == 2500.0
|
||||
|
||||
# amount -> percent and save
|
||||
resp = client.post(
|
||||
f"/api/qdros/{qid}/calculate-division",
|
||||
json={"account_balance": 20000.0, "amount": 5000.0, "save_percent_string": True},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
pct = resp.json()["percent"]
|
||||
assert round(pct, 2) == 25.0
|
||||
# Verify saved percent_awarded string
|
||||
resp = client.get(f"/api/qdros/item/{qid}")
|
||||
assert resp.status_code == 200
|
||||
assert "%" in (resp.json().get("percent_awarded") or "")
|
||||
|
||||
|
||||
def test_qdro_versions_and_communications(client: TestClient):
|
||||
_, file_no = _create_customer_and_file(client)
|
||||
resp = client.post("/api/qdros", json={"file_no": file_no, "content": "Initial"})
|
||||
qid = resp.json()["id"]
|
||||
|
||||
# Create version
|
||||
resp = client.post(f"/api/qdros/{qid}/versions", json={"version_label": "02", "status": "DRAFT"})
|
||||
assert resp.status_code == 200
|
||||
ver = resp.json()
|
||||
assert ver["version_label"] == "02"
|
||||
|
||||
# List versions
|
||||
resp = client.get(f"/api/qdros/{qid}/versions")
|
||||
assert resp.status_code == 200
|
||||
assert any(v["id"] == ver["id"] for v in resp.json())
|
||||
|
||||
# Communications
|
||||
comm = {
|
||||
"channel": "email",
|
||||
"subject": "Request Info",
|
||||
"message": "Please provide latest statements",
|
||||
"contact_name": "Plan Admin",
|
||||
"status": "sent",
|
||||
}
|
||||
resp = client.post(f"/api/qdros/{qid}/communications", json=comm)
|
||||
assert resp.status_code == 200
|
||||
comm_id = resp.json()["id"]
|
||||
resp = client.get(f"/api/qdros/{qid}/communications")
|
||||
assert resp.status_code == 200
|
||||
assert any(c["id"] == comm_id for c in resp.json())
|
||||
|
||||
|
||||
def test_plan_info_create_and_list(client: TestClient):
|
||||
plan_id = f"PL-{uuid.uuid4().hex[:6]}"
|
||||
payload = {"plan_id": plan_id, "plan_name": "Acme 401k", "plan_type": "401k"}
|
||||
resp = client.post("/api/plan-info", json=payload)
|
||||
assert resp.status_code == 200, resp.text
|
||||
resp = client.get("/api/plan-info")
|
||||
assert resp.status_code == 200
|
||||
ids = {row["plan_id"] for row in resp.json()}
|
||||
assert plan_id in ids
|
||||
|
||||
|
||||
def test_qdro_document_generation_uses_template_system(client: TestClient):
|
||||
# Create file + qdro
|
||||
_, file_no = _create_customer_and_file(client)
|
||||
resp = client.post("/api/qdros", json={"file_no": file_no, "pet": "Alice", "res": "Bob"})
|
||||
qid = resp.json()["id"]
|
||||
|
||||
# Upload a template through templates API
|
||||
files = {
|
||||
"file": (
|
||||
"qdro.docx",
|
||||
_dummy_docx_bytes(),
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
)
|
||||
}
|
||||
resp = client.post(
|
||||
"/api/templates/upload",
|
||||
data={"name": f"QDRO Template {uuid.uuid4().hex[:6]}", "semantic_version": "1.0.0"},
|
||||
files=files,
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
tpl_id = resp.json()["id"]
|
||||
|
||||
# Generate using our endpoint
|
||||
resp = client.post(f"/api/qdros/{qid}/generate-document", json={"template_id": tpl_id, "context": {}})
|
||||
assert resp.status_code == 200, resp.text
|
||||
body = resp.json()
|
||||
assert isinstance(body.get("resolved"), dict)
|
||||
assert isinstance(body.get("unresolved"), list)
|
||||
assert body.get("output_size", 0) >= 0
|
||||
|
||||
|
||||
def test_qdro_transition_authorization(client: TestClient):
|
||||
# Override to non-admin, non-approver user
|
||||
class _LimitedUser:
|
||||
def __init__(self):
|
||||
self.id = 2
|
||||
self.username = "limited"
|
||||
self.is_admin = False
|
||||
self.is_active = True
|
||||
self.is_approver = False
|
||||
|
||||
app.dependency_overrides[get_current_user] = lambda: _LimitedUser()
|
||||
try:
|
||||
# Set up file and qdro
|
||||
# Need a client with current override; use existing fixture client indirectly via app
|
||||
local_client = TestClient(app)
|
||||
_, file_no = _create_customer_and_file(local_client)
|
||||
resp = local_client.post("/api/qdros", json={"file_no": file_no})
|
||||
assert resp.status_code == 200, resp.text
|
||||
qid = resp.json()["id"]
|
||||
|
||||
# DRAFT -> APPROVAL_PENDING allowed
|
||||
resp = local_client.post(f"/api/qdros/{qid}/submit-for-approval", json={"reason": "send"})
|
||||
assert resp.status_code == 200, resp.text
|
||||
assert resp.json()["status"] == "APPROVAL_PENDING"
|
||||
|
||||
# APPROVAL_PENDING -> APPROVED forbidden for non-approver
|
||||
resp = local_client.post(f"/api/qdros/{qid}/approve", json={"reason": "ok"})
|
||||
assert_http_error(resp, 403, "Not enough permissions")
|
||||
|
||||
# APPROVAL_PENDING -> FILED forbidden for non-approver
|
||||
resp = local_client.post(f"/api/qdros/{qid}/file", json={"reason": "file"})
|
||||
assert_http_error(resp, 403, "Not enough permissions")
|
||||
|
||||
# Generic transition to APPROVED forbidden
|
||||
resp = local_client.post(f"/api/qdros/{qid}/transition", json={"target_status": "APPROVED"})
|
||||
assert_http_error(resp, 403, "Not enough permissions")
|
||||
finally:
|
||||
app.dependency_overrides.pop(get_current_user, None)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import io
|
||||
import uuid
|
||||
from fastapi.testclient import TestClient
|
||||
import pytest
|
||||
|
||||
@@ -393,3 +394,70 @@ def test_templates_categories_listing(client: TestClient):
|
||||
assert by_cat_all.get("K1", 0) >= 2
|
||||
assert by_cat_all.get("K2", 0) >= 1
|
||||
|
||||
|
||||
|
||||
def test_templates_download_current_version(client: TestClient):
|
||||
# Upload a DOCX template
|
||||
payload = {
|
||||
"name": f"Download Letter {uuid.uuid4().hex[:8]}",
|
||||
"category": "GENERAL",
|
||||
"description": "Download test",
|
||||
"semantic_version": "1.0.0",
|
||||
}
|
||||
filename = "letter.docx"
|
||||
content_type = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
data_bytes = _dummy_docx_bytes()
|
||||
files = {
|
||||
"file": (filename, data_bytes, content_type),
|
||||
}
|
||||
resp = client.post("/api/templates/upload", data=payload, files=files)
|
||||
assert resp.status_code == 200, resp.text
|
||||
tpl_id = resp.json()["id"]
|
||||
|
||||
# Download current approved version
|
||||
resp = client.get(f"/api/templates/{tpl_id}/download")
|
||||
assert resp.status_code == 200, resp.text
|
||||
# Verify headers
|
||||
assert resp.headers.get("content-type") == content_type
|
||||
cd = resp.headers.get("content-disposition", "")
|
||||
assert "attachment;" in cd and filename in cd
|
||||
# Body should be non-empty and equal to uploaded bytes
|
||||
assert resp.content == data_bytes
|
||||
|
||||
|
||||
def test_templates_download_specific_version_by_id(client: TestClient):
|
||||
# Upload initial version
|
||||
files_v1 = {"file": ("v1.docx", _docx_with_tokens("V1"), "application/vnd.openxmlformats-officedocument.wordprocessingml.document")}
|
||||
resp = client.post(
|
||||
"/api/templates/upload",
|
||||
data={"name": f"MultiVersion {uuid.uuid4().hex[:8]}", "semantic_version": "1.0.0"},
|
||||
files=files_v1,
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
tpl_id = resp.json()["id"]
|
||||
|
||||
# Add a second version (do not approve so current stays V1)
|
||||
v2_bytes = _docx_with_tokens("V2 unique")
|
||||
files_v2 = {"file": ("v2.docx", v2_bytes, "application/vnd.openxmlformats-officedocument.wordprocessingml.document")}
|
||||
resp2 = client.post(
|
||||
f"/api/templates/{tpl_id}/versions",
|
||||
data={"semantic_version": "1.1.0", "approve": False},
|
||||
files=files_v2,
|
||||
)
|
||||
assert resp2.status_code == 200, resp2.text
|
||||
|
||||
# Find versions to get v2 id
|
||||
resp_list = client.get(f"/api/templates/{tpl_id}/versions")
|
||||
assert resp_list.status_code == 200
|
||||
versions = resp_list.json()
|
||||
# v2 should be in list; grab the one with semantic_version 1.1.0
|
||||
v2 = next(v for v in versions if v["semantic_version"] == "1.1.0")
|
||||
v2_id = v2["id"]
|
||||
|
||||
# Download specifically v2
|
||||
resp_dl = client.get(f"/api/templates/{tpl_id}/download", params={"version_id": v2_id})
|
||||
assert resp_dl.status_code == 200, resp_dl.text
|
||||
assert resp_dl.headers.get("content-type") == "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
cd2 = resp_dl.headers.get("content-disposition", "")
|
||||
assert "v2.docx" in cd2
|
||||
assert resp_dl.content == v2_bytes
|
||||
|
||||
Reference in New Issue
Block a user