import asyncio from pathlib import Path from a2a_pack import LocalRunContext, PlatformUserAuth from a2a_pack.cli.local import load_local_project ROOT = Path(__file__).resolve().parents[1] FIXTURE = """From: alex@company.example Subject: Concern about team behavior Body: I want to report behavior that feels like harassment and retaliation after I raised a concern. What should I do next? --- From: priya@company.example Subject: Benefits enrollment for dependent Body: I need to add a dependent to my insurance and want to understand eligibility and enrollment timing. --- From: marco@company.example Subject: PTO request next month Body: I would like to take leave from May 6 to May 10. Can People approve this? """ def _agent(): loaded = load_local_project(ROOT).agent_cls() return loaded if hasattr(loaded, "invoke") else loaded() def test_full_stack_product_contract(): manifest = (ROOT / "a2a.yaml").read_text(encoding="utf-8") source = (ROOT / "agent.py").read_text(encoding="utf-8") frontend = (ROOT / "frontend" / "src" / "App.jsx").read_text(encoding="utf-8") frontend_client = (ROOT / "frontend" / "src" / "a2a.js").read_text(encoding="utf-8") assert "frontend:" in manifest and "mount: /app" in manifest assert "public: false" in manifest assert "mailbox: true" in manifest assert "resources:" in manifest and "databases:" in manifest assert "migrations:" in manifest and "db/migrations" in manifest assert "PlatformUserAuth" in source assert "AccountAccess(required=True, platform_skill_calls=3, after_trial=\"byok\")" in source assert "AgentPlatformResources" in source and "AgentDatabase(" in source assert "ctx.write_artifact" in source and "ctx.emit_artifact" in source assert "Skill runner" not in frontend, "replace the generic scaffold with the product workflow" assert "data-testid=\"agent-app\"" in frontend assert "data-testid=\"agent-submit\"" in frontend assert "data-testid=\"agent-result\"" in frontend assert "data-state={resultState}" in frontend assert "data-testid=\"agent-download\"" in frontend assert "unwrapInvokeResponse" in frontend_client, "unwrap the /invoke result envelope before rendering" migrations = list((ROOT / "db" / "migrations").glob("*.sql")) assert migrations and all(path.read_text(encoding="utf-8").strip() for path in migrations) card = _agent().card() assert {skill.name for skill in card.skills} >= { "triage_people_inbox", "mailbox_status", "receive_people_email", } triage = next(skill for skill in card.skills if skill.name == "triage_people_inbox") assert set(triage.input_schema["properties"]) == {"inbox_text", "policy_text", "max_messages"} databases = card.runtime.platform_resources.databases assert databases, "live Agent Card must declare its managed database" assert databases[0].migrations is not None assert databases[0].migrations.path == "db/migrations" assert card.runtime.platform_resources.mailbox is True assert card.runtime.account_access.required is True assert card.runtime.account_access.platform_skill_calls == 3 assert card.runtime.account_access.after_trial == "byok" def test_smoke_triage_returns_structured_result_without_secrets(monkeypatch): monkeypatch.delenv("DATABASE_URL", raising=False) agent = _agent() ctx = LocalRunContext( auth=PlatformUserAuth(sub="user-123", user_id=123, email="person@example.com"), task_id="test-triage", ) result = asyncio.run( agent.invoke( "triage_people_inbox", ctx, inbox_text=FIXTURE, max_messages=3, ) ) assert result.status == "ok" assert len(result.queue) == 3 assert result.queue[0].priority == "urgent" assert result.queue[0].draft.approval_required is True assert result.artifact.name == "people-team-inbox-triage.json" assert result.artifact.media_type == "application/json" assert "api_key" not in result.model_dump_json().lower() assert ctx.artifacts["people-team-inbox-triage.json"] assert result.receipt.receipt_id def test_bad_input_returns_validation_error(monkeypatch): monkeypatch.delenv("DATABASE_URL", raising=False) agent = _agent() ctx = LocalRunContext(auth=PlatformUserAuth(sub="user-123", user_id=123), task_id="test-bad") result = asyncio.run(agent.invoke("triage_people_inbox", ctx, inbox_text="short but long enough words")) assert result.status == "validation_error" assert result.queue == []