108 lines
4.1 KiB
Python
108 lines
4.1 KiB
Python
import os
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from a2a_pack import PlatformUserAuth
|
|
from a2a_pack.cli.local import load_local_project
|
|
|
|
import agent as agent_module
|
|
from agent import GeneratesSvgVideoGame9jg9vdF0f510
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
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 "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
|
|
assert "generate_svg_asset" in frontend
|
|
assert "Download SVG" in frontend
|
|
assert "unwrapSkillResponse(payload)" in frontend_client
|
|
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 {"generate_svg_asset", "list_recent_receipts"} <= skill_names
|
|
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"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generate_svg_asset_smoke_without_database(monkeypatch):
|
|
monkeypatch.delenv("DATABASE_URL", raising=False)
|
|
auth = PlatformUserAuth(sub="stable-user", user_id=42, email="player@example.test")
|
|
result = await GeneratesSvgVideoGame9jg9vdF0f510().local_invoke(
|
|
"generate_svg_asset",
|
|
auth=auth,
|
|
asset_type="character",
|
|
theme="forest knight",
|
|
palette=["#22c55e", "#14532d", "#facc15"],
|
|
size=96,
|
|
style="pixel",
|
|
variant_count=2,
|
|
)
|
|
assert result.status == "ok"
|
|
assert result.receipt_id.startswith("rcpt_")
|
|
assert result.asset_id.startswith("asset_")
|
|
assert result.receipt_persisted is False
|
|
assert len(result.svg_assets) == 2
|
|
assert result.svg_assets[0]["svg"].startswith("<svg")
|
|
assert "DATABASE_URL" not in result.model_dump_json()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generate_svg_asset_persists_receipt_when_database_configured(monkeypatch):
|
|
calls = []
|
|
|
|
def fake_persist(**kwargs):
|
|
calls.append(kwargs)
|
|
|
|
monkeypatch.setenv("DATABASE_URL", "postgresql://example.invalid/db")
|
|
monkeypatch.setattr(agent_module, "_persist_asset_and_receipt", fake_persist)
|
|
auth = PlatformUserAuth(sub="stable-user", user_id=7)
|
|
result = await GeneratesSvgVideoGame9jg9vdF0f510().local_invoke(
|
|
"generate_svg_asset",
|
|
auth=auth,
|
|
asset_type="item",
|
|
theme="crystal key",
|
|
palette=["#38bdf8", "#1e3a8a"],
|
|
size=128,
|
|
style="neon",
|
|
variant_count=1,
|
|
)
|
|
assert result.status == "ok"
|
|
assert result.receipt_persisted is True
|
|
assert calls
|
|
assert calls[0]["tenant_key"] == "user:7"
|
|
assert calls[0]["receipt_id"] == result.receipt_id
|
|
assert calls[0]["request"]["theme"] == "crystal key"
|
|
|
|
|
|
def test_primary_schema_is_bounded_and_mcp_compatible():
|
|
card = GeneratesSvgVideoGame9jg9vdF0f510().card()
|
|
skill = next(item for item in card.skills if item.name == "generate_svg_asset")
|
|
schema = skill.input_schema
|
|
props = schema["properties"]
|
|
assert set(schema["required"]) >= {"asset_type", "theme"}
|
|
assert props["theme"]["maxLength"] == 80
|
|
assert props["size"]["maximum"] == 512
|
|
assert props["variant_count"]["maximum"] == 4
|
|
assert props["asset_type"]["enum"] == ["character", "enemy", "item", "tile", "icon", "projectile"]
|
|
assert schema["additionalProperties"] is False
|