""" 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