Some checks failed
Contributor Attribution Check / check-attribution (pull_request) Failing after 34s
Docker Build and Publish / build-and-push (pull_request) Has been skipped
Supply Chain Audit / Scan PR for supply chain risks (pull_request) Successful in 38s
Tests / e2e (pull_request) Successful in 2m48s
Tests / test (pull_request) Failing after 46m8s
210 lines
6.7 KiB
Python
210 lines
6.7 KiB
Python
"""Tests for agent/agent_card.py — A2A agent card generator (#802)."""
|
|
|
|
import json
|
|
import os
|
|
import tempfile
|
|
import textwrap
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from agent.agent_card import (
|
|
AgentCard,
|
|
AgentCapabilities,
|
|
AgentSkill,
|
|
build_agent_card,
|
|
validate_agent_card,
|
|
_load_skills_from_directory,
|
|
_get_agent_name,
|
|
)
|
|
|
|
|
|
class TestAgentSkill:
|
|
def test_to_dict(self):
|
|
s = AgentSkill(id="code", name="Code", description="Write code", tags=["python"])
|
|
d = s.to_dict()
|
|
assert d["id"] == "code"
|
|
assert d["name"] == "Code"
|
|
assert d["description"] == "Write code"
|
|
assert d["tags"] == ["python"]
|
|
|
|
def test_to_dict_minimal(self):
|
|
s = AgentSkill(id="x", name="X")
|
|
d = s.to_dict()
|
|
assert "description" not in d
|
|
assert "tags" not in d
|
|
|
|
|
|
class TestAgentCapabilities:
|
|
def test_defaults(self):
|
|
caps = AgentCapabilities()
|
|
assert caps.streaming is True
|
|
assert caps.pushNotifications is False
|
|
assert caps.stateTransitionHistory is True
|
|
|
|
def test_to_dict(self):
|
|
d = AgentCapabilities().to_dict()
|
|
assert d == {"streaming": True, "pushNotifications": False, "stateTransitionHistory": True}
|
|
|
|
|
|
class TestAgentCard:
|
|
def test_to_dict(self):
|
|
card = AgentCard(
|
|
name="timmy",
|
|
description="Orchestrator",
|
|
url="http://localhost:8642",
|
|
skills=[AgentSkill(id="code", name="Code")],
|
|
)
|
|
d = card.to_dict()
|
|
assert d["name"] == "timmy"
|
|
assert d["description"] == "Orchestrator"
|
|
assert len(d["skills"]) == 1
|
|
assert d["defaultInputModes"] == ["text/plain", "application/json"]
|
|
|
|
def test_roundtrip(self):
|
|
card = AgentCard(
|
|
name="test-agent",
|
|
url="http://localhost:9999",
|
|
skills=[
|
|
AgentSkill(id="s1", name="Skill 1", tags=["a"]),
|
|
AgentSkill(id="s2", name="Skill 2"),
|
|
],
|
|
metadata={"model": "llama-3"},
|
|
)
|
|
json_str = card.to_json()
|
|
restored = AgentCard.from_json(json_str)
|
|
assert restored.name == "test-agent"
|
|
assert len(restored.skills) == 2
|
|
assert restored.skills[0].id == "s1"
|
|
assert restored.metadata["model"] == "llama-3"
|
|
|
|
def test_from_dict_defaults(self):
|
|
card = AgentCard.from_dict({})
|
|
assert card.name == "hermes-agent"
|
|
assert card.version == "1.0.0"
|
|
assert card.capabilities.streaming is True
|
|
|
|
def test_to_json_is_valid(self):
|
|
card = AgentCard(name="x", url="http://x")
|
|
parsed = json.loads(card.to_json())
|
|
assert parsed["name"] == "x"
|
|
|
|
|
|
class TestValidateAgentCard:
|
|
def test_valid_card(self):
|
|
card = AgentCard(name="agent", url="http://localhost:8080")
|
|
assert validate_agent_card(card) == []
|
|
|
|
def test_missing_name(self):
|
|
card = AgentCard(name="", url="http://x")
|
|
errors = validate_agent_card(card)
|
|
assert any("name" in e for e in errors)
|
|
|
|
def test_missing_url(self):
|
|
card = AgentCard(name="x", url="")
|
|
errors = validate_agent_card(card)
|
|
assert any("url" in e for e in errors)
|
|
|
|
def test_invalid_mime(self):
|
|
card = AgentCard(name="x", url="http://x", defaultInputModes=["plaintext"])
|
|
errors = validate_agent_card(card)
|
|
assert any("MIME" in e for e in errors)
|
|
|
|
def test_skill_missing_id(self):
|
|
card = AgentCard(
|
|
name="x",
|
|
url="http://x",
|
|
skills=[AgentSkill(id="", name="Bad Skill")],
|
|
)
|
|
errors = validate_agent_card(card)
|
|
assert any("skill missing id" in e for e in errors)
|
|
|
|
|
|
class TestLoadSkillsFromDirectory:
|
|
def test_empty_dir(self):
|
|
with tempfile.TemporaryDirectory() as d:
|
|
skills = _load_skills_from_directory(d)
|
|
assert skills == []
|
|
|
|
def test_nonexistent_dir(self):
|
|
skills = _load_skills_from_directory("/nonexistent/path")
|
|
assert skills == []
|
|
|
|
def test_loads_skills(self):
|
|
with tempfile.TemporaryDirectory() as d:
|
|
skill_dir = Path(d) / "my-skill"
|
|
skill_dir.mkdir()
|
|
(skill_dir / "SKILL.md").write_text(textwrap.dedent(""" ---
|
|
name: My Skill
|
|
description: Does amazing things
|
|
tags: [python, awesome]
|
|
---
|
|
|
|
# My Skill
|
|
Instructions here.
|
|
"""))
|
|
skills = _load_skills_from_directory(d)
|
|
assert len(skills) == 1
|
|
assert skills[0].id == "my-skill"
|
|
assert skills[0].name == "My Skill"
|
|
assert skills[0].description == "Does amazing things"
|
|
assert skills[0].tags == ["python", "awesome"]
|
|
|
|
def test_skips_non_skill_dirs(self):
|
|
with tempfile.TemporaryDirectory() as d:
|
|
(Path(d) / "not-a-skill").mkdir()
|
|
skills = _load_skills_from_directory(d)
|
|
assert skills == []
|
|
|
|
def test_skips_files(self):
|
|
with tempfile.TemporaryDirectory() as d:
|
|
(Path(d) / "random-file.txt").write_text("hello")
|
|
skills = _load_skills_from_directory(d)
|
|
assert skills == []
|
|
|
|
|
|
class TestBuildAgentCard:
|
|
def test_with_env_vars(self, monkeypatch):
|
|
monkeypatch.setenv("AGENT_NAME", "test-bot")
|
|
monkeypatch.setenv("AGENT_DESCRIPTION", "A test agent")
|
|
monkeypatch.setenv("AGENT_HOST", "10.0.0.1")
|
|
monkeypatch.setenv("AGENT_PORT", "9090")
|
|
|
|
card = build_agent_card()
|
|
assert card.name == "test-bot"
|
|
assert card.description == "A test agent"
|
|
assert "10.0.0.1" in card.url
|
|
assert "9090" in card.url
|
|
|
|
def test_with_explicit_params(self):
|
|
card = build_agent_card(
|
|
name="explicit-agent",
|
|
description="Custom desc",
|
|
url="http://custom:1234",
|
|
)
|
|
assert card.name == "explicit-agent"
|
|
assert card.url == "http://custom:1234"
|
|
|
|
def test_extra_skills(self):
|
|
card = build_agent_card(
|
|
name="agent",
|
|
extra_skills=[AgentSkill(id="custom", name="Custom Skill")],
|
|
)
|
|
assert any(s.id == "custom" for s in card.skills)
|
|
|
|
def test_card_is_valid(self):
|
|
card = build_agent_card(name="valid-agent")
|
|
errors = validate_agent_card(card)
|
|
assert not errors, f"Card validation failed: {errors}"
|
|
|
|
|
|
class TestGetAgentName:
|
|
def test_from_env(self, monkeypatch):
|
|
monkeypatch.setenv("AGENT_NAME", "from-env")
|
|
assert _get_agent_name() == "from-env"
|
|
|
|
def test_fallback_to_hostname(self, monkeypatch):
|
|
monkeypatch.delenv("AGENT_NAME", raising=False)
|
|
name = _get_agent_name()
|
|
assert len(name) > 0
|