Enterprise UI redesign — Phases 14–22 (Docusign-branded migration console) #1
|
|
@ -325,6 +325,46 @@ def merge_tabs(acc: dict, new: dict) -> dict:
|
|||
return acc
|
||||
|
||||
|
||||
# Tab types DocuSign forbids as conditional parents (auto-filled or action tabs)
|
||||
_INVALID_PARENT_TAB_TYPES = {
|
||||
"signHereTabs", "initialHereTabs", "dateSignedTabs",
|
||||
"fullNameTabs", "emailTabs", "titleTabs", "signerAttachmentTabs",
|
||||
}
|
||||
|
||||
|
||||
def _strip_invalid_conditionals(signers: list, warnings: list) -> None:
|
||||
"""
|
||||
Remove conditionalParentLabel/Value from any tab whose parent label either
|
||||
doesn't exist in the template or points to a tab type DocuSign forbids as a
|
||||
parent (signature, initial, auto-filled). Mutates signers in place.
|
||||
"""
|
||||
for signer in signers:
|
||||
tabs = signer.get("tabs", {})
|
||||
|
||||
# Collect valid parent labels: only tab types allowed as parents
|
||||
valid_labels: set[str] = set()
|
||||
for tab_type, tab_list in tabs.items():
|
||||
if tab_type in _INVALID_PARENT_TAB_TYPES:
|
||||
continue
|
||||
for tab in tab_list:
|
||||
label = tab.get("tabLabel") or tab.get("groupName")
|
||||
if label:
|
||||
valid_labels.add(label)
|
||||
|
||||
# Strip references to invalid/missing parents
|
||||
for tab_type, tab_list in tabs.items():
|
||||
for tab in tab_list:
|
||||
parent = tab.get("conditionalParentLabel")
|
||||
if parent and parent not in valid_labels:
|
||||
warnings.append(
|
||||
f"Conditional parent '{parent}' not found or not a valid "
|
||||
f"parent tab type — conditional stripped from tab "
|
||||
f"'{tab.get('tabLabel', '?')}'"
|
||||
)
|
||||
tab.pop("conditionalParentLabel", None)
|
||||
tab.pop("conditionalParentValue", None)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main compose function
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -386,6 +426,10 @@ def compose_template(template_dir: str, output_path: str) -> tuple[dict, list[st
|
|||
tabs = _apply_conditional_to_tabs(tabs, field, warnings)
|
||||
signers[idx]["tabs"] = merge_tabs(signers[idx]["tabs"], tabs)
|
||||
|
||||
# Post-process: strip conditionalParentLabel references that point to
|
||||
# non-existent or invalid parents (signature/initial tabs can't be parents).
|
||||
_strip_invalid_conditionals(signers, warnings)
|
||||
|
||||
template = {
|
||||
"name": metadata.get("name", template_dir.name),
|
||||
"description": f"Migrated from Adobe Sign — original owner: {metadata.get('ownerEmail', '')}",
|
||||
|
|
|
|||
|
|
@ -51,7 +51,14 @@ class TestVerifySend:
|
|||
|
||||
@respx.mock
|
||||
def test_send_returns_envelope_id(self):
|
||||
"""Authenticated + valid template → envelope_id returned."""
|
||||
"""Authenticated + valid template → role names fetched, envelope_id returned."""
|
||||
respx.get(
|
||||
f"{DS_BASE}/v2.1/accounts/{DS_ACCOUNT}/templates/{TEMPLATE_ID}"
|
||||
).mock(return_value=httpx.Response(200, json={
|
||||
"recipients": {
|
||||
"signers": [{"roleName": "Customer", "recipientId": "1"}],
|
||||
}
|
||||
}))
|
||||
respx.post(
|
||||
f"{DS_BASE}/v2.1/accounts/{DS_ACCOUNT}/envelopes"
|
||||
).mock(return_value=httpx.Response(201, json={"envelopeId": ENVELOPE_ID}))
|
||||
|
|
@ -67,10 +74,32 @@ class TestVerifySend:
|
|||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["envelope_id"] == ENVELOPE_ID
|
||||
assert resp.json()["roles"] == ["Customer"]
|
||||
|
||||
@respx.mock
|
||||
def test_send_falls_back_to_signer_role_on_template_error(self):
|
||||
"""Template fetch failure → falls back to 'Signer' role name."""
|
||||
respx.get(
|
||||
f"{DS_BASE}/v2.1/accounts/{DS_ACCOUNT}/templates/bad-id"
|
||||
).mock(return_value=httpx.Response(404, json={"message": "Not found"}))
|
||||
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": "bad-id", "recipient_name": "X", "recipient_email": "x@x.com"},
|
||||
cookies={_COOKIE_NAME: _ds_session()},
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["roles"] == ["Signer"]
|
||||
|
||||
@respx.mock
|
||||
def test_send_propagates_docusign_error(self):
|
||||
"""DocuSign 400 → 502 with error detail."""
|
||||
"""DocuSign 400 on envelope create → 502 with error detail."""
|
||||
respx.get(
|
||||
f"{DS_BASE}/v2.1/accounts/{DS_ACCOUNT}/templates/bad-id"
|
||||
).mock(return_value=httpx.Response(200, json={"recipients": {}}))
|
||||
respx.post(
|
||||
f"{DS_BASE}/v2.1/accounts/{DS_ACCOUNT}/envelopes"
|
||||
).mock(return_value=httpx.Response(400, json={"message": "Invalid templateId"}))
|
||||
|
|
|
|||
Loading…
Reference in New Issue