Harden QuoteJudge managed persistence

This commit is contained in:
2026-07-18 03:40:55 -03:00
parent 9a72ee38fe
commit 02c4b4fe68
5 changed files with 104 additions and 31 deletions

View File

@@ -2,12 +2,14 @@ import asyncio
import base64
import json
from pathlib import Path
from unittest.mock import Mock
from a2a_pack.auth import PlatformUserAuth
from a2a_pack.cli.local import load_local_project
from a2a_pack.context import LocalRunContext
from agent import QuoteJudgeStudioV1, _LOCAL_COMPARISONS, _LOCAL_RECEIPTS
import agent as agent_module
from agent import QuoteJudgeStudioV1
ROOT = Path(__file__).resolve().parents[1]
@@ -54,11 +56,28 @@ def test_full_stack_product_contract():
assert databases[0].migrations is not None
assert databases[0].migrations.path == "db/migrations"
assert card.runtime.pricing.caller_pays_llm is False
assert card.version == "0.1.4"
def test_success_failure_reload_and_receipt_contract():
_LOCAL_COMPARISONS.clear()
_LOCAL_RECEIPTS.clear()
def test_success_failure_reload_and_receipt_contract(monkeypatch):
comparisons = {}
receipts = []
def persist(tenant_key, comparison_id, result, receipt):
comparisons[(tenant_key, comparison_id)] = {
"result": json.loads(json.dumps(result)),
"updated_at": receipt["created_at"],
"latest_receipt_id": receipt["receipt_id"],
}
receipts.append(receipt)
return {"ok": True}
monkeypatch.setattr(agent_module, "_persist_comparison", persist)
monkeypatch.setattr(
agent_module,
"_load_comparison",
lambda tenant_key, comparison_id: comparisons.get((tenant_key, comparison_id)),
)
agent = QuoteJudgeStudioV1()
quotes = [
{"vendor": "Acme Bearings", "quantity": 500, "unit_price": 10.75, "delivery_days": 12, "warranty_months": 12},
@@ -72,7 +91,8 @@ def test_success_failure_reload_and_receipt_contract():
assert success["recommendation"]["vendor"] == "Beta Industrial"
assert success["receipt"]["receipt_id"].startswith("qjr_")
assert success["receipt"]["persisted"] is True
assert _LOCAL_RECEIPTS and _LOCAL_RECEIPTS[-1]["schema"] == "quotejudge.production_receipt.v1"
assert receipts and receipts[-1]["schema"] == "quotejudge.production_receipt.v1"
assert receipts[-1]["agent_version"] == "0.1.4"
assert "tenant_key" not in json.dumps(success)
reload = run(agent.local_invoke("get_comparison", auth=AUTH, comparison_id="studio-quote-v1"))
@@ -91,8 +111,8 @@ def test_success_failure_reload_and_receipt_contract():
assert failure == {"ok": False, "code": "at_least_two_quotes_required", "comparison_id": "studio-quote-invalid"}
def test_browser_upload_bridge_success_and_invalid_base64():
_LOCAL_COMPARISONS.clear()
def test_browser_upload_bridge_success_and_invalid_base64(monkeypatch):
monkeypatch.setattr(agent_module, "_persist_comparison", lambda *_args: {"ok": True})
agent = QuoteJudgeStudioV1()
payload = base64.b64encode(json.dumps({
"quotes": [
@@ -124,6 +144,56 @@ def test_browser_upload_bridge_success_and_invalid_base64():
assert invalid["code"] == "invalid_base64"
def test_persistence_fails_closed_without_managed_database(monkeypatch):
monkeypatch.delenv("DATABASE_URL", raising=False)
receipt = {"receipt_id": "qjr_test", "created_at": "2026-01-01T00:00:00+00:00"}
persisted = agent_module._persist_comparison("user:123", "quote-1", {}, receipt)
loaded = agent_module._load_comparison("user:123", "quote-1")
assert persisted == {"ok": False, "code": "persistence_unavailable"}
assert loaded == {"ok": False, "code": "persistence_unavailable"}
def test_database_exception_returns_stable_failure_code(monkeypatch):
monkeypatch.setenv("DATABASE_URL", "postgresql://example.invalid/db")
monkeypatch.setattr(agent_module, "psycopg", Mock())
def unavailable():
raise TimeoutError("database connection timed out")
monkeypatch.setattr(agent_module, "_connect", unavailable)
persisted = agent_module._persist_comparison(
"user:123",
"quote-1",
{"weights": {}, "quote_count": 2, "recommendation": {"vendor": "Acme"}},
{
"receipt_id": "qjr_test",
"created_at": "2026-01-01T00:00:00+00:00",
"skill_name": "compare_quotes",
"status": "ok",
"input_hash": "input",
"result_hash": "result",
},
)
assert persisted == {"ok": False, "code": "persistence_unavailable"}
def test_database_connection_has_hard_timeouts(monkeypatch):
fake_psycopg = Mock()
fake_psycopg.connect.return_value = object()
monkeypatch.setattr(agent_module, "psycopg", fake_psycopg)
monkeypatch.setenv("DATABASE_URL", "postgresql://example.invalid/db")
agent_module._connect()
_, kwargs = fake_psycopg.connect.call_args
assert kwargs["connect_timeout"] == 5
assert "statement_timeout=10000" in kwargs["options"]
assert "lock_timeout=5000" in kwargs["options"]
def test_mcp_tool_schemas_are_bounded_and_typed():
card = load_local_project(ROOT).agent_cls().card()
skills = {skill.name: skill for skill in card.skills}