Make ContractClock persistence atomic
This commit is contained in:
@@ -82,21 +82,30 @@ def test_acceptance_success_reload_failure_and_receipt_persistence():
|
||||
saved = {}
|
||||
receipts = []
|
||||
|
||||
def fake_persist_contract(tenant, contract_id, title, payload, receipt_id):
|
||||
def fake_persist_contract_with_receipt(
|
||||
tenant,
|
||||
contract_id,
|
||||
title,
|
||||
payload,
|
||||
receipt_id,
|
||||
skill_name,
|
||||
status,
|
||||
):
|
||||
assert tenant == "user:123"
|
||||
saved[(tenant, contract_id)] = {
|
||||
"payload": payload,
|
||||
"receipt_id": receipt_id,
|
||||
"updated_at": "2026-01-01T00:00:00+00:00",
|
||||
}
|
||||
receipts.append((tenant, receipt_id, skill_name, contract_id, status))
|
||||
|
||||
def fake_load_contract(tenant, contract_id):
|
||||
return saved.get((tenant, contract_id))
|
||||
|
||||
def fake_persist_receipt(tenant, receipt_id, skill_name, contract_id, payload, status):
|
||||
receipts.append((tenant, receipt_id, skill_name, contract_id, status))
|
||||
|
||||
with patch("agent.persist_contract", side_effect=fake_persist_contract), patch("agent.load_contract", side_effect=fake_load_contract), patch("agent.persist_receipt", side_effect=fake_persist_receipt):
|
||||
with patch(
|
||||
"agent.persist_contract_with_receipt",
|
||||
side_effect=fake_persist_contract_with_receipt,
|
||||
), patch("agent.load_contract", side_effect=fake_load_contract):
|
||||
result = asyncio.run(agent.analyze_contract(ctx(), contract_id="studio-contract-v1", text=SUCCESS_TEXT, title="Studio Renewal Agreement"))
|
||||
assert result.ok is True
|
||||
assert result.persisted is True
|
||||
@@ -124,7 +133,7 @@ def test_acceptance_success_reload_failure_and_receipt_persistence():
|
||||
def test_browser_upload_bridge_bounds_and_reuses_workflow():
|
||||
agent = ContractClockStudioV1()
|
||||
encoded = base64.b64encode(SUCCESS_TEXT.encode("utf-8")).decode("ascii")
|
||||
with patch("agent.persist_contract", return_value=None), patch("agent.persist_receipt", return_value=None):
|
||||
with patch("agent.persist_contract_with_receipt", return_value=None):
|
||||
result = asyncio.run(
|
||||
agent.analyze_contract_browser_upload(
|
||||
ctx(),
|
||||
@@ -146,6 +155,31 @@ def test_browser_upload_bridge_bounds_and_reuses_workflow():
|
||||
assert bad.ok is False and bad.code == "invalid_base64"
|
||||
|
||||
|
||||
def test_tools_return_structured_auth_failure_for_missing_principal():
|
||||
anonymous = LocalRunContext(
|
||||
auth=PlatformUserAuth(sub=""),
|
||||
caller="tester",
|
||||
)
|
||||
agent = ContractClockStudioV1()
|
||||
|
||||
analyzed = asyncio.run(
|
||||
agent.analyze_contract(
|
||||
anonymous,
|
||||
contract_id="studio-contract-v1",
|
||||
text=SUCCESS_TEXT,
|
||||
title="Studio Renewal Agreement",
|
||||
)
|
||||
)
|
||||
loaded = asyncio.run(
|
||||
agent.get_contract_deadlines(anonymous, contract_id="studio-contract-v1")
|
||||
)
|
||||
listed = asyncio.run(agent.list_contracts(anonymous))
|
||||
|
||||
assert analyzed.ok is False and analyzed.code == "auth_required"
|
||||
assert loaded.ok is False and loaded.code == "auth_required"
|
||||
assert listed.ok is False and listed.code == "auth_required"
|
||||
|
||||
|
||||
def test_receipt_id_is_deterministic_without_secrets():
|
||||
payload = {"contract_id": "studio-contract-v1", "deadlines": [{"kind": "renewal", "date": "2026-12-31"}]}
|
||||
receipt = production_receipt_id("user:123", "analyze_contract", payload)
|
||||
|
||||
Reference in New Issue
Block a user