codevally-vision-language-action / tests /test_action_planner.py
SahilCodevally's picture
Init commit
e1c010e
Raw History Blame Contribute Delete
4.31 kB
"""
Tests for the Action Planning Module — ActionPlanner.
"""
import pytest
from src.planner.action_planner import ActionPlanner
class TestActionPlanner:
"""Tests for ActionPlanner.plan() method."""
@pytest.fixture
def planner(self) -> ActionPlanner:
return ActionPlanner()
@pytest.fixture
def sample_detections(self) -> list[dict]:
return [
{"label": "bottle", "confidence": 0.92, "bbox": [100, 150, 200, 350]},
{"label": "box", "confidence": 0.85, "bbox": [400, 300, 550, 500]},
{"label": "cup", "confidence": 0.78, "bbox": [250, 200, 320, 300]},
]
def test_plan_returns_required_keys(self, planner, sample_detections):
"""Plan output must have all required keys."""
interpretation = {
"action": "move",
"source": "bottle",
"destination": "box",
}
result = planner.plan(sample_detections, interpretation)
assert "steps" in result
assert "source_bbox" in result
assert "dest_bbox" in result
assert "status" in result
assert "action" in result
def test_plan_success_status(self, planner, sample_detections):
"""Plan should succeed when both source and destination match."""
interpretation = {
"action": "move",
"source": "bottle",
"destination": "box",
}
result = planner.plan(sample_detections, interpretation)
assert result["status"] == "success"
assert result["source_bbox"] == [100, 150, 200, 350]
assert result["dest_bbox"] == [400, 300, 550, 500]
def test_plan_partial_status(self, planner, sample_detections):
"""Plan should be partial when destination doesn't match."""
interpretation = {
"action": "move",
"source": "bottle",
"destination": "nonexistent_object",
}
result = planner.plan(sample_detections, interpretation)
assert result["status"] == "partial"
assert result["source_bbox"] is not None
assert result["dest_bbox"] is None
def test_plan_failed_status(self, planner, sample_detections):
"""Plan should fail when source doesn't match."""
interpretation = {
"action": "move",
"source": "nonexistent_object",
"destination": "box",
}
result = planner.plan(sample_detections, interpretation)
assert result["status"] == "failed"
def test_plan_generates_steps(self, planner, sample_detections):
"""Plan should generate meaningful steps."""
interpretation = {
"action": "move",
"source": "bottle",
"destination": "box",
}
result = planner.plan(sample_detections, interpretation)
assert len(result["steps"]) >= 3
assert any("bottle" in step.lower() for step in result["steps"])
assert any("box" in step.lower() for step in result["steps"])
def test_plan_no_destination(self, planner, sample_detections):
"""Plan should work when destination is None (e.g. 'inspect')."""
interpretation = {
"action": "inspect",
"source": "cup",
"destination": None,
}
result = planner.plan(sample_detections, interpretation)
assert result["status"] == "success"
assert result["dest_bbox"] is None
def test_plan_empty_detections(self, planner):
"""Plan should fail gracefully with no detections."""
interpretation = {
"action": "move",
"source": "bottle",
"destination": "box",
}
result = planner.plan([], interpretation)
assert result["status"] == "failed"
def test_plan_with_synonym(self, planner, sample_detections):
"""Plan should resolve synonyms via ObjectMapper (mug → cup)."""
interpretation = {
"action": "pick_up",
"source": "mug",
"destination": None,
}
result = planner.plan(sample_detections, interpretation)
# "mug" should map to "cup" via the synonym mapper
assert result["status"] == "success"
assert result["source_bbox"] == [250, 200, 320, 300]