a2a-source-edit: write tests/test_full_stack_contract.py
This commit is contained in:
@@ -1,10 +1,24 @@
|
|||||||
from pathlib import Path
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from a2a_pack import LocalRunContext, PlatformUserAuth
|
||||||
from a2a_pack.cli.local import load_local_project
|
from a2a_pack.cli.local import load_local_project
|
||||||
|
|
||||||
|
from agent import QuoteInput, QuoteJudgeStudioV1, QuoteWeights, _build_comparison_result
|
||||||
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
SUCCESS_QUOTES = [
|
||||||
|
QuoteInput(vendor="Acme Bearings", unit_price=10.75, quantity=500, delivery_days=12, warranty_months=12),
|
||||||
|
QuoteInput(vendor="Beta Industrial", unit_price=9.1, quantity=500, delivery_days=8, warranty_months=18),
|
||||||
|
]
|
||||||
|
SUCCESS_WEIGHTS = QuoteWeights(price=50, delivery=30, warranty=20)
|
||||||
|
|
||||||
|
|
||||||
def test_full_stack_product_contract():
|
def test_full_stack_product_contract():
|
||||||
manifest = (ROOT / "a2a.yaml").read_text(encoding="utf-8")
|
manifest = (ROOT / "a2a.yaml").read_text(encoding="utf-8")
|
||||||
@@ -12,17 +26,79 @@ def test_full_stack_product_contract():
|
|||||||
frontend = (ROOT / "frontend" / "src" / "App.jsx").read_text(encoding="utf-8")
|
frontend = (ROOT / "frontend" / "src" / "App.jsx").read_text(encoding="utf-8")
|
||||||
|
|
||||||
assert "frontend:" in manifest and "mount: /app" in manifest
|
assert "frontend:" in manifest and "mount: /app" in manifest
|
||||||
|
assert "public: false" in manifest
|
||||||
assert "resources:" in manifest and "databases:" in manifest
|
assert "resources:" in manifest and "databases:" in manifest
|
||||||
assert "migrations:" in manifest and "db/migrations" in manifest
|
assert "migrations:" in manifest and "db/migrations" in manifest
|
||||||
assert "PlatformUserAuth" in source
|
assert "PlatformUserAuth" in source
|
||||||
assert "AgentPlatformResources" in source and "AgentDatabase(" in source
|
assert "AgentPlatformResources" in source and "AgentDatabase(" in source
|
||||||
|
assert "LLMProvisioning" not in source
|
||||||
|
assert "ctx.llm" not in source
|
||||||
assert "Skill runner" not in frontend, "replace the generic scaffold with the product workflow"
|
assert "Skill runner" not in frontend, "replace the generic scaffold with the product workflow"
|
||||||
|
forbidden = ["A2A_LITELLM_KEY", "OPENAI_API_KEY", "svc.cluster.local", "DATABASE_URL"]
|
||||||
|
assert not any(secret in frontend for secret in forbidden)
|
||||||
|
|
||||||
migrations = list((ROOT / "db" / "migrations").glob("*.sql"))
|
migrations = list((ROOT / "db" / "migrations").glob("*.sql"))
|
||||||
assert migrations and all(path.read_text(encoding="utf-8").strip() for path in migrations)
|
assert migrations and all(path.read_text(encoding="utf-8").strip() for path in migrations)
|
||||||
|
|
||||||
card = load_local_project(ROOT).agent_cls().card()
|
card = load_local_project(ROOT).agent_cls().card()
|
||||||
|
skill_names = {skill.name for skill in card.skills}
|
||||||
|
assert {"compare_quotes", "get_comparison", "compare_quotes_from_browser_upload", "compare_quotes_from_upload"}.issubset(skill_names)
|
||||||
databases = card.runtime.platform_resources.databases
|
databases = card.runtime.platform_resources.databases
|
||||||
assert databases, "live Agent Card must declare its managed database"
|
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 is not None
|
||||||
assert databases[0].migrations.path == "db/migrations"
|
assert databases[0].migrations.path == "db/migrations"
|
||||||
|
assert card.runtime.pricing.caller_pays_llm is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_scoring_success_fixture_is_deterministic():
|
||||||
|
result = _build_comparison_result("studio-quote-v1", SUCCESS_QUOTES, SUCCESS_WEIGHTS)
|
||||||
|
assert result["ok"] is True
|
||||||
|
assert result["comparison_id"] == "studio-quote-v1"
|
||||||
|
assert result["recommendation"]["vendor"] == "Beta Industrial"
|
||||||
|
beta = result["comparison_table"][0]
|
||||||
|
assert beta["vendor"] == "Beta Industrial"
|
||||||
|
assert beta["total_price"] == 4550.0
|
||||||
|
assert beta["scores"]["weighted_total"] == 100.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_failure_fixture_returns_actionable_validation():
|
||||||
|
result = _build_comparison_result(
|
||||||
|
"studio-quote-invalid",
|
||||||
|
[QuoteInput(vendor="Only Vendor", unit_price=10, quantity=1, delivery_days=5, warranty_months=6)],
|
||||||
|
SUCCESS_WEIGHTS,
|
||||||
|
)
|
||||||
|
assert result["ok"] is False
|
||||||
|
assert result["code"] == "at_least_two_quotes_required"
|
||||||
|
assert result["validation_errors"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_success_and_reload_persist_with_receipt():
|
||||||
|
saved: dict[tuple[str, str], dict] = {}
|
||||||
|
receipts: list[dict] = []
|
||||||
|
|
||||||
|
async def fake_save(tenant_key, comparison_id, result):
|
||||||
|
saved[(tenant_key, comparison_id)] = result
|
||||||
|
|
||||||
|
async def fake_load(tenant_key, comparison_id):
|
||||||
|
return saved.get((tenant_key, comparison_id))
|
||||||
|
|
||||||
|
async def fake_receipt(ctx, tenant_key, comparison_id, tool_name, status, inputs, result):
|
||||||
|
receipt = {"persisted": True, "receipt_id": f"receipt-{tool_name}-{comparison_id}", "input_hash": "abc"}
|
||||||
|
receipts.append({"tenant_key": tenant_key, "comparison_id": comparison_id, "tool_name": tool_name, "status": status})
|
||||||
|
return receipt
|
||||||
|
|
||||||
|
async def run():
|
||||||
|
agent = QuoteJudgeStudioV1()
|
||||||
|
ctx = LocalRunContext(auth=PlatformUserAuth(sub="user-1", user_id=1, email="u@example.com"))
|
||||||
|
with patch("agent._save_comparison", fake_save), patch("agent._load_comparison", fake_load), patch("agent._persist_receipt", fake_receipt):
|
||||||
|
success = await agent.invoke("compare_quotes", ctx, comparison_id="studio-quote-v1", quotes=SUCCESS_QUOTES, weights=SUCCESS_WEIGHTS)
|
||||||
|
reload = await agent.invoke("get_comparison", ctx, comparison_id="studio-quote-v1")
|
||||||
|
assert success["ok"] is True
|
||||||
|
assert success["recommendation"]["vendor"] == "Beta Industrial"
|
||||||
|
assert success["receipt"]["persisted"] is True
|
||||||
|
assert reload["ok"] is True
|
||||||
|
assert reload["recommendation"]["vendor"] == "Beta Industrial"
|
||||||
|
assert len(receipts) >= 2
|
||||||
|
|
||||||
|
asyncio.run(run())
|
||||||
|
|||||||
Reference in New Issue
Block a user