feat(system): test(seedgo): improve line coverage — architecture_check + proof standards + diagnostics (203 new tests)

Co-Authored-By: @devpulse <devpulse@aipass>
This commit is contained in:
AIOSAI
2026-04-26 02:14:43 -07:00
co-authored by @devpulse
parent 210c381352
commit bc52ad85ea
10 changed files with 11144 additions and 63 deletions
+30
View File
@@ -446,6 +446,36 @@
"file": "tests/test_monitoring_handlers.py",
"standard": "architecture",
"reason": "Test file lives in tests/ by convention, not in the 3-layer apps/ structure. This is standard for all prax test files."
},
{
"file": "tests/test_log_watcher.py",
"standard": "architecture",
"reason": "Test file lives in tests/ by convention, not in the 3-layer apps/ structure. This is standard for all prax test files."
},
{
"file": "tests/test_log_watcher.py",
"standard": "documentation",
"reason": "Pre-existing test functions follow pytest naming conventions (test_*). Original 35 functions were written without docstrings per project test style."
},
{
"file": "tests/test_log_watcher.py",
"standard": "naming",
"reason": "Pre-existing _RealFSHandler is a private test stub with underscore prefix indicating internal use. Renaming would not improve clarity."
},
{
"file": "tests/test_monitor_module.py",
"standard": "architecture",
"reason": "Test file lives in tests/ by convention, not in the 3-layer apps/ structure. This is standard for all prax test files."
},
{
"file": "tests/test_operations.py",
"standard": "architecture",
"reason": "Test file lives in tests/ by convention, not in the 3-layer apps/ structure. This is standard for all prax test files."
},
{
"file": "tests/test_operations.py",
"standard": "encapsulation",
"reason": "Unit tests must import handlers directly to test them in isolation. _load_ops() imports operations handler for unit testing. Same pattern as test_logging_handlers.py."
}
],
"notes": {
File diff suppressed because it is too large Load Diff
+597
View File
@@ -459,3 +459,600 @@ class TestInitializePositions:
assert str(log_file) in watcher.log_positions
assert str(txt_file) not in watcher.log_positions
# ============================================================================
# ADDITIONAL COVERAGE TESTS
# ============================================================================
class TestGenerateErrorHash:
"""Test _generate_error_hash deduplication helper."""
def test_returns_8_char_hex(self):
"""Hash should be an 8-character hexadecimal string."""
mod = _import_log_watcher()
result = mod._generate_error_hash("mymodule", "something broke")
assert len(result) == 8
assert all(c in "0123456789abcdef" for c in result)
def test_same_input_same_hash(self):
"""Identical module+message should produce identical hashes."""
mod = _import_log_watcher()
h1 = mod._generate_error_hash("mod", "error msg")
h2 = mod._generate_error_hash("mod", "error msg")
assert h1 == h2
def test_different_input_different_hash(self):
"""Different inputs should produce different hashes."""
mod = _import_log_watcher()
h1 = mod._generate_error_hash("mod_a", "error one")
h2 = mod._generate_error_hash("mod_b", "error two")
assert h1 != h2
class TestTriggerImportFallback:
"""Test trigger import fallback when trigger module is unavailable."""
def test_has_trigger_flag_set(self):
"""HAS_TRIGGER should be set based on trigger import availability."""
mod = _import_log_watcher()
# With our mock setup, trigger is available
assert hasattr(mod, "HAS_TRIGGER")
class TestProcessLogLine:
"""Test _process_log_line dispatching."""
def test_empty_line_skipped(self):
"""Empty/whitespace lines should be skipped entirely."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
watcher._process_log_line("PRAX", " ", "/fake/file.log")
mock_queue.enqueue.assert_not_called()
def test_command_line_emits_separator(self):
"""Command lines should emit a command separator, not a log event."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
with (
patch.object(
watcher,
"_extract_command_info",
return_value={"command": "test cmd", "caller": None, "target": None},
),
patch.object(watcher, "_emit_command_separator") as mock_sep,
patch.object(watcher, "_emit_log_event") as mock_log,
):
watcher._process_log_line("PRAX", "some command line", "/f.log")
mock_sep.assert_called_once()
mock_log.assert_not_called()
def test_regular_line_emits_log_event(self):
"""Non-command lines should emit a log event."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
with (
patch.object(watcher, "_extract_command_info", return_value=None),
patch.object(watcher, "_emit_log_event") as mock_log,
):
watcher._process_log_line("PRAX", "normal log line", "/f.log")
mock_log.assert_called_once()
class TestReadNewContent:
"""Test _read_new_content file tailing."""
def test_reads_new_content(self, tmp_path):
"""Should read content added after last known position."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
log_file = tmp_path / "test.log"
log_file.write_text("old line\n", encoding="utf-8")
old_size = log_file.stat().st_size
watcher.log_positions[str(log_file)] = old_size
# Append new content
with log_file.open("a", encoding="utf-8") as f:
f.write("new line\n")
result = watcher._read_new_content(str(log_file))
assert result is not None
assert "new line" in result
def test_returns_none_when_no_new_content(self, tmp_path):
"""Should return None when file hasn't grown."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
log_file = tmp_path / "test.log"
log_file.write_text("existing\n", encoding="utf-8")
watcher.log_positions[str(log_file)] = log_file.stat().st_size
result = watcher._read_new_content(str(log_file))
assert result is None
def test_resets_position_on_truncation(self, tmp_path):
"""Should reset position to 0 when file is smaller than last pos."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
log_file = tmp_path / "test.log"
log_file.write_text("short\n", encoding="utf-8")
watcher.log_positions[str(log_file)] = 99999 # Way past end
result = watcher._read_new_content(str(log_file))
assert result is not None
assert "short" in result
def test_returns_none_for_whitespace_only_content(self, tmp_path):
"""Should return None when new content is only whitespace."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
log_file = tmp_path / "test.log"
log_file.write_text("initial\n", encoding="utf-8")
old_size = log_file.stat().st_size
watcher.log_positions[str(log_file)] = old_size
# Append only whitespace
with log_file.open("a", encoding="utf-8") as f:
f.write(" \n \n")
result = watcher._read_new_content(str(log_file))
assert result is None
class TestOnModified:
"""Test on_modified event handler."""
def test_ignores_directory_events(self):
"""Should ignore directory modification events."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
event = MagicMock()
event.is_directory = True
watcher.on_modified(event)
mock_queue.enqueue.assert_not_called()
def test_ignores_non_log_files(self):
"""Should ignore files that don't end with .log."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
event = MagicMock()
event.is_directory = False
event.src_path = "/fake/logs/system/test.txt"
watcher.on_modified(event)
mock_queue.enqueue.assert_not_called()
def test_ignores_logs_outside_system_dir(self):
"""Should ignore log files outside the system logs directory."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
event = MagicMock()
event.is_directory = False
event.src_path = "/other/dir/test.log"
with patch.object(mod, "get_system_logs_dir", return_value=Path("/fake/logs/system")):
watcher.on_modified(event)
mock_queue.enqueue.assert_not_called()
def test_processes_valid_log_file(self, tmp_path):
"""Should process a valid log file in the system logs directory."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
log_file = tmp_path / "prax.log"
log_file.write_text("line one\n", encoding="utf-8")
watcher.log_positions[str(log_file)] = 0
event = MagicMock()
event.is_directory = False
event.src_path = str(log_file)
with (
patch.object(mod, "get_system_logs_dir", return_value=tmp_path),
patch.object(mod, "detect_branch_from_log", return_value="PRAX"),
patch.object(watcher, "_process_log_line") as mock_process,
):
watcher.on_modified(event)
mock_process.assert_called()
def test_handles_read_exception(self):
"""Should catch exceptions during log reading."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
event = MagicMock()
event.is_directory = False
event.src_path = "/fake/logs/system/crash.log"
with (
patch.object(mod, "get_system_logs_dir", return_value=Path("/fake/logs/system")),
patch.object(watcher, "_read_new_content", side_effect=OSError("disk error")),
):
# Should not raise
watcher.on_modified(event)
mock_queue.enqueue.assert_not_called()
def test_skips_when_no_new_content(self):
"""Should skip processing when _read_new_content returns None."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
event = MagicMock()
event.is_directory = False
event.src_path = "/fake/logs/system/empty.log"
with (
patch.object(mod, "get_system_logs_dir", return_value=Path("/fake/logs/system")),
patch.object(watcher, "_read_new_content", return_value=None),
):
watcher.on_modified(event)
mock_queue.enqueue.assert_not_called()
class TestShouldDisplayLog:
"""Test _should_display_log filter."""
def test_always_returns_true(self):
"""Current implementation shows all log lines."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
assert watcher._should_display_log("any line") is True
assert watcher._should_display_log("") is True
class TestMatchFlowCommand:
"""Test _match_flow_command pattern matching."""
def test_loaded_module_command(self):
"""Should detect flow command from 'Loaded module' line."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
# Clear any prior flow commands to avoid dedup
watcher.last_command_per_branch.clear()
result = watcher._match_flow_command("Loaded module: flow_planner")
assert result is not None
assert result["command"] == "flow command"
def test_loaded_module_suppressed_when_duplicate(self):
"""Should suppress duplicate 'Loaded module' commands."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
watcher.last_command_per_branch["FLOW"] = "FLOW:flow command"
result = watcher._match_flow_command("Loaded module: flow_planner")
assert result is None
def test_non_flow_line_returns_none(self):
"""Should return None for unrecognized flow lines."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
result = watcher._match_flow_command("Just a normal log line")
assert result is None
class TestExtractCommandInfoAdditional:
"""Additional command extraction tests for uncovered patterns."""
def test_seedgo_checklist_running(self):
"""Should detect seedgo checklist commands."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
line = "standards_checklist Running full standard check on prax"
result = watcher._extract_command_info(line)
assert result is not None
assert "seedgo checklist" in result["command"]
def test_backup_versioned(self):
"""Should detect backup versioned commands."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
line = "[backup] Starting versioned backup"
result = watcher._extract_command_info(line)
assert result is not None
assert "backup versioned" in result["command"]
def test_backup_sync(self):
"""Should detect backup sync commands."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
line = "[backup] Running sync operation"
result = watcher._extract_command_info(line)
assert result is not None
assert "backup sync" in result["command"]
def test_memory_search(self):
"""Should detect memory search commands."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
line = "[memory] Handling search query for branch status"
result = watcher._extract_command_info(line)
assert result is not None
assert "memory search" in result["command"]
def test_trigger_triggered(self):
"""Should detect trigger events with 'triggered' keyword."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
line = "[trigger] Rule triggered: error_threshold"
result = watcher._extract_command_info(line)
assert result is not None
assert "trigger fire" in result["command"]
def test_ai_mail_send_without_target(self):
"""Should handle ai_mail send without a parseable recipient."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
line = "[ai_mail] Sending broadcast message"
result = watcher._extract_command_info(line)
assert result is not None
assert "ai_mail send" in result["command"]
def test_drone_started_without_bracket_prefix(self):
"""Should detect 'Drone started with args' without [drone] prefix."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
line = "Drone started with args: ['audit', '@prax']"
result = watcher._extract_command_info(line)
assert result is not None
assert "drone" in result["command"]
assert "audit" in result["command"]
class TestMatchExecutingCommand:
"""Test _match_executing_command pattern matching."""
def test_returns_none_when_no_cmd_match(self):
"""Should return None when command pattern doesn't match."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
result = watcher._match_executing_command("Executing something weird")
assert result is None
def test_extracts_caller_and_simplifies_path(self):
"""Should extract caller and simplify aipass paths to @branch."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
line = "Executing command [CALLER:DRONE]: /path/to/aipass/seedgo/apps/audit.py @prax"
result = watcher._match_executing_command(line)
assert result is not None
assert result["caller"] == "DRONE"
assert result["target"] == "PRAX"
def test_without_caller(self):
"""Should work without CALLER tag."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
line = "Executing command: /path/to/aipass/flow run"
result = watcher._match_executing_command(line)
assert result is not None
assert result["caller"] is None
class TestExtractTargetFromCmd:
"""Test _extract_target_from_cmd static method."""
def test_extracts_at_target(self):
"""Should extract target from @branch pattern."""
mod = _import_log_watcher()
result = mod.LogFileWatcher._extract_target_from_cmd("audit @prax")
assert result == "PRAX"
def test_extracts_path_target(self):
"""Should extract target from /aipass/branch pattern."""
mod = _import_log_watcher()
result = mod.LogFileWatcher._extract_target_from_cmd("/path/to/aipass/seedgo/run.py")
assert result == "SEEDGO"
def test_returns_none_when_no_target(self):
"""Should return None when no target pattern matches."""
mod = _import_log_watcher()
result = mod.LogFileWatcher._extract_target_from_cmd("plain command")
assert result is None
class TestEmitCommandSeparator:
"""Test _emit_command_separator event emission."""
def test_dict_format(self):
"""Should handle dict command_info format."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
watcher.last_command_per_branch.clear()
watcher._emit_command_separator("PRAX", {"command": "test cmd", "caller": "DRONE", "target": "FLOW"})
mock_queue.enqueue.assert_called_once()
def test_tuple_format(self):
"""Should handle legacy tuple (command, caller) format."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
watcher.last_command_per_branch.clear()
watcher._emit_command_separator("PRAX", ("test cmd", "DRONE"))
mock_queue.enqueue.assert_called_once()
def test_string_format(self):
"""Should handle plain string command format."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
watcher.last_command_per_branch.clear()
watcher._emit_command_separator("PRAX", "test cmd")
mock_queue.enqueue.assert_called_once()
def test_deduplication(self):
"""Should skip duplicate consecutive commands for same branch."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
watcher.last_command_per_branch.clear()
cmd_info = {"command": "same cmd", "caller": None, "target": None}
watcher._emit_command_separator("PRAX", cmd_info)
watcher._emit_command_separator("PRAX", cmd_info)
assert mock_queue.enqueue.call_count == 1
def test_target_stored_in_action(self):
"""Should store target in action field when target is provided."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
watcher.last_command_per_branch.clear()
watcher._emit_command_separator("PRAX", {"command": "cmd", "caller": None, "target": "FLOW"})
enqueued_event = mock_queue.enqueue.call_args[0][0]
assert "FLOW" in enqueued_event.action
class TestEmitLogEvent:
"""Test _emit_log_event event emission."""
def test_emits_log_event(self):
"""Should create and enqueue a log monitoring event."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
watcher._emit_log_event("PRAX", "test message", "info")
mock_queue.enqueue.assert_called_once()
def test_error_level_fires_trigger(self):
"""Should fire trigger event for ERROR level logs."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
# Set up trigger mock
mock_trigger = MagicMock()
setattr(mod, "HAS_TRIGGER", True)
setattr(mod, "trigger", mock_trigger)
watcher._emit_log_event(
"PRAX",
"[PRAX] 2025-01-01 | mymodule | ERROR | something broke",
"error",
"/fake/prax.log",
)
mock_trigger.fire.assert_called_once()
call_kwargs = mock_trigger.fire.call_args
assert call_kwargs[0][0] == "error_detected"
assert call_kwargs[1]["branch"] == "PRAX"
def test_info_level_does_not_fire_trigger(self):
"""Should not fire trigger for non-error levels."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
mock_trigger = MagicMock()
setattr(mod, "HAS_TRIGGER", True)
setattr(mod, "trigger", mock_trigger)
watcher._emit_log_event("PRAX", "normal info", "info")
mock_trigger.fire.assert_not_called()
def test_trigger_not_fired_when_unavailable(self):
"""Should skip trigger when HAS_TRIGGER is False."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
setattr(mod, "HAS_TRIGGER", False)
setattr(mod, "trigger", None)
# Should not raise
watcher._emit_log_event("PRAX", "error msg", "error")
mock_queue.enqueue.assert_called_once()
def test_error_with_no_log_file_path(self):
"""Should use 'unknown' for log_file when path not provided."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
mock_trigger = MagicMock()
setattr(mod, "HAS_TRIGGER", True)
setattr(mod, "trigger", mock_trigger)
watcher._emit_log_event("PRAX", "error msg", "error")
call_kwargs = mock_trigger.fire.call_args[1]
assert call_kwargs["log_file"] == "unknown"
def test_error_extracts_module_name_from_pipe_format(self):
"""Should extract module name from pipe-delimited log lines."""
mod = _import_log_watcher()
watcher, mock_queue = _make_watcher(mod)
mock_trigger = MagicMock()
setattr(mod, "HAS_TRIGGER", True)
setattr(mod, "trigger", mock_trigger)
watcher._emit_log_event(
"PRAX",
"[PRAX] 2025-01-01 | mymod.handler | ERROR | crash",
"error",
"/fake/log.log",
)
call_kwargs = mock_trigger.fire.call_args[1]
assert call_kwargs["module_name"] == "mymod.handler"
class TestStartLogWatcherAdditional:
"""Additional tests for start_log_watcher."""
def test_stops_existing_observer_before_starting(self):
"""Should stop existing observer if already running."""
mod = _import_log_watcher()
mock_queue = MagicMock()
mock_old_observer = MagicMock()
mock_old_observer.is_alive.return_value = True
setattr(mod, "_log_observer", mock_old_observer)
mock_new_observer = MagicMock()
with (
patch.object(mod, "WatchdogObserver", return_value=mock_new_observer),
patch.object(mod, "get_system_logs_dir", return_value=Path("/fake/logs")),
patch.object(mod, "stop_log_watcher") as mock_stop,
):
mod.start_log_watcher(mock_queue)
mock_stop.assert_called_once()
class TestInitializePositionsAdditional:
"""Additional tests for initialize_positions."""
def test_handles_stat_exception(self, tmp_path):
"""Should handle exceptions when getting file size."""
mod = _import_log_watcher()
watcher, _ = _make_watcher(mod)
log_file = tmp_path / "bad.log"
log_file.write_text("content\n", encoding="utf-8")
with (
patch.object(mod, "get_system_logs_dir", return_value=tmp_path),
patch.object(Path, "stat", side_effect=OSError("stat failed")),
):
# Should not raise
watcher.initialize_positions()
assert str(log_file) not in watcher.log_positions
File diff suppressed because it is too large Load Diff
@@ -984,3 +984,986 @@ class TestPrintStatus:
# Should not raise
mod.print_status(["PRAX"], verbosity=0, filters=None)
assert mod.console.print.called
# =============================================
# BRANCH DETECTOR — ADDITIONAL COVERAGE TESTS
# =============================================
# Fake home base used across external-project tests to avoid
# hardcoded /home/ paths that trip the SEEDGO log-structure checker.
_FAKE_HOME = Path("/fakehome/user")
_FAKE_PROJECTS = _FAKE_HOME / "Projects"
class TestFindRepoRoot:
"""Tests for BranchDetector._find_repo_root()."""
def test_cached_repo_root_returned(self):
"""When _repo_root is already set, return it without walking."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
sentinel = Path("/cached/root")
detector._repo_root = sentinel
result = detector._find_repo_root()
assert result is sentinel
def test_walks_parents_to_find_registry(self):
"""Should walk up from __file__ to find AIPASS_REGISTRY.json."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector._repo_root = None
mock_parent = MagicMock(spec=Path)
mock_registry = MagicMock(spec=Path)
mock_registry.exists.return_value = True
mock_parent.__truediv__ = MagicMock(return_value=mock_registry)
mock_resolved = MagicMock()
mock_resolved.parent = mock_parent
mock_resolved.parents = []
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path_cls.return_value.resolve.return_value = mock_resolved
result = detector._find_repo_root()
assert result is mock_parent
def test_fallback_to_cwd_when_registry_missing(self):
"""Should fall back to Path.cwd() when no AIPASS_REGISTRY.json found."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector._repo_root = None
mock_parent = MagicMock(spec=Path)
mock_no_registry = MagicMock(spec=Path)
mock_no_registry.exists.return_value = False
mock_parent.__truediv__ = MagicMock(return_value=mock_no_registry)
mock_parent.parents = []
mock_resolved = MagicMock()
mock_resolved.parent = mock_parent
mock_resolved.parents = []
fake_cwd = Path("/fake/cwd")
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path_cls.return_value.resolve.return_value = mock_resolved
mock_path_cls.cwd.return_value = fake_cwd
result = detector._find_repo_root()
assert result is fake_cwd
class TestRegisterBranch:
"""Tests for BranchDetector._register_branch()."""
def test_registers_branch_with_name_and_path(self):
"""Should add to branch_map and known_branches."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod, branches={})
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_resolved = MagicMock(spec=Path)
mock_resolved.__str__ = MagicMock(return_value="/resolved/path")
mock_path_cls.return_value.resolve.return_value = mock_resolved
detector._register_branch({"name": "DRONE", "path": "/some/drone"})
assert "DRONE" in detector.known_branches
assert "/resolved/path" in detector.branch_map
assert "/resolved/path/" in detector.branch_map
def test_skips_empty_name(self):
"""Should skip branch entries with empty name."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod, branches={})
initial_count = len(detector.known_branches)
detector._register_branch({"name": "", "path": "/some/path"})
assert len(detector.known_branches) == initial_count
def test_skips_empty_path(self):
"""Should skip branch entries with empty path."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod, branches={})
initial_count = len(detector.known_branches)
detector._register_branch({"name": "EMPTY", "path": ""})
assert len(detector.known_branches) == initial_count
def test_skips_missing_keys(self):
"""Should skip branch entries with missing name or path keys."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod, branches={})
initial_count = len(detector.known_branches)
detector._register_branch({})
assert len(detector.known_branches) == initial_count
class TestLoadRegistry:
"""Tests for BranchDetector._load_registry()."""
def test_loads_branches_from_valid_registry(self):
"""Should populate known_branches from valid registry file."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod, branches={})
detector._repo_root = Path("/fake/root")
registry_data = {
"branches": [
{"name": "PRAX", "path": "/fake/prax"},
{"name": "FLOW", "path": "/fake/flow"},
],
}
mock_registry = MagicMock(spec=Path)
mock_registry.exists.return_value = True
mock_root = MagicMock(spec=Path)
mock_root.__truediv__ = MagicMock(return_value=mock_registry)
with (
patch.object(detector, "_find_repo_root", return_value=mock_root),
patch("builtins.open", _mopen(read_data=json.dumps(registry_data))),
patch(f"{mod.__name__}.json.load", return_value=registry_data),
patch(f"{mod.__name__}.Path") as mock_path_cls,
):
mock_path_cls.return_value.resolve.return_value = MagicMock(__str__=MagicMock(return_value="/resolved"))
detector._load_registry()
assert len(detector.known_branches) >= 2
def test_loads_fallback_when_registry_missing(self):
"""Should load fallback branches when registry file not found."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod, branches={})
detector.known_branches.clear()
mock_root = MagicMock(spec=Path)
mock_registry = MagicMock(spec=Path)
mock_registry.exists.return_value = False
mock_root.__truediv__ = MagicMock(return_value=mock_registry)
with patch.object(detector, "_find_repo_root", return_value=mock_root):
detector._load_registry()
assert "PRAX" in detector.known_branches
assert "SEEDGO" in detector.known_branches
def test_loads_fallback_when_no_branches_key(self):
"""Should load fallback when registry has empty branches array."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod, branches={})
detector.known_branches.clear()
mock_root = MagicMock(spec=Path)
mock_registry = MagicMock(spec=Path)
mock_registry.exists.return_value = True
mock_root.__truediv__ = MagicMock(return_value=mock_registry)
with (
patch.object(detector, "_find_repo_root", return_value=mock_root),
patch("builtins.open", _mopen(read_data='{"branches": []}')),
patch(f"{mod.__name__}.json.load", return_value={"branches": []}),
):
detector._load_registry()
assert "PRAX" in detector.known_branches
def test_loads_fallback_on_json_decode_error(self):
"""Should load fallback on JSONDecodeError."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod, branches={})
detector.known_branches.clear()
mock_root = MagicMock(spec=Path)
mock_registry = MagicMock(spec=Path)
mock_registry.exists.return_value = True
mock_root.__truediv__ = MagicMock(return_value=mock_registry)
with (
patch.object(detector, "_find_repo_root", return_value=mock_root),
patch("builtins.open", _mopen(read_data="{bad json")),
patch(
f"{mod.__name__}.json.load",
side_effect=json.JSONDecodeError("err", "doc", 0),
),
):
detector._load_registry()
assert "PRAX" in detector.known_branches
def test_loads_fallback_on_generic_exception(self):
"""Should load fallback on any unexpected exception."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod, branches={})
detector.known_branches.clear()
mock_root = MagicMock(spec=Path)
mock_registry = MagicMock(spec=Path)
mock_registry.exists.return_value = True
mock_root.__truediv__ = MagicMock(return_value=mock_registry)
with (
patch.object(detector, "_find_repo_root", return_value=mock_root),
patch("builtins.open", side_effect=PermissionError("denied")),
):
detector._load_registry()
assert "PRAX" in detector.known_branches
class TestLoadFallbackBranches:
"""Tests for BranchDetector._load_fallback_branches()."""
def test_populates_known_branches(self):
"""Should populate known_branches with standard fallback set."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod, branches={})
detector.known_branches.clear()
detector._load_fallback_branches()
expected = {
"SEEDGO",
"CLI",
"FLOW",
"PRAX",
"DRONE",
"BACKUP",
"SECURITY",
"AIPASS",
}
assert detector.known_branches == expected
class TestResolveExternalProjectName:
"""Tests for BranchDetector._resolve_external_project_name()."""
def test_returns_uppercased_name_when_no_registry(self):
"""Should return uppercased directory name when no _REGISTRY.json found."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_home = MagicMock(spec=Path)
mock_projects = MagicMock(spec=Path)
mock_project_dir = MagicMock(spec=Path)
mock_project_dir.exists.return_value = True
mock_project_dir.iterdir.return_value = []
mock_projects.__truediv__ = MagicMock(return_value=mock_project_dir)
mock_home.__truediv__ = MagicMock(return_value=mock_projects)
mock_path_cls.home.return_value = mock_home
result = detector._resolve_external_project_name("MyProject")
assert result == "MYPROJECT"
def test_returns_uppercased_when_dir_not_found(self):
"""Should return uppercased name when project dir doesn't exist."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_home = MagicMock(spec=Path)
mock_projects = MagicMock(spec=Path)
mock_project_dir = MagicMock(spec=Path)
mock_project_dir.exists.return_value = False
mock_projects.__truediv__ = MagicMock(return_value=mock_project_dir)
mock_home.__truediv__ = MagicMock(return_value=mock_projects)
mock_path_cls.home.return_value = mock_home
result = detector._resolve_external_project_name("NoSuchProject")
assert result == "NOSUCHPROJECT"
def test_handles_os_error_on_iterdir(self):
"""Should return uppercased name on OSError reading directory."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_home = MagicMock(spec=Path)
mock_projects = MagicMock(spec=Path)
mock_project_dir = MagicMock(spec=Path)
mock_project_dir.exists.return_value = True
mock_project_dir.iterdir.side_effect = OSError("permission denied")
mock_projects.__truediv__ = MagicMock(return_value=mock_project_dir)
mock_home.__truediv__ = MagicMock(return_value=mock_projects)
mock_path_cls.home.return_value = mock_home
result = detector._resolve_external_project_name("Locked")
assert result == "LOCKED"
class TestParseExternalProjectPath:
"""Tests for BranchDetector._parse_external_project_path()."""
def test_returns_none_for_non_encoded_path(self):
"""Should return (None, None) if path doesn't start with dash."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
project, agent = detector._parse_external_project_path("not-encoded")
assert project is None
assert agent is None
def test_returns_none_when_no_projects_boundary(self):
"""Should return (None, None) if -projects- not found."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
project, agent = detector._parse_external_project_path("-fakehome-user-something")
assert project is None
assert agent is None
def test_parses_project_without_src(self):
"""Should parse project name without src subdirectory."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
with patch.object(detector, "_resolve_external_project_name", return_value="VERA-STUDIO"):
project, agent = detector._parse_external_project_path("-fakehome-user-Projects-Vera-Studio")
assert project == "VERA-STUDIO"
assert agent is None
def test_parses_project_with_src_agent(self):
"""Should parse project and agent from -src- segment."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
with patch.object(detector, "_resolve_external_project_name", return_value="AIPL"):
project, agent = detector._parse_external_project_path("-fakehome-user-Projects-AIPL-src-polyglot")
assert project == "AIPL"
assert agent == "POLYGLOT"
class TestDetectFromClaudeProject:
"""Tests for BranchDetector._detect_from_claude_project()."""
def test_internal_aipass_branch_via_registry(self):
"""Should detect internal AIPass branch from Claude project path."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
path = str(_FAKE_HOME) + "/.claude/projects/" + "-fakehome-user-Projects-AIPass-src-aipass-prax/session.jsonl"
result = detector._detect_from_claude_project(path)
assert result is not None
assert "AIPASS" in result
def test_internal_aipass_fallback_to_segment_scan(self):
"""Should fall back to known branch name scanning for internal AIPass."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector.known_branches.add("PRAX")
path = str(_FAKE_HOME) + "/.claude/projects/" + "-fakehome-user-Projects-AIPass-src-aipass-prax/something.jsonl"
result = detector._detect_from_claude_project(path)
assert result is not None
assert "PRAX" in result
def test_internal_aipass_subagent_suffix(self):
"""Should append SUB for subagent paths."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector.known_branches.add("PRAX")
path = (
str(_FAKE_HOME)
+ "/.claude/projects/"
+ "-fakehome-user-Projects-AIPass-src-aipass-prax"
+ "/subagents/abc123/session.jsonl"
)
result = detector._detect_from_claude_project(path)
assert result is not None
assert "SUB" in result
def test_external_project_detection(self):
"""Should detect external project from Claude project path."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
with patch.object(
detector,
"_parse_external_project_path",
return_value=("AIPL", "POLYGLOT"),
):
path = str(_FAKE_HOME) + "/.claude/projects/" + "-fakehome-user-Projects-AIPL-src-polyglot/file.jsonl"
result = detector._detect_from_claude_project(path)
assert result == "AIPL/POLYGLOT"
def test_external_project_main_session(self):
"""Should return project name only for main session (no agent)."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
with patch.object(
detector,
"_parse_external_project_path",
return_value=("VERA-STUDIO", None),
):
path = str(_FAKE_HOME) + "/.claude/projects/" + "-fakehome-user-Projects-Vera-Studio/file.jsonl"
result = detector._detect_from_claude_project(path)
assert result == "VERA-STUDIO"
def test_external_project_subagent(self):
"""Should append SUB to external project subagent paths."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
with patch.object(
detector,
"_parse_external_project_path",
return_value=("VERA-STUDIO", None),
):
path = (
str(_FAKE_HOME)
+ "/.claude/projects/"
+ "-fakehome-user-Projects-Vera-Studio"
+ "/subagents/abc/file.jsonl"
)
result = detector._detect_from_claude_project(path)
assert result == "VERA-STUDIO SUB"
def test_old_fallback_segment_scan(self):
"""Should fall back to segment scanning for unknown external paths."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector.known_branches.add("SEEDGO")
with patch.object(
detector,
"_parse_external_project_path",
return_value=(None, None),
):
path = str(_FAKE_HOME) + "/.claude/projects/some-random-seedgo/file.jsonl"
result = detector._detect_from_claude_project(path)
assert result == "SEEDGO"
def test_old_fallback_returns_none_for_empty_segments(self):
"""Should return None when folder parses to no segments."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
with patch.object(
detector,
"_parse_external_project_path",
return_value=(None, None),
):
path = str(_FAKE_HOME) + "/.claude/projects//file.jsonl"
result = detector._detect_from_claude_project(path)
assert result is None
def test_old_fallback_last_segment_known(self):
"""Should return last segment if it matches a known branch."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector.known_branches.add("CLI")
with patch.object(
detector,
"_parse_external_project_path",
return_value=(None, None),
):
path = str(_FAKE_HOME) + "/.claude/projects/unknown-cli/file.jsonl"
result = detector._detect_from_claude_project(path)
assert result == "CLI"
def test_old_fallback_returns_none_when_no_branch_match(self):
"""Should return None when no segments match known branches."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
with patch.object(
detector,
"_parse_external_project_path",
return_value=(None, None),
):
path = str(_FAKE_HOME) + "/.claude/projects/completely-unknown-xyz/file.jsonl"
result = detector._detect_from_claude_project(path)
assert result is None
def test_internal_aipass_fallback_last_segment(self):
"""When no known branch found, should use last segment uppercased."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod, branches={})
path = str(_FAKE_HOME) + "/.claude/projects/" + "-fakehome-user-Projects-AIPass-src-aipass-newmod/file.jsonl"
result = detector._detect_from_claude_project(path)
assert result is not None
assert "NEWMOD" in result
class TestDetectFromCompoundParts:
"""Tests for BranchDetector._detect_from_compound_parts()."""
def test_finds_known_branch_in_underscore_part(self):
"""Should detect branch from underscore-delimited path part."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector.known_branches.add("MAIL")
result = detector._detect_from_compound_parts(["src", "ai_mail", "handler"])
assert result == "MAIL"
def test_returns_none_when_no_match(self):
"""Should return None when no underscore subparts match."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
result = detector._detect_from_compound_parts(["src", "something_else"])
assert result is None
def test_skips_parts_without_underscore(self):
"""Should only check parts that contain underscores."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
result = detector._detect_from_compound_parts(["simple", "parts", "here"])
assert result is None
class TestDetectFromExternalProjectPath:
"""Tests for BranchDetector._detect_from_external_project_path()."""
def test_path_not_under_projects(self):
"""Should return None if path is not under ~/Projects/."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
path = Path("/tmp/some/random/file.py")
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path_cls.home.return_value = _FAKE_HOME
result = detector._detect_from_external_project_path(path)
assert result is None
def test_skips_aipass_directory(self):
"""Should return None for paths under AIPass directory."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
path = _FAKE_PROJECTS / "AIPass" / "src" / "prax" / "file.py"
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path_cls.home.return_value = _FAKE_HOME
result = detector._detect_from_external_project_path(path)
assert result is None
def test_returns_project_with_agent(self):
"""Should return PROJECT/AGENT for path with src subdirectory."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector._external_project_cache["AIPL"] = "AIPL"
path = _FAKE_PROJECTS / "AIPL" / "src" / "polyglot" / "file.py"
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path_cls.home.return_value = _FAKE_HOME
result = detector._detect_from_external_project_path(path)
assert result == "AIPL/POLYGLOT"
def test_returns_project_without_agent(self):
"""Should return PROJECT for path without src subdirectory."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector._external_project_cache["MyApp"] = "MYAPP"
path = _FAKE_PROJECTS / "MyApp" / "config.json"
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path_cls.home.return_value = _FAKE_HOME
result = detector._detect_from_external_project_path(path)
assert result == "MYAPP"
def test_test_suffix_appended(self):
"""Should append TESTS suffix for test-related paths."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector._external_project_cache["AIPL"] = "AIPL"
path = _FAKE_PROJECTS / "AIPL" / "src" / "polyglot" / "tests" / "test_main.py"
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path_cls.home.return_value = _FAKE_HOME
result = detector._detect_from_external_project_path(path)
assert result == "AIPL/POLYGLOT TESTS"
def test_cached_project_name(self):
"""Should use cached project name on subsequent lookups."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector._external_project_cache["CachedProj"] = "CACHED"
path = _FAKE_PROJECTS / "CachedProj" / "file.py"
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path_cls.home.return_value = _FAKE_HOME
result = detector._detect_from_external_project_path(path)
assert result == "CACHED"
def test_empty_rel_parts_returns_none(self):
"""Should return None if relative path has no parts."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path_cls.home.return_value = _FAKE_HOME
result = detector._detect_from_external_project_path(_FAKE_PROJECTS)
assert result is None
def test_returns_none_when_no_registry_file(self):
"""Should return None when project directory has no _REGISTRY.json."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector._external_project_cache.clear()
path = _FAKE_PROJECTS / "SomeProject" / "src" / "module" / "file.py"
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path_cls.home.return_value = _FAKE_HOME
mock_project_dir = MagicMock(spec=Path)
mock_project_dir.iterdir.return_value = iter([])
result = detector._detect_from_external_project_path(path)
assert result is None
def test_test_suffix_for_test_log_file(self):
"""Should append TESTS for paths ending in _test.log."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector._external_project_cache["Proj"] = "PROJ"
path = _FAKE_PROJECTS / "Proj" / "output_test.log"
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path_cls.home.return_value = _FAKE_HOME
result = detector._detect_from_external_project_path(path)
assert result == "PROJ TESTS"
class TestExtractBranchFromCentral:
"""Tests for BranchDetector._extract_branch_from_central()."""
def test_dot_central_json(self):
"""Should extract branch name from .central.json filename."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
path = Path("/some/ai_mail/DRONE.central.json")
result = detector._extract_branch_from_central(str(path), path)
assert result == "DRONE"
def test_underscore_central_json(self):
"""Should extract branch name from _central.json filename."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
path = Path("/some/dir/ai_mail/PRAX_central.json")
result = detector._extract_branch_from_central("ai_mail/PRAX_central.json", path)
assert result == "PRAX"
def test_returns_none_for_non_mail_path(self):
"""Should return None if path doesn't contain ai_mail or AI_MAIL."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
path = Path("/some/dir/config.json")
result = detector._extract_branch_from_central(str(path), path)
assert result is None
def test_returns_none_for_non_central_filename(self):
"""Should return None for non-central filename in ai_mail path."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
path = Path("/ai_mail/inbox.json")
result = detector._extract_branch_from_central("ai_mail/inbox.json", path)
assert result is None
class TestDetectFromPathStrategies:
"""Tests for detect_from_path covering all strategy branches."""
def test_cache_hit_returns_cached_value(self):
"""Strategy 0: Should return cached value from log_map."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
resolved = "/cached/path"
detector.log_map[resolved] = "CACHED_BRANCH"
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path = MagicMock(spec=Path)
mock_path.__str__ = MagicMock(return_value=resolved)
mock_path.resolve.return_value = mock_path
mock_path_cls.return_value = mock_path
result = detector.detect_from_path(resolved)
assert result == "CACHED_BRANCH"
def test_claude_project_path_strategy(self):
"""Strategy 3: Should detect from .claude/projects/ path."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector.known_branches.add("PRAX")
resolved = (
str(_FAKE_HOME) + "/.claude/projects/" + "-fakehome-user-Projects-AIPass-src-aipass-prax/session.jsonl"
)
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path = MagicMock(spec=Path)
mock_path.__str__ = MagicMock(return_value=resolved)
mock_path.resolve.return_value = mock_path
mock_path.parents = []
mock_path.parent = MagicMock()
mock_path.parent.__eq__ = MagicMock(return_value=False)
mock_path.name = "session.jsonl"
mock_path_cls.return_value = mock_path
mock_path_cls.home.return_value = _FAKE_HOME
detector._repo_root = MagicMock(spec=Path)
detector._repo_root.__str__ = MagicMock(return_value=str(_FAKE_PROJECTS / "AIPass"))
result = detector.detect_from_path(resolved)
assert "PRAX" in result
def test_ai_mail_central_strategy(self):
"""Strategy 4: Should detect from ai_mail central filename."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
resolved = str(_FAKE_PROJECTS / "AIPass" / "src" / "aipass" / "ai_mail" / "central" / "DRONE.central.json")
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path = MagicMock(spec=Path)
mock_path.__str__ = MagicMock(return_value=resolved)
mock_path.resolve.return_value = mock_path
mock_path.parents = []
mock_path.parent = MagicMock()
mock_path.parent.__eq__ = MagicMock(return_value=False)
mock_path.name = "DRONE.central.json"
mock_path_cls.return_value = mock_path
mock_path_cls.home.return_value = _FAKE_HOME
detector._repo_root = MagicMock(spec=Path)
detector._repo_root.__str__ = MagicMock(return_value=str(_FAKE_PROJECTS / "AIPass"))
result = detector.detect_from_path(resolved)
assert result == "DRONE"
def test_root_level_system_files_strategy(self):
"""Strategy 5: Should return SYSTEM for repo root files."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
repo_root = _FAKE_PROJECTS / "AIPass"
resolved = str(repo_root / "README.md")
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path = MagicMock(spec=Path)
mock_path.__str__ = MagicMock(return_value=resolved)
mock_path.resolve.return_value = mock_path
mock_path.parents = []
mock_path.parent = repo_root
mock_path.name = "README.md"
mock_path_cls.return_value = mock_path
mock_path_cls.home.return_value = _FAKE_HOME
detector._repo_root = repo_root
result = detector.detect_from_path(resolved)
assert result == "SYSTEM"
def test_root_claude_dir_system_files(self):
"""Strategy 5: Should return SYSTEM for .claude dir under repo root."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
repo_root = _FAKE_PROJECTS / "AIPass"
resolved = str(repo_root / ".claude" / "settings.json")
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path = MagicMock(spec=Path)
mock_path.__str__ = MagicMock(return_value=resolved)
mock_path.resolve.return_value = mock_path
mock_path.parents = []
mock_path.parent = repo_root / ".claude"
mock_path.name = "settings.json"
mock_path_cls.return_value = mock_path
mock_path_cls.home.return_value = _FAKE_HOME
detector._repo_root = repo_root
result = detector.detect_from_path(resolved)
assert result == "SYSTEM"
def test_known_branch_in_path_parts_strategy(self):
"""Strategy 6: Should detect branch from path parts."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector.known_branches.add("DRONE")
resolved = "/some/random/drone/handler.py"
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path = MagicMock(spec=Path)
mock_path.__str__ = MagicMock(return_value=resolved)
mock_path.resolve.return_value = mock_path
mock_path.parents = []
mock_path.parent = MagicMock()
mock_path.parent.__eq__ = MagicMock(return_value=False)
mock_path.name = "handler.py"
mock_path_cls.return_value = mock_path
mock_path_cls.home.return_value = _FAKE_HOME
detector._repo_root = MagicMock(spec=Path)
detector._repo_root.__str__ = MagicMock(return_value=str(_FAKE_PROJECTS / "AIPass"))
detector._repo_root.__truediv__ = MagicMock(return_value=MagicMock())
result = detector.detect_from_path(resolved)
assert result == "DRONE"
def test_compound_parts_detection_strategy(self):
"""Strategy 6 (compound): Should detect from compound underscore parts."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
# Add WIDGET as known branch but NOT "SOME_WIDGET" — so only
# compound splitting of "some_widget" -> ["some", "widget"] can match.
detector.known_branches.add("WIDGET")
resolved = "/unrelated/some_widget/handler.py"
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path = MagicMock(spec=Path)
mock_path.__str__ = MagicMock(return_value=resolved)
mock_path.resolve.return_value = mock_path
mock_path.parents = []
mock_path.parent = MagicMock()
mock_path.parent.__eq__ = MagicMock(return_value=False)
mock_path.name = "handler.py"
mock_path_cls.return_value = mock_path
mock_path_cls.home.return_value = _FAKE_HOME
detector._repo_root = MagicMock(spec=Path)
detector._repo_root.__str__ = MagicMock(return_value=str(_FAKE_PROJECTS / "AIPass"))
detector._repo_root.__truediv__ = MagicMock(return_value=MagicMock())
result = detector.detect_from_path(resolved)
assert result == "WIDGET"
def test_external_project_path_priority(self):
"""Strategy 0 (external): Should detect external project with priority."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
resolved = str(_FAKE_PROJECTS / "AIPL" / "src" / "polyglot" / "tests" / "test_main.py")
with patch(f"{mod.__name__}.Path") as mock_path_cls:
mock_path = MagicMock(spec=Path)
mock_path.__str__ = MagicMock(return_value=resolved)
mock_path.resolve.return_value = mock_path
mock_path.parents = []
mock_path.parent = MagicMock()
mock_path.name = "test_main.py"
mock_path_cls.return_value = mock_path
mock_path_cls.home.return_value = _FAKE_HOME
detector._repo_root = MagicMock(spec=Path)
detector._repo_root.__str__ = MagicMock(return_value=str(_FAKE_PROJECTS / "AIPass"))
with patch.object(
detector,
"_detect_from_external_project_path",
return_value="AIPL/POLYGLOT TESTS",
):
result = detector.detect_from_path(resolved)
assert result == "AIPL/POLYGLOT TESTS"
class TestDetectFromLogAdditional:
"""Additional tests for detect_from_log covering uncovered lines."""
def test_log_name_matches_branch_exactly(self):
"""Should detect branch when log name equals known branch."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector.known_branches.add("BACKUP")
result = detector.detect_from_log("backup.log")
assert result == "BACKUP"
assert "backup" in detector.log_map
def test_cache_hit_on_stem(self):
"""Should return cached stem from log_map."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector.log_map["cached_log"] = "CACHED"
result = detector.detect_from_log("cached_log.log")
assert result == "CACHED"
class TestDetectFromModuleAdditional:
"""Additional tests for detect_from_module covering uncovered lines."""
def test_module_cache_hit(self):
"""Should return cached module from module_map."""
mod = _import_branch_detector()
detector = _make_detector_with_branches(mod)
detector.module_map["cached.module"] = "CACHED"
result = detector.detect_from_module("cached.module")
assert result == "CACHED"
class TestPublicAPIFunctions:
"""Tests for module-level public API functions."""
def test_detect_branch_from_path_delegates(self):
"""detect_branch_from_path should delegate to get_detector."""
mod = _import_branch_detector()
mock_detector = MagicMock()
mock_detector.detect_from_path.return_value = "PRAX"
setattr(mod, "_detector_instance", mock_detector)
result = mod.detect_branch_from_path("/some/path")
assert result == "PRAX"
mock_detector.detect_from_path.assert_called_once_with("/some/path")
def test_detect_branch_from_log_delegates(self):
"""detect_branch_from_log should delegate to get_detector."""
mod = _import_branch_detector()
mock_detector = MagicMock()
mock_detector.detect_from_log.return_value = "FLOW"
setattr(mod, "_detector_instance", mock_detector)
result = mod.detect_branch_from_log("flow_plan.log")
assert result == "FLOW"
mock_detector.detect_from_log.assert_called_once_with("flow_plan.log")
def test_reload_registry_delegates(self):
"""reload_registry should delegate to get_detector."""
mod = _import_branch_detector()
mock_detector = MagicMock()
setattr(mod, "_detector_instance", mock_detector)
mod.reload_registry()
mock_detector.reload_registry.assert_called_once()
+621
View File
@@ -1318,3 +1318,624 @@ class TestPrintTemplate:
mod = _load_dashboard_module()
# Should not raise
mod.print_template()
# =============================================
# print_introspection (dashboard.py)
# =============================================
class TestDashboardPrintIntrospection:
"""Tests for print_introspection -- CLI module info display."""
def test_prints_introspection_without_error(self):
"""Introspection output runs without raising."""
mod = _load_dashboard_module()
mod.print_introspection()
def test_introspection_lists_template_sections(self):
"""Introspection lists all template section names."""
mod = _load_dashboard_module()
mod.print_introspection()
# console.print should have been called multiple times
assert mod.console.print.call_count > 3
# =============================================
# print_help (dashboard.py)
# =============================================
class TestDashboardPrintHelp:
"""Tests for print_help -- CLI help output."""
def test_prints_help_without_error(self):
"""Help output runs without raising."""
mod = _load_dashboard_module()
mod.print_help()
def test_help_lists_commands(self):
"""Help output includes subcommand names."""
mod = _load_dashboard_module()
mod.print_help()
assert mod.console.print.call_count > 5
# =============================================
# handle_command (dashboard.py)
# =============================================
class TestDashboardHandleCommand:
"""Tests for handle_command -- command routing."""
def test_returns_false_for_non_dashboard_command(self):
"""handle_command returns False for commands other than 'dashboard'."""
mod = _load_dashboard_module()
assert mod.handle_command("monitor", []) is False
def test_no_args_calls_print_introspection(self):
"""Bare 'dashboard' with no args shows introspection."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
with _patch.object(mod, "print_introspection") as mock_intro:
result = mod.handle_command("dashboard", [])
assert result is True
mock_intro.assert_called_once()
def test_help_flag_calls_print_help(self):
"""--help flag shows help and returns True."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
for flag in ("--help", "-h", "help"):
with _patch.object(mod, "print_help") as mock_help:
result = mod.handle_command("dashboard", [flag])
assert result is True
mock_help.assert_called_once()
def test_status_subcommand(self):
"""'status' subcommand dispatches to print_status."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
with _patch.object(mod, "print_status") as mock_status:
result = mod.handle_command("dashboard", ["status"])
assert result is True
mock_status.assert_called_once()
def test_template_subcommand(self):
"""'template' subcommand dispatches to print_template."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
with _patch.object(mod, "print_template") as mock_tmpl:
result = mod.handle_command("dashboard", ["template"])
assert result is True
mock_tmpl.assert_called_once()
def test_refresh_subcommand(self):
"""'refresh' subcommand dispatches to _handle_refresh."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
with _patch.object(mod, "_handle_refresh") as mock_refresh:
result = mod.handle_command("dashboard", ["refresh"])
assert result is True
mock_refresh.assert_called_once_with([])
def test_refresh_subcommand_passes_args(self):
"""'refresh' subcommand forwards trailing args."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
with _patch.object(mod, "_handle_refresh") as mock_refresh:
mod.handle_command("dashboard", ["refresh", "--all"])
mock_refresh.assert_called_once_with(["--all"])
def test_push_template_subcommand(self):
"""'push-template' subcommand dispatches to _handle_push_template."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
with _patch.object(mod, "_handle_push_template") as mock_push:
result = mod.handle_command("dashboard", ["push-template"])
assert result is True
mock_push.assert_called_once_with([])
def test_diff_template_subcommand(self):
"""'diff-template' subcommand dispatches to _handle_diff_template."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
with _patch.object(mod, "_handle_diff_template") as mock_diff:
result = mod.handle_command("dashboard", ["diff-template"])
assert result is True
mock_diff.assert_called_once_with([])
def test_template_status_subcommand(self):
"""'template-status' subcommand dispatches to _handle_template_status."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
with _patch.object(mod, "_handle_template_status") as mock_ts:
result = mod.handle_command("dashboard", ["template-status"])
assert result is True
mock_ts.assert_called_once()
def test_unknown_subcommand_shows_help(self):
"""Unknown subcommand shows help and returns True."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
with _patch.object(mod, "print_help") as mock_help:
result = mod.handle_command("dashboard", ["bogus"])
assert result is True
mock_help.assert_called_once()
# =============================================
# _handle_refresh (dashboard.py)
# =============================================
class TestHandleRefresh:
"""Tests for _handle_refresh -- refresh command handler."""
def test_refresh_all(self, monkeypatch):
"""--all flag refreshes all branches."""
mod = _load_dashboard_module()
monkeypatch.setattr(
mod,
"refresh_all_dashboards",
lambda: {"status": "success", "branches_updated": 3},
)
mod._handle_refresh(["--all"])
def test_refresh_all_partial(self, monkeypatch):
"""Partial success path for --all refresh."""
mod = _load_dashboard_module()
monkeypatch.setattr(
mod,
"refresh_all_dashboards",
lambda: {
"status": "partial",
"branches_updated": 2,
"branches_failed": 1,
"errors": ["branch X failed"],
},
)
mod._handle_refresh(["--all"])
def test_refresh_all_failure(self, monkeypatch):
"""Full failure path for --all refresh."""
mod = _load_dashboard_module()
monkeypatch.setattr(
mod,
"refresh_all_dashboards",
lambda: {
"status": "error",
"branches_updated": 0,
"branches_failed": 3,
"errors": ["total failure"],
},
)
mod._handle_refresh(["--all"])
def test_refresh_specific_branch_success(self, tmp_path, monkeypatch):
"""@branch arg refreshes specific branch on success."""
mod = _load_dashboard_module()
monkeypatch.setattr(mod, "_resolve_branch_path", lambda ref: tmp_path / "flow")
monkeypatch.setattr(
mod,
"refresh_single_dashboard",
lambda bp: {"status": "success", "branch": "FLOW"},
)
mod._handle_refresh(["@flow"])
def test_refresh_specific_branch_failure(self, tmp_path, monkeypatch):
"""@branch arg shows error on refresh failure."""
mod = _load_dashboard_module()
monkeypatch.setattr(mod, "_resolve_branch_path", lambda ref: tmp_path / "flow")
monkeypatch.setattr(
mod,
"refresh_single_dashboard",
lambda bp: {"status": "error", "error": "disk full"},
)
mod._handle_refresh(["@flow"])
def test_refresh_specific_branch_not_found(self, monkeypatch):
"""@branch arg handles branch not found in registry."""
mod = _load_dashboard_module()
def _raise(ref):
"""Simulate branch not found."""
raise FileNotFoundError("not found in registry")
monkeypatch.setattr(mod, "_resolve_branch_path", _raise)
mod._handle_refresh(["@ghost"])
def test_refresh_cwd_success(self, tmp_path, monkeypatch):
"""No args refreshes current branch from CWD."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
monkeypatch.setattr(
mod,
"refresh_single_dashboard",
lambda bp: {"status": "success", "branch": "PRAX"},
)
with _patch("pathlib.Path.cwd", return_value=tmp_path):
mod._handle_refresh([])
def test_refresh_cwd_failure(self, tmp_path, monkeypatch):
"""No args shows error on CWD refresh failure."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
monkeypatch.setattr(
mod,
"refresh_single_dashboard",
lambda bp: {"status": "error", "error": "no dashboard"},
)
with _patch("pathlib.Path.cwd", return_value=tmp_path):
mod._handle_refresh([])
def test_refresh_cwd_walks_up_to_find_branch(self, tmp_path, monkeypatch):
"""No args walks up directory tree to find branch root."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
# Create a branch root with .aipass marker above CWD
branch_root = tmp_path / "src" / "aipass" / "flow"
branch_root.mkdir(parents=True)
(branch_root / ".aipass").mkdir()
deep_cwd = branch_root / "apps" / "modules"
deep_cwd.mkdir(parents=True)
monkeypatch.setattr(
mod,
"refresh_single_dashboard",
lambda bp: {"status": "success", "branch": bp.name.upper()},
)
with _patch("pathlib.Path.cwd", return_value=deep_cwd):
mod._handle_refresh([])
# =============================================
# _handle_push_template (dashboard.py)
# =============================================
class TestHandlePushTemplate:
"""Tests for _handle_push_template -- push template handler."""
def test_push_template_with_changes(self, monkeypatch):
"""Push template shows changes when branches need updating."""
mod = _load_dashboard_module()
monkeypatch.setattr(
mod,
"push_dashboard_template",
lambda dry_run=False: {
"branches_scanned": 5,
"branches_updated": 2,
"branches_created": 1,
"branches_skipped": 2,
"changes": [
{
"branch": "FLOW",
"actions": ["removed bulletin_board"],
}
],
"errors": [],
},
)
mod._handle_push_template([])
def test_push_template_dry_run(self, monkeypatch):
"""Dry run flag is forwarded to push_dashboard_template."""
mod = _load_dashboard_module()
captured: dict[str, object] = {}
def _mock_push(dry_run=False):
"""Capture dry_run flag."""
captured["dry_run"] = dry_run
return {
"branches_scanned": 3,
"branches_updated": 0,
"branches_created": 0,
"branches_skipped": 3,
"changes": [],
"errors": [],
}
monkeypatch.setattr(mod, "push_dashboard_template", _mock_push)
mod._handle_push_template(["--dry-run"])
assert captured["dry_run"] is True
def test_push_template_with_errors(self, monkeypatch):
"""Push template shows errors when some branches fail."""
mod = _load_dashboard_module()
monkeypatch.setattr(
mod,
"push_dashboard_template",
lambda dry_run=False: {
"branches_scanned": 3,
"branches_updated": 1,
"branches_created": 0,
"branches_skipped": 1,
"changes": [],
"errors": ["branch X: permission denied"],
},
)
mod._handle_push_template([])
def test_push_template_all_up_to_date(self, monkeypatch):
"""Push template shows 'all up to date' message."""
mod = _load_dashboard_module()
monkeypatch.setattr(
mod,
"push_dashboard_template",
lambda dry_run=False: {
"branches_scanned": 3,
"branches_updated": 0,
"branches_created": 0,
"branches_skipped": 3,
"changes": [],
"errors": [],
},
)
mod._handle_push_template([])
# =============================================
# _handle_diff_template (dashboard.py)
# =============================================
class TestHandleDiffTemplate:
"""Tests for _handle_diff_template -- diff template handler."""
def test_diff_template_error(self, monkeypatch):
"""Diff template shows error when result has error key."""
mod = _load_dashboard_module()
monkeypatch.setattr(
mod,
"diff_dashboard_template",
lambda branch_name=None: {"error": "template not found"},
)
mod._handle_diff_template([])
def test_diff_template_with_branch_flag(self, monkeypatch):
"""--branch flag filters diff to single branch."""
mod = _load_dashboard_module()
captured: dict[str, object] = {}
def _mock_diff(branch_name=None):
"""Capture branch_name arg."""
captured["branch_name"] = branch_name
return {
"summary": {
"needs_update": 0,
"up_to_date": 1,
"missing": 0,
},
"branches": [],
}
monkeypatch.setattr(mod, "diff_dashboard_template", _mock_diff)
mod._handle_diff_template(["--branch", "FLOW"])
assert captured["branch_name"] == "FLOW"
def test_diff_template_branch_flag_missing_name(self):
"""--branch without name prints error."""
mod = _load_dashboard_module()
mod._handle_diff_template(["--branch"])
mod.error.assert_called()
def test_diff_template_with_changes(self, monkeypatch):
"""Diff template shows additions, removals, and modifications."""
mod = _load_dashboard_module()
monkeypatch.setattr(
mod,
"diff_dashboard_template",
lambda branch_name=None: {
"summary": {
"needs_update": 1,
"up_to_date": 1,
"missing": 1,
"invalid_json": 1,
},
"branches": [
{
"branch": "FLOW",
"status": "needs_update",
"additions": ["+ commons_activity"],
"removals": ["- bulletin_board"],
"modifications": ["~ ai_mail.new default changed"],
},
{"branch": "MISSING", "status": "missing"},
{"branch": "GOOD", "status": "up_to_date"},
],
},
)
mod._handle_diff_template([])
# =============================================
# _handle_template_status (dashboard.py)
# =============================================
class TestHandleTemplateStatus:
"""Tests for _handle_template_status -- template status display."""
def test_template_status_basic(self, monkeypatch):
"""Template status displays version info."""
mod = _load_dashboard_module()
monkeypatch.setattr(
mod,
"get_template_status",
lambda: {
"templates_dir": "/path/to/templates",
"template_exists": True,
"version": "3.0.0",
"last_updated": "2026-03-01",
"updated_by": "prax",
"last_push": "2026-03-02",
"last_push_branches": ["FLOW", "AI_MAIL"],
"changes": [],
},
)
mod._handle_template_status()
def test_template_status_with_many_branches(self, monkeypatch):
"""Template status truncates branch list at 5 with ellipsis."""
mod = _load_dashboard_module()
monkeypatch.setattr(
mod,
"get_template_status",
lambda: {
"templates_dir": "/path/to/templates",
"template_exists": True,
"version": "3.0.0",
"last_updated": "2026-03-01",
"updated_by": "prax",
"last_push": "2026-03-02",
"last_push_branches": [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
],
"changes": ["added commons"],
},
)
mod._handle_template_status()
def test_template_status_missing(self, monkeypatch):
"""Template status handles missing template."""
mod = _load_dashboard_module()
monkeypatch.setattr(
mod,
"get_template_status",
lambda: {
"templates_dir": "/path/to/templates",
"template_exists": False,
"version": None,
"last_updated": None,
"updated_by": None,
"last_push": None,
"last_push_branches": [],
"changes": [],
},
)
mod._handle_template_status()
def test_template_status_no_push(self, monkeypatch):
"""Template status handles never-pushed template."""
mod = _load_dashboard_module()
monkeypatch.setattr(
mod,
"get_template_status",
lambda: {
"templates_dir": "/path/to/templates",
"template_exists": True,
"version": "1.0.0",
"last_updated": "2026-01-01",
"updated_by": "dev",
"last_push": None,
"last_push_branches": [],
"changes": [],
},
)
mod._handle_template_status()
# =============================================
# _resolve_branch_path (dashboard.py wrapper)
# =============================================
class TestResolveBranchPathWrapper:
"""Tests for _resolve_branch_path -- dashboard module wrapper."""
def test_delegates_to_handler(self, monkeypatch):
"""Wrapper delegates to resolve_branch_path handler."""
mod = _load_dashboard_module()
from pathlib import Path
monkeypatch.setattr(
mod,
"resolve_branch_path",
lambda ref: Path("/fake/flow"),
)
result = mod._resolve_branch_path("@flow")
assert result == Path("/fake/flow")
# =============================================
# main (dashboard.py)
# =============================================
class TestDashboardMain:
"""Tests for main -- CLI entry point."""
def test_main_no_args(self, monkeypatch):
"""No args shows introspection."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
monkeypatch.setattr(sys, "argv", ["dashboard"])
with _patch.object(mod, "print_introspection") as mock_intro:
mod.main()
mock_intro.assert_called_once()
def test_main_help_flag(self, monkeypatch):
"""--help flag shows help."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
monkeypatch.setattr(sys, "argv", ["dashboard", "--help"])
with _patch.object(mod, "print_help") as mock_help:
mod.main()
mock_help.assert_called_once()
def test_main_h_flag(self, monkeypatch):
"""-h flag shows help."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
monkeypatch.setattr(sys, "argv", ["dashboard", "-h"])
with _patch.object(mod, "print_help") as mock_help:
mod.main()
mock_help.assert_called_once()
def test_main_valid_command(self, monkeypatch):
"""Valid command delegates to handle_command."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
monkeypatch.setattr(sys, "argv", ["dashboard", "dashboard", "status"])
with _patch.object(mod, "handle_command", return_value=True) as mock_hc:
mod.main()
mock_hc.assert_called_once_with("dashboard", ["status"])
def test_main_unknown_command(self, monkeypatch):
"""Unknown command shows error and help."""
mod = _load_dashboard_module()
from unittest.mock import patch as _patch
monkeypatch.setattr(sys, "argv", ["dashboard", "bogus"])
with (
_patch.object(mod, "handle_command", return_value=False),
_patch.object(mod, "print_help") as mock_help,
):
mod.main()
mock_help.assert_called_once()
mod.error.assert_called()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -155,7 +155,7 @@
"content_hash": "a4cf0a8e3b4f",
"has_branch_placeholder": false
},
"f015": {
"f026": {
"path": "apps/modules/__init__.py",
"name": "__init__.py",
"content_hash": "e3b0c44298fc",
@@ -263,7 +263,7 @@
"content_hash": "28e9ae373563",
"has_branch_placeholder": false
},
"f026": {
"f015": {
"path": "apps/plugins/__init__.py",
"name": "__init__.py",
"content_hash": "e3b0c44298fc",