personal memory agent
1# SPDX-License-Identifier: AGPL-3.0-only
2# Copyright (c) 2026 sol pbc
3
4"""Tests for segment orchestration in dream."""
5
6import importlib
7import json
8from pathlib import Path
9
10import pytest
11
12
13@pytest.fixture
14def segment_dir(tmp_path, monkeypatch):
15 """Create a temporary journal with a segment directory."""
16 journal = tmp_path / "journal"
17 day_dir = journal / "20240115"
18 segment_path = day_dir / "default" / "120000_300"
19 segment_path.mkdir(parents=True)
20 (segment_path / "agents").mkdir(parents=True)
21
22 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal))
23 return segment_path
24
25
26def _segment_configs(*names: str) -> dict[str, dict]:
27 configs = {
28 "sense": {
29 "priority": 10,
30 "type": "generate",
31 "output": "json",
32 "schedule": "segment",
33 },
34 "entities": {
35 "priority": 20,
36 "type": "cogitate",
37 "schedule": "segment",
38 },
39 "screen": {
40 "priority": 20,
41 "type": "generate",
42 "output": "md",
43 "schedule": "segment",
44 },
45 "speaker_attribution": {
46 "priority": 20,
47 "type": "cogitate",
48 "schedule": "segment",
49 },
50 "pulse": {
51 "priority": 30,
52 "type": "cogitate",
53 "schedule": "segment",
54 },
55 }
56 return {name: dict(configs[name]) for name in names}
57
58
59def _write_sense_output(segment_dir: Path, sense_json: dict) -> None:
60 (segment_dir / "agents" / "sense.json").write_text(
61 json.dumps(sense_json),
62 encoding="utf-8",
63 )
64
65
66class TestLoadSegmentFacets:
67 """Tests for load_segment_facets helper function."""
68
69 def test_missing_file_returns_empty(self, segment_dir):
70 from think.facets import load_segment_facets
71
72 assert load_segment_facets("20240115", "120000_300") == []
73
74 def test_empty_file_returns_empty(self, segment_dir):
75 from think.facets import load_segment_facets
76
77 (segment_dir / "agents" / "facets.json").write_text("")
78 assert load_segment_facets("20240115", "120000_300") == []
79
80 def test_empty_array_returns_empty(self, segment_dir):
81 from think.facets import load_segment_facets
82
83 (segment_dir / "agents" / "facets.json").write_text("[]")
84 assert load_segment_facets("20240115", "120000_300") == []
85
86 def test_valid_facets_extracted(self, segment_dir):
87 from think.facets import load_segment_facets
88
89 facets_data = [
90 {"facet": "work", "activity": "Code review", "level": "high"},
91 {"facet": "personal", "activity": "Email check", "level": "low"},
92 ]
93 (segment_dir / "agents" / "facets.json").write_text(json.dumps(facets_data))
94
95 assert load_segment_facets("20240115", "120000_300") == ["work", "personal"]
96
97 def test_malformed_json_returns_empty(self, segment_dir, caplog):
98 from think.facets import load_segment_facets
99
100 (segment_dir / "agents" / "facets.json").write_text("{ invalid json")
101 assert load_segment_facets("20240115", "120000_300") == []
102 assert "Failed to parse facets.json" in caplog.text
103
104 def test_non_array_returns_empty(self, segment_dir, caplog):
105 from think.facets import load_segment_facets
106
107 (segment_dir / "agents" / "facets.json").write_text('{"facet": "work"}')
108 assert load_segment_facets("20240115", "120000_300") == []
109 assert "not an array" in caplog.text
110
111 def test_missing_facet_field_skipped(self, segment_dir):
112 from think.facets import load_segment_facets
113
114 facets_data = [
115 {"facet": "work", "activity": "Coding"},
116 {"activity": "Unknown"},
117 {"facet": "personal", "activity": "Email"},
118 ]
119 (segment_dir / "agents" / "facets.json").write_text(json.dumps(facets_data))
120
121 assert load_segment_facets("20240115", "120000_300") == ["work", "personal"]
122
123
124class TestRunSegmentSense:
125 def test_sense_runs_first(self, segment_dir, monkeypatch):
126 from think import dream
127
128 spawned = []
129 _write_sense_output(
130 segment_dir,
131 {"density": "active", "recommend": {}, "facets": []},
132 )
133
134 monkeypatch.setattr(
135 dream,
136 "get_talent_configs",
137 lambda schedule=None, **kwargs: _segment_configs("sense", "entities"),
138 )
139 monkeypatch.setattr(
140 dream,
141 "cortex_request",
142 lambda prompt, name, config=None: spawned.append(name) or f"agent-{name}",
143 )
144 monkeypatch.setattr(
145 dream,
146 "wait_for_agents",
147 lambda agent_ids, timeout=600: ({aid: "finish" for aid in agent_ids}, []),
148 )
149 monkeypatch.setattr(dream, "_callosum", None)
150
151 success, failed, failed_names = dream.run_segment_sense(
152 "20240115",
153 "120000_300",
154 refresh=False,
155 verbose=False,
156 stream="default",
157 )
158
159 assert spawned == ["sense", "entities"]
160 assert success == 2
161 assert failed == 0
162 assert failed_names == []
163
164 def test_idle_segment_returns_early(self, segment_dir, monkeypatch):
165 from think import dream
166
167 spawned = []
168 updates = []
169
170 class StubStateMachine:
171 def update(self, sense_output, segment, day):
172 updates.append((sense_output, segment, day))
173 return []
174
175 def get_current_state(self):
176 return []
177
178 _write_sense_output(
179 segment_dir,
180 {"density": "idle", "recommend": {"screen_record": True}, "facets": []},
181 )
182
183 monkeypatch.setattr(
184 dream,
185 "get_talent_configs",
186 lambda schedule=None, **kwargs: _segment_configs(
187 "sense", "entities", "screen"
188 ),
189 )
190 monkeypatch.setattr(
191 dream,
192 "cortex_request",
193 lambda prompt, name, config=None: spawned.append(name) or f"agent-{name}",
194 )
195 monkeypatch.setattr(
196 dream,
197 "wait_for_agents",
198 lambda agent_ids, timeout=600: ({aid: "finish" for aid in agent_ids}, []),
199 )
200 monkeypatch.setattr(dream, "_callosum", None)
201
202 success, failed, _ = dream.run_segment_sense(
203 "20240115",
204 "120000_300",
205 refresh=False,
206 verbose=False,
207 stream="default",
208 state_machine=StubStateMachine(),
209 )
210
211 assert spawned == ["sense"]
212 assert success == 1
213 assert failed == 0
214 assert updates == [
215 (
216 {"density": "idle", "recommend": {"screen_record": True}, "facets": []},
217 "120000_300",
218 "20240115",
219 )
220 ]
221 density = json.loads((segment_dir / "agents" / "density.json").read_text())
222 assert density["classification"] == "idle"
223
224 # Verify activity state persisted even on idle path
225 activity_state_path = (
226 segment_dir.parent.parent.parent / "awareness" / "activity_state.json"
227 )
228 assert activity_state_path.exists()
229 state_data = json.loads(activity_state_path.read_text())
230 assert state_data == []
231
232 def test_conditional_screen_dispatch(self, segment_dir, monkeypatch):
233 from think import dream
234
235 spawned = []
236 _write_sense_output(
237 segment_dir,
238 {"density": "active", "recommend": {"screen_record": True}, "facets": []},
239 )
240
241 monkeypatch.setattr(
242 dream,
243 "get_talent_configs",
244 lambda schedule=None, **kwargs: _segment_configs(
245 "sense", "entities", "screen"
246 ),
247 )
248 monkeypatch.setattr(
249 dream,
250 "cortex_request",
251 lambda prompt, name, config=None: spawned.append(name) or f"agent-{name}",
252 )
253 monkeypatch.setattr(
254 dream,
255 "wait_for_agents",
256 lambda agent_ids, timeout=600: ({aid: "finish" for aid in agent_ids}, []),
257 )
258 monkeypatch.setattr(dream, "_callosum", None)
259
260 dream.run_segment_sense(
261 "20240115",
262 "120000_300",
263 refresh=False,
264 verbose=False,
265 stream="default",
266 )
267
268 assert spawned == ["sense", "entities", "screen"]
269
270 @pytest.mark.parametrize(
271 ("has_embeddings", "expected"),
272 [
273 (False, ["sense", "entities"]),
274 (True, ["sense", "entities", "speaker_attribution"]),
275 ],
276 )
277 def test_conditional_speaker_attribution(
278 self,
279 segment_dir,
280 monkeypatch,
281 has_embeddings,
282 expected,
283 ):
284 from think import dream
285
286 spawned = []
287 if has_embeddings:
288 (segment_dir / "audio.npz").write_bytes(b"npz")
289
290 _write_sense_output(
291 segment_dir,
292 {
293 "density": "active",
294 "recommend": {"speaker_attribution": True},
295 "facets": [],
296 },
297 )
298
299 monkeypatch.setattr(
300 dream,
301 "get_talent_configs",
302 lambda schedule=None, **kwargs: _segment_configs(
303 "sense",
304 "entities",
305 "speaker_attribution",
306 ),
307 )
308 monkeypatch.setattr(
309 dream,
310 "cortex_request",
311 lambda prompt, name, config=None: spawned.append(name) or f"agent-{name}",
312 )
313 monkeypatch.setattr(
314 dream,
315 "wait_for_agents",
316 lambda agent_ids, timeout=600: ({aid: "finish" for aid in agent_ids}, []),
317 )
318 monkeypatch.setattr(dream, "_callosum", None)
319
320 dream.run_segment_sense(
321 "20240115",
322 "120000_300",
323 refresh=False,
324 verbose=False,
325 stream="default",
326 )
327
328 assert spawned == expected
329
330 def test_refresh_bypasses_idle(self, segment_dir, monkeypatch):
331 from think import dream
332
333 spawned = []
334 _write_sense_output(
335 segment_dir,
336 {"density": "idle", "recommend": {}, "facets": []},
337 )
338
339 monkeypatch.setattr(
340 dream,
341 "get_talent_configs",
342 lambda schedule=None, **kwargs: _segment_configs("sense", "entities"),
343 )
344 monkeypatch.setattr(
345 dream,
346 "cortex_request",
347 lambda prompt, name, config=None: spawned.append(name) or f"agent-{name}",
348 )
349 monkeypatch.setattr(
350 dream,
351 "wait_for_agents",
352 lambda agent_ids, timeout=600: ({aid: "finish" for aid in agent_ids}, []),
353 )
354 monkeypatch.setattr(dream, "_callosum", None)
355
356 success, failed, failed_names = dream.run_segment_sense(
357 "20240115",
358 "120000_300",
359 refresh=True,
360 verbose=False,
361 stream="default",
362 )
363
364 assert spawned == ["sense", "entities"]
365 assert success == 2
366 assert failed == 0
367 assert failed_names == []
368
369 def test_entities_always_runs(self, segment_dir, monkeypatch):
370 from think import dream
371
372 spawned = []
373 _write_sense_output(
374 segment_dir,
375 {"density": "active", "recommend": {"screen_record": False}, "facets": []},
376 )
377
378 monkeypatch.setattr(
379 dream,
380 "get_talent_configs",
381 lambda schedule=None, **kwargs: _segment_configs(
382 "sense", "entities", "screen"
383 ),
384 )
385 monkeypatch.setattr(
386 dream,
387 "cortex_request",
388 lambda prompt, name, config=None: spawned.append(name) or f"agent-{name}",
389 )
390 monkeypatch.setattr(
391 dream,
392 "wait_for_agents",
393 lambda agent_ids, timeout=600: ({aid: "finish" for aid in agent_ids}, []),
394 )
395 monkeypatch.setattr(dream, "_callosum", None)
396
397 dream.run_segment_sense(
398 "20240115",
399 "120000_300",
400 refresh=False,
401 verbose=False,
402 stream="default",
403 )
404
405 assert "entities" in spawned
406 assert "screen" not in spawned
407
408 def test_pulse_dispatch(self, segment_dir, monkeypatch):
409 from think import dream
410
411 spawned = []
412 _write_sense_output(
413 segment_dir,
414 {"density": "active", "recommend": {"pulse_update": True}, "facets": []},
415 )
416
417 monkeypatch.setattr(
418 dream,
419 "get_talent_configs",
420 lambda schedule=None, **kwargs: _segment_configs(
421 "sense", "entities", "pulse"
422 ),
423 )
424 monkeypatch.setattr(
425 dream,
426 "cortex_request",
427 lambda prompt, name, config=None: spawned.append(name) or f"agent-{name}",
428 )
429 monkeypatch.setattr(
430 dream,
431 "wait_for_agents",
432 lambda agent_ids, timeout=600: ({aid: "finish" for aid in agent_ids}, []),
433 )
434 monkeypatch.setattr(dream, "_callosum", None)
435
436 dream.run_segment_sense(
437 "20240115",
438 "120000_300",
439 refresh=False,
440 verbose=False,
441 stream="default",
442 )
443
444 assert spawned == ["sense", "entities", "pulse"]
445
446 def test_sense_failure_stops_orchestrator(self, segment_dir, monkeypatch):
447 from think import dream
448
449 spawned = []
450 _write_sense_output(
451 segment_dir,
452 {"density": "active", "recommend": {}, "facets": []},
453 )
454
455 monkeypatch.setattr(
456 dream,
457 "get_talent_configs",
458 lambda schedule=None, **kwargs: _segment_configs("sense", "entities"),
459 )
460 monkeypatch.setattr(
461 dream,
462 "cortex_request",
463 lambda prompt, name, config=None: spawned.append(name) or f"agent-{name}",
464 )
465
466 def mock_wait_for_agents(agent_ids, timeout=600):
467 return ({agent_ids[0]: "error"}, [])
468
469 monkeypatch.setattr(dream, "wait_for_agents", mock_wait_for_agents)
470 monkeypatch.setattr(dream, "_callosum", None)
471
472 success, failed, failed_names = dream.run_segment_sense(
473 "20240115",
474 "120000_300",
475 refresh=False,
476 verbose=False,
477 stream="default",
478 )
479
480 assert spawned == ["sense"]
481 assert success == 0
482 assert failed == 1
483 assert failed_names == ["sense (error)"]
484
485 def test_activity_state_machine_updated(self, segment_dir, monkeypatch):
486 from think import dream
487
488 updates = []
489 activity_calls = []
490
491 class StubStateMachine:
492 def update(self, sense_output, segment, day):
493 updates.append((sense_output, segment, day))
494 return [{"state": "ended", "id": "coding_120000_300", "_facet": "work"}]
495
496 def get_current_state(self):
497 return [{"facet": "work", "state": "active", "id": "coding_120000_300"}]
498
499 _write_sense_output(
500 segment_dir,
501 {"density": "active", "recommend": {}, "facets": []},
502 )
503
504 monkeypatch.setattr(
505 dream,
506 "get_talent_configs",
507 lambda schedule=None, **kwargs: _segment_configs("sense", "entities"),
508 )
509 monkeypatch.setattr(
510 dream,
511 "cortex_request",
512 lambda prompt, name, config=None: f"agent-{name}",
513 )
514 monkeypatch.setattr(
515 dream,
516 "wait_for_agents",
517 lambda agent_ids, timeout=600: ({aid: "finish" for aid in agent_ids}, []),
518 )
519 monkeypatch.setattr(
520 dream,
521 "run_activity_prompts",
522 lambda **kwargs: activity_calls.append(kwargs) or True,
523 )
524 monkeypatch.setattr(dream, "_callosum", None)
525
526 dream.run_segment_sense(
527 "20240115",
528 "120000_300",
529 refresh=False,
530 verbose=False,
531 stream="default",
532 state_machine=StubStateMachine(),
533 )
534
535 assert updates == [
536 (
537 {"density": "active", "recommend": {}, "facets": []},
538 "120000_300",
539 "20240115",
540 )
541 ]
542 assert activity_calls == [
543 {
544 "day": "20240115",
545 "activity_id": "coding_120000_300",
546 "facet": "work",
547 "refresh": False,
548 "verbose": False,
549 "max_concurrency": 2,
550 }
551 ]
552 activity_state_path = (
553 segment_dir.parent.parent.parent / "awareness" / "activity_state.json"
554 )
555 assert activity_state_path.exists()
556 state_data = json.loads(activity_state_path.read_text())
557 assert state_data == [
558 {"facet": "work", "state": "active", "id": "coding_120000_300"}
559 ]
560
561 def test_generator_triggers_incremental_indexing(self, segment_dir, monkeypatch):
562 from think import dream
563
564 indexer_calls = []
565 _write_sense_output(
566 segment_dir,
567 {"density": "active", "recommend": {}, "facets": []},
568 )
569 (segment_dir / "agents" / "entities.md").write_text(
570 "entities", encoding="utf-8"
571 )
572
573 monkeypatch.setattr(
574 dream,
575 "get_talent_configs",
576 lambda schedule=None, **kwargs: {
577 **_segment_configs("sense"),
578 "entities": {
579 "priority": 20,
580 "type": "generate",
581 "output": "md",
582 "schedule": "segment",
583 },
584 },
585 )
586 monkeypatch.setattr(
587 dream,
588 "cortex_request",
589 lambda prompt, name, config=None: f"agent-{name}",
590 )
591 monkeypatch.setattr(
592 dream,
593 "wait_for_agents",
594 lambda agent_ids, timeout=600: ({aid: "finish" for aid in agent_ids}, []),
595 )
596 monkeypatch.setattr(
597 dream,
598 "run_queued_command",
599 lambda cmd, day, timeout=60: indexer_calls.append(cmd) or True,
600 )
601 monkeypatch.setattr(dream, "_callosum", None)
602
603 dream.run_segment_sense(
604 "20240115",
605 "120000_300",
606 refresh=False,
607 verbose=False,
608 stream="default",
609 )
610
611 assert len(indexer_calls) == 1
612 assert indexer_calls[0][:2] == ["sol", "indexer"]
613 assert "--rescan-file" in indexer_calls[0]
614
615 def test_send_failure_counted(self, segment_dir, monkeypatch):
616 from think import dream
617
618 calls = []
619 _write_sense_output(
620 segment_dir,
621 {"density": "active", "recommend": {}, "facets": []},
622 )
623
624 def mock_cortex_request(prompt, name, config=None):
625 calls.append(name)
626 if name == "sense":
627 return "agent-sense"
628 return None
629
630 monkeypatch.setattr(
631 dream,
632 "get_talent_configs",
633 lambda schedule=None, **kwargs: _segment_configs("sense", "entities"),
634 )
635 monkeypatch.setattr(dream, "cortex_request", mock_cortex_request)
636 monkeypatch.setattr(dream, "_SEND_RETRY_DELAYS", (0.0, 0.0))
637 monkeypatch.setattr(
638 dream,
639 "wait_for_agents",
640 lambda agent_ids, timeout=600: ({aid: "finish" for aid in agent_ids}, []),
641 )
642 monkeypatch.setattr(dream, "_callosum", None)
643
644 success, failed, failed_names = dream.run_segment_sense(
645 "20240115",
646 "120000_300",
647 refresh=False,
648 verbose=False,
649 stream="default",
650 )
651
652 assert calls[0] == "sense"
653 assert calls[1:] == ["entities", "entities", "entities"]
654 assert success == 1
655 assert failed == 1
656 assert failed_names == ["entities (send)"]
657
658
659class TestCortexRequestRetry:
660 """Tests for _cortex_request_with_retry."""
661
662 def test_succeeds_on_first_try(self, monkeypatch):
663 from think import dream
664
665 calls = []
666
667 def mock_cortex_request(**kwargs):
668 calls.append(kwargs)
669 return "agent-1"
670
671 monkeypatch.setattr(dream, "cortex_request", mock_cortex_request)
672
673 result = dream._cortex_request_with_retry(prompt="hi", name="test")
674
675 assert result == "agent-1"
676 assert len(calls) == 1
677
678 def test_succeeds_on_retry(self, monkeypatch):
679 from think import dream
680
681 calls = []
682
683 def mock_cortex_request(**kwargs):
684 calls.append(kwargs)
685 return None if len(calls) <= 1 else "agent-2"
686
687 monkeypatch.setattr(dream, "cortex_request", mock_cortex_request)
688 monkeypatch.setattr(dream, "_SEND_RETRY_DELAYS", (0.0, 0.0))
689
690 result = dream._cortex_request_with_retry(prompt="hi", name="test")
691
692 assert result == "agent-2"
693 assert len(calls) == 2
694
695 def test_returns_none_after_all_retries(self, monkeypatch):
696 from think import dream
697
698 calls = []
699
700 def mock_cortex_request(**kwargs):
701 calls.append(kwargs)
702 return None
703
704 monkeypatch.setattr(dream, "cortex_request", mock_cortex_request)
705 monkeypatch.setattr(dream, "_SEND_RETRY_DELAYS", (0.0, 0.0))
706
707 result = dream._cortex_request_with_retry(prompt="hi", name="test")
708
709 assert result is None
710 assert len(calls) == 3
711
712
713class TestStreamAutoResolution:
714 """Tests for stream resolution in segment mode."""
715
716 def test_auto_resolves_stream_from_filesystem(self, segment_dir, monkeypatch):
717 mod = importlib.import_module("think.dream")
718 calls: list[dict] = []
719
720 class MockCallosumConnection:
721 def __init__(self, *args, **kwargs):
722 pass
723
724 def start(self, callback=None):
725 return None
726
727 def stop(self):
728 return None
729
730 def mock_run_segment_sense(day, segment, refresh, verbose, **kwargs):
731 calls.append(
732 {
733 "day": day,
734 "segment": segment,
735 "refresh": refresh,
736 "verbose": verbose,
737 **kwargs,
738 }
739 )
740 return (1, 0, [])
741
742 monkeypatch.setattr(
743 mod,
744 "iter_segments",
745 lambda day: [("mystream", "120000_300", Path("/tmp/segment"))],
746 )
747 monkeypatch.setattr(mod, "run_segment_sense", mock_run_segment_sense)
748 monkeypatch.setattr(mod, "check_callosum_available", lambda: True)
749 monkeypatch.setattr(mod, "run_command", lambda cmd, day: True)
750 monkeypatch.setattr(
751 mod, "run_queued_command", lambda cmd, day, timeout=600: True
752 )
753 monkeypatch.setattr(mod, "CallosumConnection", MockCallosumConnection)
754 monkeypatch.setattr(
755 "sys.argv",
756 ["sol dream", "--day", "20240115", "--segment", "120000_300"],
757 )
758
759 mod.main()
760
761 assert len(calls) == 1
762 assert calls[0]["stream"] == "mystream"
763
764 def test_segment_not_found_exits(self, segment_dir, monkeypatch):
765 mod = importlib.import_module("think.dream")
766
767 class MockCallosumConnection:
768 def __init__(self, *args, **kwargs):
769 pass
770
771 def start(self, callback=None):
772 return None
773
774 def stop(self):
775 return None
776
777 monkeypatch.setattr(mod, "iter_segments", lambda day: [])
778 monkeypatch.setattr(
779 mod, "run_segment_sense", lambda *args, **kwargs: (1, 0, [])
780 )
781 monkeypatch.setattr(mod, "check_callosum_available", lambda: True)
782 monkeypatch.setattr(mod, "run_command", lambda cmd, day: True)
783 monkeypatch.setattr(mod, "CallosumConnection", MockCallosumConnection)
784 monkeypatch.setattr(
785 "sys.argv",
786 ["sol dream", "--day", "20240115", "--segment", "999999_300"],
787 )
788
789 with pytest.raises(SystemExit) as excinfo:
790 mod.main()
791
792 assert excinfo.value.code != 0
793
794 def test_explicit_stream_skips_filesystem_lookup(self, segment_dir, monkeypatch):
795 mod = importlib.import_module("think.dream")
796 iter_calls = 0
797 calls: list[dict] = []
798
799 class MockCallosumConnection:
800 def __init__(self, *args, **kwargs):
801 pass
802
803 def start(self, callback=None):
804 return None
805
806 def stop(self):
807 return None
808
809 def mock_iter_segments(day):
810 nonlocal iter_calls
811 iter_calls += 1
812 return [("mystream", "120000_300", Path("/tmp/segment"))]
813
814 def mock_run_segment_sense(day, segment, refresh, verbose, **kwargs):
815 calls.append(kwargs)
816 return (1, 0, [])
817
818 monkeypatch.setattr(mod, "iter_segments", mock_iter_segments)
819 monkeypatch.setattr(mod, "run_segment_sense", mock_run_segment_sense)
820 monkeypatch.setattr(mod, "check_callosum_available", lambda: True)
821 monkeypatch.setattr(mod, "run_command", lambda cmd, day: True)
822 monkeypatch.setattr(
823 mod, "run_queued_command", lambda cmd, day, timeout=600: True
824 )
825 monkeypatch.setattr(mod, "CallosumConnection", MockCallosumConnection)
826 monkeypatch.setattr(
827 "sys.argv",
828 [
829 "sol dream",
830 "--day",
831 "20240115",
832 "--segment",
833 "120000_300",
834 "--stream",
835 "explicit_stream",
836 ],
837 )
838
839 mod.main()
840
841 assert iter_calls == 0
842 assert len(calls) == 1
843 assert calls[0]["stream"] == "explicit_stream"