a2a-source-edit: write tests/test_full_stack_contract.py
This commit is contained in:
@@ -1,28 +1,134 @@
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from a2a_pack import LocalRunContext, LocalWorkspaceClient, PlatformUserAuth, WorkspaceMode
|
||||
from a2a_pack.cli.local import load_local_project
|
||||
|
||||
import agent as agent_module
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def _auth():
|
||||
return PlatformUserAuth(sub="test-user", user_id=123, email="tester@example.com")
|
||||
|
||||
|
||||
def _ctx():
|
||||
workspace = LocalWorkspaceClient(
|
||||
{},
|
||||
access=agent_module.TypeDataCsvExcel7rh4pz057e66.workspace_access,
|
||||
bucket="local",
|
||||
issuer="pytest",
|
||||
)
|
||||
workspace.outputs_prefix = "outputs/data-workflows"
|
||||
workspace.write_prefixes = ("outputs/data-workflows/",)
|
||||
workspace.current_mode = WorkspaceMode.READ_WRITE_OVERLAY
|
||||
return LocalRunContext(auth=_auth(), workspace=workspace, task_id="test-task", caller="pytest")
|
||||
|
||||
|
||||
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")
|
||||
migration = (ROOT / "db" / "migrations" / "001_app_records.sql").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"
|
||||
|
||||
migrations = list((ROOT / "db" / "migrations").glob("*.sql"))
|
||||
assert migrations and all(path.read_text(encoding="utf-8").strip() for path in migrations)
|
||||
assert "DATABASE_URL" not in frontend
|
||||
assert "execution_receipts" in migration
|
||||
|
||||
card = load_local_project(ROOT).agent_cls().card()
|
||||
skills = {skill.name: skill for skill in card.skills}
|
||||
assert "create_workflow_from_browser_upload" in skills
|
||||
assert "create_workflow_from_file_upload" in skills
|
||||
assert "list_workflow_receipts" in skills
|
||||
schema = skills["create_workflow_from_browser_upload"].input_schema
|
||||
assert set(schema["required"]) >= {"source_file", "output_schema"}
|
||||
assert "workflow_name" in schema["properties"]
|
||||
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 and databases[0].migrations is not None
|
||||
assert databases[0].migrations.path == "db/migrations"
|
||||
assert card.runtime.pricing.caller_pays_llm is False
|
||||
|
||||
|
||||
def test_browser_upload_workflow_smoke_without_secrets(monkeypatch):
|
||||
monkeypatch.delenv("DATABASE_URL", raising=False)
|
||||
app = agent_module.TypeDataCsvExcel7rh4pz057e66()
|
||||
csv_bytes = b"Name,Age,Email\nAda,37,ada@example.com\n"
|
||||
result = asyncio.run(
|
||||
app.local_invoke(
|
||||
"create_workflow_from_browser_upload",
|
||||
auth=_auth(),
|
||||
workspace=_ctx().workspace,
|
||||
source_file={
|
||||
"filename": "people.csv",
|
||||
"media_type": "text/csv",
|
||||
"data_base64": base64.b64encode(csv_bytes).decode("ascii"),
|
||||
},
|
||||
output_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"email": {"type": "string", "format": "email"},
|
||||
},
|
||||
"required": ["name", "email"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
workflow_name="people-normalizer",
|
||||
)
|
||||
)
|
||||
assert result.status == "storage_warning"
|
||||
assert result.source_format == "csv"
|
||||
assert result.workflow_path.startswith("outputs/data-workflows/")
|
||||
assert result.artifact_uri and not any("KEY" in warning for warning in result.warnings)
|
||||
mapped_targets = {item["target_field"] for item in result.mapped_fields}
|
||||
assert {"name", "age", "email"} <= mapped_targets
|
||||
|
||||
|
||||
def test_invalid_schema_returns_structured_validation_error():
|
||||
app = agent_module.TypeDataCsvExcel7rh4pz057e66()
|
||||
result = asyncio.run(
|
||||
app.local_invoke(
|
||||
"create_workflow_from_browser_upload",
|
||||
auth=_auth(),
|
||||
workspace=_ctx().workspace,
|
||||
source_file={
|
||||
"filename": "data.json",
|
||||
"media_type": "application/json",
|
||||
"data_base64": base64.b64encode(b"[]").decode("ascii"),
|
||||
},
|
||||
output_schema={"type": "array"},
|
||||
workflow_name="bad",
|
||||
)
|
||||
)
|
||||
assert result.status == "validation_error"
|
||||
assert result.persisted is False
|
||||
assert "type='object'" in " ".join(result.warnings)
|
||||
|
||||
|
||||
def test_workflow_spec_has_no_secret_fields():
|
||||
schema = {"type": "object", "properties": {"Name": {"type": "string"}}, "required": ["Name"]}
|
||||
profile = agent_module._profile_source_data("x.csv", "text/csv", b"Name\nAda\n")
|
||||
spec = agent_module._build_workflow_spec(
|
||||
workflow_id="wf_test",
|
||||
workflow_name="test",
|
||||
filename="x.csv",
|
||||
media_type="text/csv",
|
||||
upload_mode="browser_base64",
|
||||
profile=profile,
|
||||
output_schema=schema,
|
||||
receipt_id="rcpt_test",
|
||||
)
|
||||
dumped = json.dumps(spec)
|
||||
assert "DATABASE_URL" not in dumped
|
||||
assert "OPENAI_API_KEY" not in dumped
|
||||
assert spec["receipt"]["receipt_id"] == "rcpt_test"
|
||||
|
||||
Reference in New Issue
Block a user