130 lines
3.7 KiB
Python
130 lines
3.7 KiB
Python
"""Tests for the WorldInterface contract and type system."""
|
|
|
|
import pytest
|
|
|
|
from infrastructure.world.interface import WorldInterface
|
|
from infrastructure.world.types import (
|
|
ActionResult,
|
|
ActionStatus,
|
|
CommandInput,
|
|
PerceptionOutput,
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Type construction
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestPerceptionOutput:
|
|
def test_defaults(self):
|
|
p = PerceptionOutput()
|
|
assert p.location == ""
|
|
assert p.entities == []
|
|
assert p.events == []
|
|
assert p.raw == {}
|
|
assert p.timestamp is not None
|
|
|
|
def test_custom_values(self):
|
|
p = PerceptionOutput(
|
|
location="Balmora",
|
|
entities=["Guard", "Merchant"],
|
|
events=["door_opened"],
|
|
)
|
|
assert p.location == "Balmora"
|
|
assert len(p.entities) == 2
|
|
assert "door_opened" in p.events
|
|
|
|
|
|
class TestCommandInput:
|
|
def test_minimal(self):
|
|
c = CommandInput(action="move")
|
|
assert c.action == "move"
|
|
assert c.target is None
|
|
assert c.parameters == {}
|
|
|
|
def test_with_target_and_params(self):
|
|
c = CommandInput(action="attack", target="Rat", parameters={"weapon": "sword"})
|
|
assert c.target == "Rat"
|
|
assert c.parameters["weapon"] == "sword"
|
|
|
|
|
|
class TestActionResult:
|
|
def test_defaults(self):
|
|
r = ActionResult()
|
|
assert r.status == ActionStatus.SUCCESS
|
|
assert r.message == ""
|
|
|
|
def test_failure(self):
|
|
r = ActionResult(status=ActionStatus.FAILURE, message="blocked")
|
|
assert r.status == ActionStatus.FAILURE
|
|
|
|
|
|
class TestActionStatus:
|
|
def test_values(self):
|
|
assert ActionStatus.SUCCESS.value == "success"
|
|
assert ActionStatus.FAILURE.value == "failure"
|
|
assert ActionStatus.PENDING.value == "pending"
|
|
assert ActionStatus.NOOP.value == "noop"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Abstract contract
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestWorldInterfaceContract:
|
|
"""Verify the ABC cannot be instantiated directly."""
|
|
|
|
def test_cannot_instantiate(self):
|
|
with pytest.raises(TypeError):
|
|
WorldInterface()
|
|
|
|
def test_subclass_must_implement_observe(self):
|
|
class Incomplete(WorldInterface):
|
|
def act(self, command):
|
|
pass
|
|
|
|
def speak(self, message, target=None):
|
|
pass
|
|
|
|
with pytest.raises(TypeError):
|
|
Incomplete()
|
|
|
|
def test_subclass_must_implement_act(self):
|
|
class Incomplete(WorldInterface):
|
|
def observe(self):
|
|
return PerceptionOutput()
|
|
|
|
def speak(self, message, target=None):
|
|
pass
|
|
|
|
with pytest.raises(TypeError):
|
|
Incomplete()
|
|
|
|
def test_subclass_must_implement_speak(self):
|
|
class Incomplete(WorldInterface):
|
|
def observe(self):
|
|
return PerceptionOutput()
|
|
|
|
def act(self, command):
|
|
return ActionResult()
|
|
|
|
with pytest.raises(TypeError):
|
|
Incomplete()
|
|
|
|
def test_complete_subclass_instantiates(self):
|
|
class Complete(WorldInterface):
|
|
def observe(self):
|
|
return PerceptionOutput()
|
|
|
|
def act(self, command):
|
|
return ActionResult()
|
|
|
|
def speak(self, message, target=None):
|
|
pass
|
|
|
|
adapter = Complete()
|
|
assert adapter.is_connected is True # default
|
|
assert isinstance(adapter.observe(), PerceptionOutput)
|
|
assert isinstance(adapter.act(CommandInput(action="test")), ActionResult)
|