personal memory agent
at main 843 lines 26 kB view raw
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"