CallMeDaniel Claude Opus 4.6 (1M context) commited on
Commit
edd81ef
·
1 Parent(s): 7278899

test: add design state, mock orchestrator, and orchestrator tests

Browse files

fix: part name regex in design_state.py — {5,40?} → {5,40}? (lazy quantifier)
- test_design_state.py: 12 tests for DesignState model + extract_decisions
- test_mock_orchestrator.py: 10 tests for MockChatBackend routing + responses
- test_single_call_orchestrator.py: 8 tests with fake LLM backend

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

agents/design_state.py CHANGED
@@ -164,7 +164,7 @@ def extract_decisions(
164
  # Extract part name from user message if not set
165
  if not state.part_name and user_message:
166
  name_patterns = [
167
- r'(?:need|want|design|make|create)\s+(?:a|an)\s+(.{5,40?})\s*(?:with|for|that|,|$)',
168
  ]
169
  for pattern in name_patterns:
170
  match = re.search(pattern, user_message, re.IGNORECASE)
 
164
  # Extract part name from user message if not set
165
  if not state.part_name and user_message:
166
  name_patterns = [
167
+ r'(?:need|want|design|make|create)\s+(?:a|an)\s+(.{5,40}?)\s*(?:with|for|that|,|$)',
168
  ]
169
  for pattern in name_patterns:
170
  match = re.search(pattern, user_message, re.IGNORECASE)
tests/test_design_state.py ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for agents/design_state.py — state tracking and decision extraction."""
2
+
3
+ from agents.design_state import DesignState, extract_decisions
4
+
5
+
6
+ class TestDesignState:
7
+ def test_empty_render(self):
8
+ state = DesignState()
9
+ assert state.render() == ""
10
+
11
+ def test_render_with_fields(self):
12
+ state = DesignState(
13
+ part_name="bracket",
14
+ material="aluminum 6061",
15
+ dimensions={"width": 60.0, "height": 40.0},
16
+ )
17
+ rendered = state.render()
18
+ assert "bracket" in rendered
19
+ assert "aluminum 6061" in rendered
20
+ assert "width=60.0mm" in rendered
21
+
22
+ def test_render_features(self):
23
+ state = DesignState(features=["4x M6 holes", "fillet"])
24
+ rendered = state.render()
25
+ assert "4x M6 holes" in rendered
26
+
27
+ def test_render_decisions_capped_at_5(self):
28
+ state = DesignState(decisions=[f"decision {i}" for i in range(10)])
29
+ rendered = state.render()
30
+ assert "decision 9" in rendered
31
+ assert "decision 4" not in rendered
32
+
33
+
34
+ class TestExtractDecisions:
35
+ def test_extracts_material(self):
36
+ responses = [
37
+ {"agent_id": "engineering", "message": "I recommend aluminum 6061 for this application."}
38
+ ]
39
+ state = extract_decisions(responses, DesignState())
40
+ assert "aluminum" in state.material.lower()
41
+
42
+ def test_extracts_dimensions_from_user(self):
43
+ responses = []
44
+ state = extract_decisions(responses, DesignState(), user_message="Make it 60mm wide and 40mm high")
45
+ assert state.dimensions.get("width") == 60.0
46
+ assert state.dimensions.get("height") == 40.0
47
+
48
+ def test_extracts_fastener_features(self):
49
+ responses = [
50
+ {"agent_id": "engineering", "message": "I'll add 4x M6 clearance holes for mounting."}
51
+ ]
52
+ state = extract_decisions(responses, DesignState())
53
+ assert any("M6" in f for f in state.features)
54
+
55
+ def test_extracts_axis_recommendation(self):
56
+ responses = [
57
+ {"agent_id": "cnc", "message": "This part needs 5-axis machining due to the undercut."}
58
+ ]
59
+ state = extract_decisions(responses, DesignState())
60
+ assert "5-axis" in state.axis_recommendation
61
+
62
+ def test_extracts_part_name(self):
63
+ responses = []
64
+ state = extract_decisions(responses, DesignState(), user_message="I need a servo bracket with M4 holes")
65
+ assert "servo bracket" in state.part_name.lower()
66
+
67
+ def test_preserves_existing_state(self):
68
+ existing = DesignState(material="steel", dimensions={"width": 50.0})
69
+ responses = [
70
+ {"agent_id": "engineering", "message": "Height should be 30mm."}
71
+ ]
72
+ updated = extract_decisions(responses, existing, user_message="add height")
73
+ assert updated.material == "steel"
74
+ assert updated.dimensions.get("width") == 50.0
75
+
76
+ def test_extracts_decisions_from_agreement(self):
77
+ responses = [
78
+ {"agent_id": "design", "message": "I'd recommend an L-bracket form factor for this."}
79
+ ]
80
+ state = extract_decisions(responses, DesignState())
81
+ assert len(state.decisions) > 0
82
+
83
+ def test_no_duplicate_features(self):
84
+ existing = DesignState(features=["4x M6 holes"])
85
+ responses = [
86
+ {"agent_id": "engineering", "message": "The 4x M6 holes are properly specified."}
87
+ ]
88
+ updated = extract_decisions(responses, existing)
89
+ m6_count = sum(1 for f in updated.features if "M6" in f)
90
+ assert m6_count == 1
tests/test_mock_orchestrator.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for agents/orchestrator.py — MockChatBackend and helpers."""
2
+
3
+ from agents.orchestrator import MockChatBackend, _format_response
4
+ from agents.definitions import AGENTS, AGENT_COLORS, AGENT_NAMES, AGENT_AVATARS
5
+
6
+
7
+ class TestFormatResponse:
8
+ def test_returns_all_fields(self):
9
+ resp = _format_response("design", "Hello")
10
+ assert resp["agent_id"] == "design"
11
+ assert resp["agent_name"] == AGENT_NAMES["design"]
12
+ assert resp["message"] == "Hello"
13
+ assert resp["color"] == AGENT_COLORS["design"]
14
+ assert resp["avatar"] == AGENT_AVATARS["design"]
15
+ assert resp["code"] is None
16
+
17
+ def test_includes_code(self):
18
+ resp = _format_response("cad", "Done.", code="result = cq.Workplane().box(10,10,10)")
19
+ assert resp["code"] == "result = cq.Workplane().box(10,10,10)"
20
+
21
+
22
+ class TestMockChatBackend:
23
+ def test_response_shape(self, tmp_output_dir):
24
+ mock = MockChatBackend(output_dir=tmp_output_dir)
25
+ result = mock.chat_turn("I need a bracket", history=[])
26
+ assert "responses" in result
27
+ assert "preview" in result
28
+ assert "design_state" in result
29
+ assert isinstance(result["responses"], list)
30
+ assert len(result["responses"]) > 0
31
+
32
+ def test_bracket_routes_to_design(self, tmp_output_dir):
33
+ mock = MockChatBackend(output_dir=tmp_output_dir)
34
+ result = mock.chat_turn("Design a mounting bracket", history=[])
35
+ agent_ids = [r["agent_id"] for r in result["responses"]]
36
+ assert "design" in agent_ids
37
+
38
+ def test_mention_overrides_routing(self, tmp_output_dir):
39
+ mock = MockChatBackend(output_dir=tmp_output_dir)
40
+ result = mock.chat_turn(
41
+ "What do you think?",
42
+ history=[],
43
+ mentions=["cnc"],
44
+ )
45
+ agent_ids = [r["agent_id"] for r in result["responses"]]
46
+ assert agent_ids == ["cnc"]
47
+
48
+ def test_cad_mention_generates_code(self, tmp_output_dir):
49
+ mock = MockChatBackend(output_dir=tmp_output_dir)
50
+ result = mock.chat_turn(
51
+ "Generate a 50mm cube",
52
+ history=[],
53
+ mentions=["cad"],
54
+ )
55
+ agent_ids = [r["agent_id"] for r in result["responses"]]
56
+ assert "cad" in agent_ids
57
+ cad_resp = next(r for r in result["responses"] if r["agent_id"] == "cad")
58
+ assert cad_resp["code"] is not None
59
+ assert "result" in cad_resp["code"]
60
+
61
+ def test_design_state_updated(self, tmp_output_dir):
62
+ mock = MockChatBackend(output_dir=tmp_output_dir)
63
+ result = mock.chat_turn(
64
+ "Make it 60mm wide in aluminum",
65
+ history=[],
66
+ )
67
+ ds = result["design_state"]
68
+ assert isinstance(ds, dict)
69
+
70
+ def test_engineering_keywords_trigger_engineering(self, tmp_output_dir):
71
+ mock = MockChatBackend(output_dir=tmp_output_dir)
72
+ result = mock.chat_turn("Use M6 bolts with 3mm wall thickness", history=[])
73
+ agent_ids = [r["agent_id"] for r in result["responses"]]
74
+ assert "engineering" in agent_ids
75
+
76
+ def test_cnc_keywords_trigger_cnc(self, tmp_output_dir):
77
+ mock = MockChatBackend(output_dir=tmp_output_dir)
78
+ result = mock.chat_turn("Can this be machined on a CNC mill?", history=[])
79
+ agent_ids = [r["agent_id"] for r in result["responses"]]
80
+ assert "cnc" in agent_ids
81
+
82
+ def test_generic_message_default_agents(self, tmp_output_dir):
83
+ mock = MockChatBackend(output_dir=tmp_output_dir)
84
+ result = mock.chat_turn("Hello there", history=[])
85
+ agent_ids = [r["agent_id"] for r in result["responses"]]
86
+ assert "design" in agent_ids
87
+ assert "engineering" in agent_ids