File size: 4,313 Bytes
e1c010e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
"""
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]