Skip to content

Commit f5d6ea8

Browse files
add test for machine controller
1 parent 8dcf3e7 commit f5d6ea8

File tree

1 file changed

+193
-0
lines changed

1 file changed

+193
-0
lines changed

tests/test_machine_controller.py

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
from unittest.mock import ANY, MagicMock, patch
2+
3+
from src.config.config_manager import CONFIG
4+
from src.config.config_types import PumpConfig
5+
from src.machine.controller import MachineController, _build_preparation_data, _PreparationData
6+
from src.models import Ingredient
7+
8+
9+
class TestController:
10+
def test_build_preparation_data(self):
11+
original_pump_config = CONFIG.PUMP_CONFIG.copy()
12+
original_maker_number_bottles = CONFIG.MAKER_NUMBER_BOTTLES
13+
14+
try:
15+
CONFIG.PUMP_CONFIG = [ # type: ignore
16+
PumpConfig(pin=1, volume_flow=10.0, tube_volume=0),
17+
PumpConfig(pin=2, volume_flow=20.0, tube_volume=0),
18+
]
19+
CONFIG.MAKER_NUMBER_BOTTLES = 2
20+
21+
# Create actual Ingredient objects
22+
ingredients = [
23+
Ingredient(
24+
id=1,
25+
name="Test Ing 1",
26+
alcohol=40,
27+
bottle_volume=750,
28+
fill_level=500,
29+
hand=False,
30+
pump_speed=100,
31+
amount=100,
32+
bottle=1,
33+
recipe_order=1,
34+
),
35+
Ingredient(
36+
id=2,
37+
name="Test Ing 2",
38+
alcohol=0,
39+
bottle_volume=750,
40+
fill_level=500,
41+
hand=False,
42+
pump_speed=50,
43+
amount=200,
44+
bottle=2,
45+
recipe_order=2,
46+
),
47+
Ingredient(
48+
id=3,
49+
name="Hand Ing",
50+
alcohol=0,
51+
bottle_volume=750,
52+
fill_level=500,
53+
hand=True,
54+
pump_speed=100,
55+
amount=50,
56+
bottle=None,
57+
recipe_order=1,
58+
),
59+
]
60+
61+
prep_data = _build_preparation_data(ingredients)
62+
63+
# Verify results
64+
assert len(prep_data) == 2
65+
assert prep_data[0].pin == 1
66+
assert prep_data[0].volume_flow == 10.0
67+
assert prep_data[0].flow_time == 10.0 # 100ml / 10ml/s
68+
assert prep_data[0].recipe_order == 1
69+
70+
assert prep_data[1].pin == 2
71+
assert prep_data[1].volume_flow == 10.0 # 20.0 * 0.5 (pump_speed 50%)
72+
assert prep_data[1].flow_time == 20.0 # 200ml / 10ml/s
73+
assert prep_data[1].recipe_order == 2
74+
75+
finally:
76+
# Restore original configuration
77+
CONFIG.PUMP_CONFIG = original_pump_config # type: ignore
78+
CONFIG.MAKER_NUMBER_BOTTLES = original_maker_number_bottles
79+
80+
def test_chunk_preparation_data(self):
81+
# Set original value to restore later
82+
original_simultaneous_pumps = CONFIG.MAKER_SIMULTANEOUSLY_PUMPS
83+
84+
try:
85+
# Set test configuration
86+
CONFIG.MAKER_SIMULTANEOUSLY_PUMPS = 2
87+
88+
# Create test data
89+
prep_data = [
90+
_PreparationData(pin=1, volume_flow=10, flow_time=5, recipe_order=1),
91+
_PreparationData(pin=2, volume_flow=10, flow_time=5, recipe_order=1),
92+
_PreparationData(pin=3, volume_flow=10, flow_time=5, recipe_order=1),
93+
_PreparationData(pin=4, volume_flow=10, flow_time=5, recipe_order=2),
94+
]
95+
96+
mc = MachineController()
97+
chunks = mc._chunk_preparation_data(prep_data)
98+
99+
# Should split first three into two chunks (2+1), and one chunk for order=2
100+
assert len(chunks) == 3
101+
assert [len(chunk) for chunk in chunks] == [2, 1, 1]
102+
assert chunks[0][0].recipe_order == 1
103+
assert chunks[1][0].recipe_order == 1
104+
assert chunks[2][0].recipe_order == 2
105+
106+
finally:
107+
# Restore original configuration
108+
CONFIG.MAKER_SIMULTANEOUSLY_PUMPS = original_simultaneous_pumps
109+
110+
def test_process_preparation_section(self):
111+
# Create test section data
112+
section = [
113+
_PreparationData(pin=1, volume_flow=10, flow_time=5),
114+
_PreparationData(pin=2, volume_flow=20, flow_time=3),
115+
]
116+
117+
mc = MachineController()
118+
# Mock only the internal _stop_pumps method
119+
mc._stop_pumps = MagicMock()
120+
121+
# First call: section_time < all flow_times
122+
mc._process_preparation_section(0, 10, section, section_time=2)
123+
assert section[0].consumption == 20 # 10 ml/s * 2s
124+
assert section[1].consumption == 40 # 20 ml/s * 2s
125+
assert not section[0].closed
126+
assert not section[1].closed
127+
mc._stop_pumps.assert_not_called()
128+
129+
# Second call: section_time > flow_time for pin=2
130+
mc._process_preparation_section(0, 10, section, section_time=4)
131+
assert section[0].consumption == 40 # 10 ml/s * 4s
132+
assert section[1].closed
133+
mc._stop_pumps.assert_called_once_with([2], ANY)
134+
135+
# Third call: section_time > flow_time for pin=1
136+
mc._process_preparation_section(0, 10, section, section_time=6)
137+
assert section[0].closed
138+
assert mc._stop_pumps.call_count == 2
139+
mc._stop_pumps.assert_any_call([1], ANY)
140+
141+
@patch("time.perf_counter")
142+
def test_start_preparation(self, mock_time):
143+
# Mock time to simulate passage of time during preparation
144+
# First call is for cocktail_start_time, then section_start_time, then repeatedly in the while loop
145+
mock_time.side_effect = [
146+
0.0, # cocktail_start_time
147+
0.0, # First section_start_time
148+
1.0, # Time checks during first preparation
149+
1.0, # Second section_start_time
150+
2.0, # Time checks during second preparation
151+
]
152+
153+
# Create test data with two ingredients with different recipe orders
154+
prep_data = [
155+
_PreparationData(pin=1, volume_flow=10, flow_time=1.0, recipe_order=1),
156+
_PreparationData(pin=2, volume_flow=10, flow_time=1.0, recipe_order=2),
157+
]
158+
159+
mc = MachineController()
160+
mc._start_pumps = MagicMock()
161+
mc._stop_pumps = MagicMock()
162+
mc._process_preparation_section = MagicMock()
163+
mc._consumption_print = MagicMock()
164+
165+
# Call the method under test (w is None as requested)
166+
current_time, max_time = mc._start_preparation(None, prep_data, True)
167+
168+
# Verify the method worked correctly
169+
assert max_time == 2.0 # 1.0s + 1.0s for the two ingredients
170+
assert current_time == 2.0 # Last time value from our mock
171+
172+
# Verify _start_pumps was called for both chunks with correct pins
173+
assert mc._start_pumps.call_count == 2
174+
mc._start_pumps.assert_any_call([1], ANY) # First ingredient
175+
mc._start_pumps.assert_any_call([2], ANY) # Second ingredient
176+
177+
# Verify _stop_pumps was called for both chunks with correct pins
178+
assert mc._stop_pumps.call_count == 2
179+
mc._stop_pumps.assert_any_call([1], ANY) # First ingredient
180+
mc._stop_pumps.assert_any_call([2], ANY) # Second ingredient
181+
182+
# Verify _process_preparation_section was called multiple times for each chunk
183+
assert mc._process_preparation_section.call_count >= 2
184+
185+
# Verify calls were in the right sequence (first processing ingredient 1, then ingredient 2)
186+
first_call = mc._process_preparation_section.call_args_list[0]
187+
assert first_call[0][2] == [prep_data[0]] # First call with first ingredient
188+
189+
# Find first call with second ingredient
190+
second_ingredient_calls = [
191+
call for call in mc._process_preparation_section.call_args_list if call[0][2] == [prep_data[1]]
192+
]
193+
assert len(second_ingredient_calls) > 0 # Should have at least one call with second ingredient

0 commit comments

Comments
 (0)