128 lines
4.5 KiB
Python
128 lines
4.5 KiB
Python
"""
|
|
tests/test_upload_upsert.py
|
|
---------------------------
|
|
Tests for idempotent (upsert) upload logic in upload_docusign_template.py.
|
|
|
|
All DocuSign API calls are mocked with responses; no live account needed.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
import responses as rsps_lib
|
|
|
|
# Ensure src/ is importable before import
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
|
|
|
# Patch get_access_token at import time so the module never tries to load a private key
|
|
with patch("docusign_auth.get_access_token", return_value="fake-token"):
|
|
import upload_docusign_template
|
|
|
|
BASE_URL = "https://demo.docusign.net/restapi"
|
|
ACCOUNT_ID = "test-account-id"
|
|
TEMPLATE_NAME = "My NDA Template"
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def env_vars(monkeypatch):
|
|
monkeypatch.setenv("DOCUSIGN_ACCOUNT_ID", ACCOUNT_ID)
|
|
monkeypatch.setenv("DOCUSIGN_BASE_URL", BASE_URL)
|
|
|
|
|
|
@pytest.fixture()
|
|
def template_file():
|
|
"""Write a minimal template JSON to a temp file."""
|
|
template = {"name": TEMPLATE_NAME, "description": "test template"}
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
json.dump(template, f)
|
|
path = f.name
|
|
yield path
|
|
os.unlink(path)
|
|
|
|
|
|
def _list_url():
|
|
return f"{BASE_URL}/v2.1/accounts/{ACCOUNT_ID}/templates"
|
|
|
|
|
|
def _update_url(template_id):
|
|
return f"{BASE_URL}/v2.1/accounts/{ACCOUNT_ID}/templates/{template_id}"
|
|
|
|
|
|
@rsps_lib.activate
|
|
def test_creates_when_no_match(template_file):
|
|
"""No existing templates with this name → POST called, new ID returned."""
|
|
new_id = "new-template-abc"
|
|
|
|
rsps_lib.add(rsps_lib.GET, _list_url(), json={"envelopeTemplates": []}, status=200)
|
|
rsps_lib.add(rsps_lib.POST, _list_url(), json={"templateId": new_id}, status=201)
|
|
|
|
with patch.object(upload_docusign_template, "get_access_token", return_value="fake-token"):
|
|
result = upload_docusign_template.upload_template(template_file)
|
|
|
|
assert result == new_id
|
|
methods = [c.request.method for c in rsps_lib.calls]
|
|
assert methods == ["GET", "POST"]
|
|
|
|
|
|
@rsps_lib.activate
|
|
def test_updates_most_recent_when_match(template_file):
|
|
"""Two exact-name matches → PUT called on the most recently modified one."""
|
|
older_id = "template-older"
|
|
newer_id = "template-newer"
|
|
|
|
existing = [
|
|
{"templateId": older_id, "name": TEMPLATE_NAME, "lastModified": "2026-04-10T10:00:00.000Z"},
|
|
{"templateId": newer_id, "name": TEMPLATE_NAME, "lastModified": "2026-04-15T10:00:00.000Z"},
|
|
]
|
|
rsps_lib.add(rsps_lib.GET, _list_url(), json={"envelopeTemplates": existing}, status=200)
|
|
rsps_lib.add(rsps_lib.PUT, _update_url(newer_id), json={}, status=200)
|
|
|
|
with patch.object(upload_docusign_template, "get_access_token", return_value="fake-token"):
|
|
result = upload_docusign_template.upload_template(template_file)
|
|
|
|
assert result == newer_id
|
|
put_calls = [c for c in rsps_lib.calls if c.request.method == "PUT"]
|
|
assert len(put_calls) == 1
|
|
assert newer_id in put_calls[0].request.url
|
|
|
|
|
|
@rsps_lib.activate
|
|
def test_force_create_bypasses_upsert(template_file):
|
|
"""force_create=True → always POST, no GET for existing templates."""
|
|
new_id = "force-created-id"
|
|
|
|
rsps_lib.add(rsps_lib.POST, _list_url(), json={"templateId": new_id}, status=201)
|
|
|
|
with patch.object(upload_docusign_template, "get_access_token", return_value="fake-token"):
|
|
result = upload_docusign_template.upload_template(template_file, force_create=True)
|
|
|
|
assert result == new_id
|
|
get_calls = [c for c in rsps_lib.calls if c.request.method == "GET"]
|
|
assert len(get_calls) == 0
|
|
post_calls = [c for c in rsps_lib.calls if c.request.method == "POST"]
|
|
assert len(post_calls) == 1
|
|
|
|
|
|
@rsps_lib.activate
|
|
def test_partial_name_match_ignored(template_file):
|
|
"""DocuSign search_text is substring; we must reject partial-name results and POST."""
|
|
partial_id = "partial-match-id"
|
|
|
|
existing = [
|
|
{"templateId": partial_id, "name": "My NDA Template (Copy)", "lastModified": "2026-04-15T10:00:00.000Z"},
|
|
]
|
|
rsps_lib.add(rsps_lib.GET, _list_url(), json={"envelopeTemplates": existing}, status=200)
|
|
new_id = "created-new-id"
|
|
rsps_lib.add(rsps_lib.POST, _list_url(), json={"templateId": new_id}, status=201)
|
|
|
|
with patch.object(upload_docusign_template, "get_access_token", return_value="fake-token"):
|
|
result = upload_docusign_template.upload_template(template_file)
|
|
|
|
assert result == new_id
|
|
put_calls = [c for c in rsps_lib.calls if c.request.method == "PUT"]
|
|
assert len(put_calls) == 0
|