Skip to content

Commit a53d4b8

Browse files
committed
feat(adapters): test_core.py
1 parent 8aec14f commit a53d4b8

1 file changed

Lines changed: 153 additions & 0 deletions

File tree

adapters/python/tests/test_core.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
"""Tests for agentcard_adapters.core — zero framework dependencies."""
2+
3+
import json
4+
import pytest
5+
6+
import sys, pathlib
7+
sys.path.insert(0, str(pathlib.Path(__file__).parents[1] / "src"))
8+
9+
from agentcard_adapters.core import (
10+
AgentCard, Capability, Endpoint, PricingModel,
11+
GoalSubscription, Metadata, LANDAUER_FLOOR_JOULES,
12+
)
13+
14+
VALID_ID = "01HZQK3P8EMXR9V7T5N2W4J6C0"
15+
16+
17+
def minimal_card(**overrides) -> AgentCard:
18+
defaults = dict(
19+
agent_id=VALID_ID,
20+
name="Test Agent",
21+
version="1.0.0",
22+
capabilities=[Capability(id="text.generate", description="Generate text.")],
23+
endpoint=Endpoint(protocol="http", url="https://example.com"),
24+
)
25+
defaults.update(overrides)
26+
return AgentCard(**defaults)
27+
28+
29+
# ── Validation ────────────────────────────────────────────────────────────────
30+
31+
class TestValidation:
32+
def test_minimal_valid(self):
33+
minimal_card().validate()
34+
35+
def test_rejects_short_agent_id(self):
36+
with pytest.raises(ValueError, match="26-character"):
37+
minimal_card(agent_id="SHORT").validate()
38+
39+
def test_rejects_invalid_crockford(self):
40+
# I, L, O, U not in Crockford alphabet
41+
with pytest.raises(ValueError):
42+
minimal_card(agent_id="ILOUILOUILOUILOUILOUILOUI0").validate()
43+
44+
def test_accepts_all_crockford(self):
45+
minimal_card(agent_id="0123456789ABCDEFGHJKMNPQRS").validate()
46+
47+
def test_rejects_empty_name(self):
48+
with pytest.raises(ValueError, match="name"):
49+
minimal_card(name="").validate()
50+
51+
def test_rejects_bad_semver(self):
52+
for bad in ("1.0", "v1.0.0", "1.0.0.0"):
53+
with pytest.raises(ValueError, match="semver"):
54+
minimal_card(version=bad).validate()
55+
56+
def test_accepts_semver_with_pre_and_build(self):
57+
minimal_card(version="1.0.0-rc.1+sha.abc").validate()
58+
59+
def test_rejects_empty_capabilities(self):
60+
with pytest.raises(ValueError, match="capabilities"):
61+
minimal_card(capabilities=[]).validate()
62+
63+
def test_rejects_invalid_capability_id(self):
64+
with pytest.raises(ValueError, match=r"capabilities\[0\]\.id"):
65+
minimal_card(capabilities=[
66+
Capability(id="Has Spaces", description="desc")
67+
]).validate()
68+
69+
def test_rejects_cost_below_landauer(self):
70+
with pytest.raises(ValueError, match="Landauer"):
71+
minimal_card(pricing=PricingModel(base_cost_joules=1e-30)).validate()
72+
73+
def test_accepts_zero_cost(self):
74+
minimal_card(pricing=PricingModel(base_cost_joules=0.0)).validate()
75+
76+
def test_accepts_cost_at_landauer_floor(self):
77+
minimal_card(pricing=PricingModel(
78+
base_cost_joules=LANDAUER_FLOOR_JOULES
79+
)).validate()
80+
81+
def test_rejects_negative_cost(self):
82+
with pytest.raises(ValueError):
83+
minimal_card(pricing=PricingModel(base_cost_joules=-1e-20)).validate()
84+
85+
def test_rejects_invalid_goal_subscription_ulid(self):
86+
with pytest.raises(ValueError, match="ULID"):
87+
minimal_card(goal_subscriptions=[
88+
GoalSubscription(target_agent_id="SHORT", label="test")
89+
]).validate()
90+
91+
def test_rejects_invalid_coupling_scale(self):
92+
with pytest.raises(ValueError, match="coupling"):
93+
minimal_card(goal_subscriptions=[
94+
GoalSubscription(
95+
target_agent_id=VALID_ID, label="test", coupling_scale=0.0
96+
)
97+
]).validate()
98+
99+
def test_accepts_valid_goal_subscription(self):
100+
minimal_card(goal_subscriptions=[
101+
GoalSubscription(target_agent_id=VALID_ID, label="goal", coupling_scale=0.5)
102+
]).validate()
103+
104+
105+
# ── Serialisation ─────────────────────────────────────────────────────────────
106+
107+
class TestSerialisation:
108+
def test_roundtrip_dict(self):
109+
card = minimal_card()
110+
assert AgentCard.from_dict(card.to_dict()) == card
111+
112+
def test_roundtrip_json(self):
113+
card = minimal_card()
114+
assert AgentCard.from_json(card.to_json()) == card
115+
116+
def test_metadata_pacr_prefix(self):
117+
card = minimal_card(metadata=Metadata(
118+
interaction_count=42,
119+
reputation_score=0.9,
120+
trust_tier="verified",
121+
))
122+
d = card.to_dict()
123+
meta = d["metadata"]
124+
assert "pacr:interaction_count" in meta
125+
assert "pacr:reputation_score" in meta
126+
assert "pacr:trust_tier" in meta
127+
assert meta["pacr:interaction_count"] == 42
128+
129+
def test_goal_subscriptions_roundtrip(self):
130+
card = minimal_card(goal_subscriptions=[
131+
GoalSubscription(target_agent_id=VALID_ID, label="goal", coupling_scale=0.7)
132+
])
133+
d = card.to_dict()
134+
assert d["goal_subscriptions"][0]["coupling_scale"] == 0.7
135+
card2 = AgentCard.from_dict(d)
136+
assert card2.goal_subscriptions[0].coupling_scale == 0.7
137+
138+
def test_pricing_omitted_when_none(self):
139+
card = minimal_card()
140+
d = card.to_dict()
141+
assert "pricing" not in d
142+
143+
def test_json_is_valid_json(self):
144+
card = minimal_card()
145+
# Should not raise
146+
json.loads(card.to_json())
147+
148+
def test_landauer_floor_value(self):
149+
# Physics constant must match k_B × 300K × ln(2)
150+
import math
151+
k_b = 1.380_649e-23
152+
expected = k_b * 300 * math.log(2)
153+
assert abs(LANDAUER_FLOOR_JOULES - expected) / expected < 0.01

0 commit comments

Comments
 (0)