This commit was merged in pull request #150.
This commit is contained in:
@@ -1,343 +0,0 @@
|
||||
"""Functional tests for agent_core — interface.
|
||||
|
||||
Covers the substrate-agnostic agent contract (data classes, enums,
|
||||
factory methods, abstract enforcement).
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from timmy.agent_core.interface import (
|
||||
Action,
|
||||
ActionType,
|
||||
AgentCapability,
|
||||
AgentEffect,
|
||||
AgentIdentity,
|
||||
Communication,
|
||||
Memory,
|
||||
Perception,
|
||||
PerceptionType,
|
||||
TimAgent,
|
||||
)
|
||||
|
||||
# ── AgentIdentity ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestAgentIdentity:
|
||||
def test_generate_creates_uuid(self):
|
||||
identity = AgentIdentity.generate("Timmy")
|
||||
assert identity.name == "Timmy"
|
||||
uuid.UUID(identity.id) # raises on invalid
|
||||
|
||||
def test_generate_default_version(self):
|
||||
identity = AgentIdentity.generate("Timmy")
|
||||
assert identity.version == "1.0.0"
|
||||
|
||||
def test_generate_custom_version(self):
|
||||
identity = AgentIdentity.generate("Timmy", version="2.0.0")
|
||||
assert identity.version == "2.0.0"
|
||||
|
||||
def test_frozen_identity(self):
|
||||
identity = AgentIdentity.generate("Timmy")
|
||||
with pytest.raises(AttributeError):
|
||||
identity.name = "Other"
|
||||
|
||||
def test_created_at_populated(self):
|
||||
identity = AgentIdentity.generate("Timmy")
|
||||
assert identity.created_at # not empty
|
||||
assert "T" in identity.created_at # ISO format
|
||||
|
||||
def test_two_identities_differ(self):
|
||||
a = AgentIdentity.generate("A")
|
||||
b = AgentIdentity.generate("B")
|
||||
assert a.id != b.id
|
||||
|
||||
|
||||
# ── Perception ────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestPerception:
|
||||
def test_text_factory(self):
|
||||
p = Perception.text("hello")
|
||||
assert p.type == PerceptionType.TEXT
|
||||
assert p.data == "hello"
|
||||
assert p.source == "user"
|
||||
|
||||
def test_text_factory_custom_source(self):
|
||||
p = Perception.text("hello", source="api")
|
||||
assert p.source == "api"
|
||||
|
||||
def test_sensor_factory(self):
|
||||
p = Perception.sensor("temperature", 22.5, "°C")
|
||||
assert p.type == PerceptionType.SENSOR
|
||||
assert p.data["kind"] == "temperature"
|
||||
assert p.data["value"] == 22.5
|
||||
assert p.data["unit"] == "°C"
|
||||
assert p.source == "sensor_temperature"
|
||||
|
||||
def test_timestamp_auto_populated(self):
|
||||
p = Perception.text("hi")
|
||||
assert p.timestamp
|
||||
assert "T" in p.timestamp
|
||||
|
||||
def test_metadata_defaults_empty(self):
|
||||
p = Perception.text("hi")
|
||||
assert p.metadata == {}
|
||||
|
||||
|
||||
# ── Action ────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestAction:
|
||||
def test_respond_factory(self):
|
||||
a = Action.respond("Hello!")
|
||||
assert a.type == ActionType.TEXT
|
||||
assert a.payload == "Hello!"
|
||||
assert a.confidence == 1.0
|
||||
|
||||
def test_respond_with_confidence(self):
|
||||
a = Action.respond("Maybe", confidence=0.5)
|
||||
assert a.confidence == 0.5
|
||||
|
||||
def test_move_factory(self):
|
||||
a = Action.move((1.0, 2.0, 3.0), speed=0.5)
|
||||
assert a.type == ActionType.MOVE
|
||||
assert a.payload["vector"] == (1.0, 2.0, 3.0)
|
||||
assert a.payload["speed"] == 0.5
|
||||
|
||||
def test_move_default_speed(self):
|
||||
a = Action.move((0, 0, 0))
|
||||
assert a.payload["speed"] == 1.0
|
||||
|
||||
def test_deadline_defaults_none(self):
|
||||
a = Action.respond("test")
|
||||
assert a.deadline is None
|
||||
|
||||
|
||||
# ── Memory ────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestMemory:
|
||||
def test_touch_increments(self):
|
||||
m = Memory(id="m1", content="hello", created_at="2025-01-01T00:00:00Z")
|
||||
assert m.access_count == 0
|
||||
m.touch()
|
||||
assert m.access_count == 1
|
||||
m.touch()
|
||||
assert m.access_count == 2
|
||||
|
||||
def test_touch_sets_last_accessed(self):
|
||||
m = Memory(id="m1", content="hello", created_at="2025-01-01T00:00:00Z")
|
||||
assert m.last_accessed is None
|
||||
m.touch()
|
||||
assert m.last_accessed is not None
|
||||
|
||||
def test_default_importance(self):
|
||||
m = Memory(id="m1", content="x", created_at="now")
|
||||
assert m.importance == 0.5
|
||||
|
||||
def test_tags_default_empty(self):
|
||||
m = Memory(id="m1", content="x", created_at="now")
|
||||
assert m.tags == []
|
||||
|
||||
|
||||
# ── Communication ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestCommunication:
|
||||
def test_defaults(self):
|
||||
c = Communication(sender="A", recipient="B", content="hi")
|
||||
assert c.protocol == "direct"
|
||||
assert c.encrypted is False
|
||||
assert c.timestamp # auto-populated
|
||||
|
||||
|
||||
# ── TimAgent abstract enforcement ─────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestTimAgentABC:
|
||||
def test_cannot_instantiate_abstract(self):
|
||||
with pytest.raises(TypeError):
|
||||
TimAgent(AgentIdentity.generate("X"))
|
||||
|
||||
def test_concrete_subclass_works(self):
|
||||
class Dummy(TimAgent):
|
||||
def perceive(self, p):
|
||||
return Memory(id="1", content=p.data, created_at="")
|
||||
|
||||
def reason(self, q, c):
|
||||
return Action.respond(q)
|
||||
|
||||
def act(self, a):
|
||||
return a.payload
|
||||
|
||||
def remember(self, m):
|
||||
pass
|
||||
|
||||
def recall(self, q, limit=5):
|
||||
return []
|
||||
|
||||
def communicate(self, m):
|
||||
return True
|
||||
|
||||
d = Dummy(AgentIdentity.generate("Dummy"))
|
||||
assert d.identity.name == "Dummy"
|
||||
assert d.capabilities == set()
|
||||
|
||||
def test_has_capability(self):
|
||||
class Dummy(TimAgent):
|
||||
def perceive(self, p):
|
||||
pass
|
||||
|
||||
def reason(self, q, c):
|
||||
pass
|
||||
|
||||
def act(self, a):
|
||||
pass
|
||||
|
||||
def remember(self, m):
|
||||
pass
|
||||
|
||||
def recall(self, q, limit=5):
|
||||
return []
|
||||
|
||||
def communicate(self, m):
|
||||
return True
|
||||
|
||||
d = Dummy(AgentIdentity.generate("D"))
|
||||
d._capabilities.add(AgentCapability.REASONING)
|
||||
assert d.has_capability(AgentCapability.REASONING)
|
||||
assert not d.has_capability(AgentCapability.VISION)
|
||||
|
||||
def test_capabilities_returns_copy(self):
|
||||
class Dummy(TimAgent):
|
||||
def perceive(self, p):
|
||||
pass
|
||||
|
||||
def reason(self, q, c):
|
||||
pass
|
||||
|
||||
def act(self, a):
|
||||
pass
|
||||
|
||||
def remember(self, m):
|
||||
pass
|
||||
|
||||
def recall(self, q, limit=5):
|
||||
return []
|
||||
|
||||
def communicate(self, m):
|
||||
return True
|
||||
|
||||
d = Dummy(AgentIdentity.generate("D"))
|
||||
caps = d.capabilities
|
||||
caps.add(AgentCapability.VISION)
|
||||
assert AgentCapability.VISION not in d.capabilities
|
||||
|
||||
def test_get_state(self):
|
||||
class Dummy(TimAgent):
|
||||
def perceive(self, p):
|
||||
pass
|
||||
|
||||
def reason(self, q, c):
|
||||
pass
|
||||
|
||||
def act(self, a):
|
||||
pass
|
||||
|
||||
def remember(self, m):
|
||||
pass
|
||||
|
||||
def recall(self, q, limit=5):
|
||||
return []
|
||||
|
||||
def communicate(self, m):
|
||||
return True
|
||||
|
||||
d = Dummy(AgentIdentity.generate("D"))
|
||||
state = d.get_state()
|
||||
assert "identity" in state
|
||||
assert "capabilities" in state
|
||||
assert "state" in state
|
||||
|
||||
def test_shutdown_does_not_raise(self):
|
||||
class Dummy(TimAgent):
|
||||
def perceive(self, p):
|
||||
pass
|
||||
|
||||
def reason(self, q, c):
|
||||
pass
|
||||
|
||||
def act(self, a):
|
||||
pass
|
||||
|
||||
def remember(self, m):
|
||||
pass
|
||||
|
||||
def recall(self, q, limit=5):
|
||||
return []
|
||||
|
||||
def communicate(self, m):
|
||||
return True
|
||||
|
||||
d = Dummy(AgentIdentity.generate("D"))
|
||||
d.shutdown() # should not raise
|
||||
|
||||
|
||||
# ── AgentEffect ───────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestAgentEffect:
|
||||
def test_empty_export(self):
|
||||
effect = AgentEffect()
|
||||
assert effect.export() == []
|
||||
|
||||
def test_log_perceive(self):
|
||||
effect = AgentEffect()
|
||||
p = Perception.text("test input")
|
||||
effect.log_perceive(p, "mem_0")
|
||||
log = effect.export()
|
||||
assert len(log) == 1
|
||||
assert log[0]["type"] == "perceive"
|
||||
assert log[0]["perception_type"] == "TEXT"
|
||||
assert log[0]["memory_id"] == "mem_0"
|
||||
assert "timestamp" in log[0]
|
||||
|
||||
def test_log_reason(self):
|
||||
effect = AgentEffect()
|
||||
effect.log_reason("How to help?", ActionType.TEXT)
|
||||
log = effect.export()
|
||||
assert len(log) == 1
|
||||
assert log[0]["type"] == "reason"
|
||||
assert log[0]["query"] == "How to help?"
|
||||
assert log[0]["action_type"] == "TEXT"
|
||||
|
||||
def test_log_act(self):
|
||||
effect = AgentEffect()
|
||||
action = Action.respond("Hello!")
|
||||
effect.log_act(action, "Hello!")
|
||||
log = effect.export()
|
||||
assert len(log) == 1
|
||||
assert log[0]["type"] == "act"
|
||||
assert log[0]["confidence"] == 1.0
|
||||
assert log[0]["result_type"] == "str"
|
||||
|
||||
def test_export_returns_copy(self):
|
||||
effect = AgentEffect()
|
||||
effect.log_reason("q", ActionType.TEXT)
|
||||
exported = effect.export()
|
||||
exported.clear()
|
||||
assert len(effect.export()) == 1
|
||||
|
||||
def test_full_audit_trail(self):
|
||||
effect = AgentEffect()
|
||||
p = Perception.text("input")
|
||||
effect.log_perceive(p, "m0")
|
||||
effect.log_reason("what now?", ActionType.TEXT)
|
||||
action = Action.respond("response")
|
||||
effect.log_act(action, "response")
|
||||
log = effect.export()
|
||||
assert len(log) == 3
|
||||
types = [e["type"] for e in log]
|
||||
assert types == ["perceive", "reason", "act"]
|
||||
Reference in New Issue
Block a user