From d29538ae5102285c4cb6b170f5ce7a980c718693 Mon Sep 17 00:00:00 2001 From: a2a-cloud Date: Sat, 18 Jul 2026 02:32:44 +0000 Subject: [PATCH] a2a-source-edit: write tests/test_full_stack_contract.py --- tests/test_full_stack_contract.py | 142 +++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/tests/test_full_stack_contract.py b/tests/test_full_stack_contract.py index 59cee34..0be4181 100644 --- a/tests/test_full_stack_contract.py +++ b/tests/test_full_stack_contract.py @@ -1,9 +1,136 @@ +import base64 +import json from pathlib import Path +import pytest +from a2a_pack import LocalRunContext, PlatformUserAuth from a2a_pack.cli.local import load_local_project +from agent import QuoteJudgeStudioV1 + ROOT = Path(__file__).resolve().parents[1] +SUCCESS_QUOTES = [ + {"vendor": "Acme Bearings", "unit_price": 10.75, "quantity": 500, "delivery_days": 12, "warranty_months": 12}, + {"vendor": "Beta Industrial", "unit_price": 9.1, "quantity": 500, "delivery_days": 8, "warranty_months": 18}, +] +SUCCESS_WEIGHTS = {"price": 50, "delivery": 30, "warranty": 20} + + +class FakeCursor: + def __init__(self, row=None): + self._row = row + + def fetchone(self): + return self._row + + +class FakeConn: + store = {} + receipts = [] + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc, tb): + return False + + def execute(self, sql, params): + normalized = " ".join(sql.lower().split()) + if normalized.startswith("insert into quote_comparisons"): + tenant, comparison_id, vendor, score, payload_json = params + self.store[(tenant, comparison_id)] = json.loads(payload_json) + return FakeCursor() + if normalized.startswith("select payload from quote_comparisons"): + tenant, comparison_id = params + payload = self.store.get((tenant, comparison_id)) + return FakeCursor((payload,) if payload else None) + if normalized.startswith("insert into quote_execution_receipts"): + self.receipts.append(params) + return FakeCursor() + raise AssertionError(f"unexpected SQL: {sql}") + + +@pytest.fixture(autouse=True) +def fake_db(monkeypatch): + FakeConn.store = {} + FakeConn.receipts = [] + monkeypatch.setenv("DATABASE_URL", "postgresql://example.invalid/quotejudge") + monkeypatch.setattr("agent._connect", lambda: FakeConn()) + + +@pytest.fixture +def ctx(): + return LocalRunContext( + auth=PlatformUserAuth(sub="user-123", user_id=123, email="buyer@example.com", scopes=["agent:invoke"]), + task_id="test-task", + ) + + +@pytest.mark.asyncio +async def test_success_failure_reload_and_receipt(ctx): + agent = QuoteJudgeStudioV1() + success = await agent.invoke_json( + "compare_quotes", + ctx, + {"comparison_id": "studio-quote-v1", "quotes": SUCCESS_QUOTES, "weights": SUCCESS_WEIGHTS}, + ) + assert success["ok"] is True + assert success["comparison_id"] == "studio-quote-v1" + assert success["recommendation"]["vendor"] == "Beta Industrial" + assert success["quotes"][0]["weighted_score"] == 100.0 + assert FakeConn.receipts, "production execution receipt should be persisted" + + failure = await agent.invoke_json( + "compare_quotes", + ctx, + { + "comparison_id": "studio-quote-invalid", + "quotes": [{"vendor": "Only Vendor", "unit_price": 10, "quantity": 1, "delivery_days": 5, "warranty_months": 6}], + "weights": SUCCESS_WEIGHTS, + }, + ) + assert failure["ok"] is False + assert failure["code"] == "at_least_two_quotes_required" + + reload = await agent.invoke_json("get_comparison", ctx, {"comparison_id": "studio-quote-v1"}) + assert reload["ok"] is True + assert reload["comparison_id"] == "studio-quote-v1" + assert reload["recommendation"]["vendor"] == "Beta Industrial" + assert reload["reloaded"] is True + + +@pytest.mark.asyncio +async def test_browser_upload_bridge(ctx): + agent = QuoteJudgeStudioV1() + csv_text = "vendor,unit_price,quantity,delivery_days,warranty_months\nAcme Bearings,10.75,500,12,12\nBeta Industrial,9.10,500,8,18\n" + result = await agent.invoke_json( + "compare_quotes_upload", + ctx, + { + "comparison_id": "upload-quote-v1", + "upload": { + "filename": "quotes.csv", + "media_type": "text/csv", + "data_base64": base64.b64encode(csv_text.encode()).decode(), + }, + "weights": SUCCESS_WEIGHTS, + }, + ) + assert result["ok"] is True + assert result["recommendation"]["vendor"] == "Beta Industrial" + + invalid = await agent.invoke_json( + "compare_quotes_upload", + ctx, + { + "comparison_id": "bad-upload", + "upload": {"filename": "quotes.csv", "media_type": "text/csv", "data_base64": "%%%not-base64%%%"}, + "weights": SUCCESS_WEIGHTS, + }, + ) + assert invalid["ok"] is False + assert invalid["code"] == "invalid_base64" def test_full_stack_product_contract(): @@ -12,17 +139,30 @@ def test_full_stack_product_contract(): frontend = (ROOT / "frontend" / "src" / "App.jsx").read_text(encoding="utf-8") assert "frontend:" in manifest and "mount: /app" in manifest + assert "public: false" in manifest assert "resources:" in manifest and "databases:" in manifest assert "migrations:" in manifest and "db/migrations" in manifest assert "PlatformUserAuth" in source assert "AgentPlatformResources" in source and "AgentDatabase(" in source - assert "Skill runner" not in frontend, "replace the generic scaffold with the product workflow" + assert "Skill runner" not in frontend + assert "QuoteJudge" in frontend + assert "data_base64" in frontend + assert "DATABASE_URL" not in frontend migrations = list((ROOT / "db" / "migrations").glob("*.sql")) assert migrations and all(path.read_text(encoding="utf-8").strip() for path in migrations) card = load_local_project(ROOT).agent_cls().card() + skill_names = {skill.name for skill in card.skills} + assert {"compare_quotes", "get_comparison", "compare_quotes_upload", "compare_quotes_file"} <= skill_names + compare_schema = next(skill.input_schema for skill in card.skills if skill.name == "compare_quotes") + assert set(compare_schema["required"]) == {"comparison_id", "quotes", "weights"} + upload_schema = next(skill.input_schema for skill in card.skills if skill.name == "compare_quotes_file") + assert upload_schema["properties"]["document"]["x-a2a-file-upload"]["max_bytes"] == 65536 databases = card.runtime.platform_resources.databases assert databases, "live Agent Card must declare its managed database" + assert databases[0].scope == "user" assert databases[0].migrations is not None assert databases[0].migrations.path == "db/migrations" + assert card.mcp_endpoint == "/mcp" + assert card.runtime.pricing.caller_pays_llm is False