Files
hermes-agent/tests/test_agent_card.py
Alexander Whitestone dddbbc9705
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
test: A2A agent card tests (#802)
2026-04-15 16:37:30 +00:00

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