"""Tests for seedgo handler functions (audit_display, diagnostics, json extras, readme, hooks_ext).""" # =================== META ==================== # Name: test_handler_functions.py # Description: Unit tests for handler-level functions across multiple handler packages # Version: 1.0.0 # Created: 2026-04-25 # Modified: 2026-04-25 # ============================================= import json import pytest from typing import Dict from unittest.mock import MagicMock, patch # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @pytest.fixture(autouse=True) def _mock_infrastructure(monkeypatch): """Mock heavy infrastructure imports for handler functions.""" import sys mock_logger = MagicMock() mock_json_handler = MagicMock() mock_json_handler.log_operation = MagicMock(return_value=True) # -- prax --------------------------------------------------------------- prax_mod = MagicMock() prax_mod.logger = mock_logger monkeypatch.setitem(sys.modules, "aipass.prax", prax_mod) # -- seedgo json handler ------------------------------------------------ json_pkg = MagicMock() json_pkg.json_handler = mock_json_handler monkeypatch.setitem(sys.modules, "aipass.seedgo.apps.handlers.json", json_pkg) json_mod = MagicMock() json_mod.log_operation = mock_json_handler.log_operation monkeypatch.setitem(sys.modules, "aipass.seedgo.apps.handlers.json.json_handler", json_mod) # -- bypass handler ----------------------------------------------------- bypass_pkg = MagicMock() bypass_ignore = MagicMock() bypass_ignore.get_template_ignore_patterns = MagicMock(return_value=[]) bypass_ignore.get_audit_ignore_patterns = MagicMock(return_value=[]) bypass_pkg.ignore_handler = bypass_ignore monkeypatch.setitem(sys.modules, "aipass.seedgo.apps.handlers.bypass", bypass_pkg) monkeypatch.setitem( sys.modules, "aipass.seedgo.apps.handlers.bypass.ignore_handler", bypass_ignore, ) # -- cli (console for audit_display) ------------------------------------ mock_console = MagicMock() cli_mod = MagicMock() cli_mod.console = mock_console monkeypatch.setitem(sys.modules, "aipass.cli", cli_mod) # -- cli.apps.modules (warning function for hooks_ext) ------------------ cli_apps = MagicMock() cli_apps_modules = MagicMock() cli_apps_modules.warning = MagicMock() cli_apps.modules = cli_apps_modules cli_mod.apps = cli_apps monkeypatch.setitem(sys.modules, "aipass.cli.apps", cli_apps) monkeypatch.setitem(sys.modules, "aipass.cli.apps.modules", cli_apps_modules) # Force re-imports so handler modules pick up fresh mocks for mod_name in [ "aipass.seedgo.apps.handlers.audit.audit_display", "aipass.seedgo.apps.handlers.diagnostics.diagnostics_check", "aipass.seedgo.apps.handlers.json.json_handler", "aipass.seedgo.apps.handlers.readme.readme_ops", "aipass.seedgo.apps.handlers.readme.readme_generator", ]: monkeypatch.delitem(sys.modules, mod_name, raising=False) # =========================================================================== # 1. audit_display -- print_branch_summary # =========================================================================== def test_print_branch_summary_basic(): """print_branch_summary runs without error on minimal audit result.""" from aipass.seedgo.apps.handlers.audit.audit_display import print_branch_summary audit_result: Dict = { "branch": {"name": "seedgo"}, "scores": {"meta": 100, "naming": 90}, "average": 95, "files_checked": 10, "results": {}, } # Should not raise print_branch_summary(audit_result) def test_print_branch_summary_with_violations(): """print_branch_summary handles violation lists.""" from aipass.seedgo.apps.handlers.audit.audit_display import print_branch_summary audit_result: Dict = { "branch": {"name": "testbranch"}, "scores": {"meta": 60, "naming": 80}, "average": 70, "files_checked": 5, "results": { "meta": {"checks": [{"name": "META block present", "passed": False, "message": "Missing META block"}]} }, "meta_violations": [{"path": "file.py", "score": 50, "issues": ["Missing META block"]}], } # Should not raise print_branch_summary(audit_result) def test_print_branch_summary_with_system_averages(): """print_branch_summary handles optional system averages.""" from aipass.seedgo.apps.handlers.audit.audit_display import print_branch_summary audit_result: Dict = { "branch": {"name": "seedgo"}, "scores": {"meta": 100}, "average": 100, "files_checked": 1, "results": {}, } system_averages: Dict[str, int] = {"meta": 90} # Should not raise print_branch_summary(audit_result, system_averages, 90) # =========================================================================== # 2. diagnostics_check -- check_directory # =========================================================================== def test_check_directory_missing(tmp_path): """check_directory on nonexistent directory returns error.""" from aipass.seedgo.apps.handlers.diagnostics.diagnostics_check import ( check_directory, ) result = check_directory(str(tmp_path / "nonexistent")) assert result["total_files"] == 0 assert "error" in result assert "not found" in result["error"].lower() def test_check_directory_exists(tmp_path): """check_directory on existing directory calls pyright.""" # Create a Python file py_file = tmp_path / "example.py" py_file.write_text("x = 1\n", encoding="utf-8") from aipass.seedgo.apps.handlers.diagnostics.diagnostics_check import ( check_directory, ) with patch("subprocess.run") as mock_run: mock_run.return_value = MagicMock( stdout=json.dumps( { "generalDiagnostics": [], "summary": {"filesAnalyzed": 1}, } ), stderr="", ) result = check_directory(str(tmp_path)) assert result["total_errors"] == 0 assert result["total_files"] == 1 def test_check_directory_with_errors(tmp_path): """check_directory reports errors from pyright output.""" py_file = tmp_path / "bad.py" py_file.write_text("x: int = 'not_int'\n", encoding="utf-8") from aipass.seedgo.apps.handlers.diagnostics.diagnostics_check import ( check_directory, ) with patch("subprocess.run") as mock_run: mock_run.return_value = MagicMock( stdout=json.dumps( { "generalDiagnostics": [ { "file": str(py_file), "severity": "error", "range": {"start": {"line": 0}}, "message": "Type mismatch", "rule": "reportAssignment", } ], "summary": {"filesAnalyzed": 1}, } ), stderr="", ) result = check_directory(str(tmp_path)) assert result["total_errors"] == 1 assert result["files_with_errors"] == 1 # =========================================================================== # 3. json_handler -- increment_counter # =========================================================================== def _get_real_json_handler(tmp_path, monkeypatch): """Import the real json_handler module with a tmp_path JSON directory.""" import importlib import sys # Remove ALL json_handler mocks/caches so a fresh import is forced keys_to_remove = [k for k in sys.modules if "json_handler" in k or "handlers.json" in k] for key in keys_to_remove: sys.modules.pop(key, None) # Ensure prax logger mock is in place for the real module prax = MagicMock() sys.modules["aipass.prax"] = prax # Fresh import of the real module jh_mod = importlib.import_module("aipass.seedgo.apps.handlers.json.json_handler") monkeypatch.setattr(jh_mod, "_BRANCH_ROOT", tmp_path) monkeypatch.setattr(jh_mod, "_BRANCH_NAME", "test") monkeypatch.setattr(jh_mod, "JSON_DIR", tmp_path / "test_json") return jh_mod def test_increment_counter(tmp_path, monkeypatch): """increment_counter increments a named counter in data JSON.""" jh = _get_real_json_handler(tmp_path, monkeypatch) result = jh.increment_counter("testmod", "runs", 1) assert result is True # Verify counter was set data = jh.load_json("testmod", "data") assert data is not None assert data["runs"] == 1 # Increment again jh.increment_counter("testmod", "runs", 5) data = jh.load_json("testmod", "data") assert data is not None assert data["runs"] == 6 def test_increment_counter_new_counter(tmp_path, monkeypatch): """increment_counter creates a new counter if it does not exist.""" jh = _get_real_json_handler(tmp_path, monkeypatch) result = jh.increment_counter("testmod", "new_counter", 10) assert result is True data = jh.load_json("testmod", "data") assert data is not None assert data["new_counter"] == 10 # =========================================================================== # 4. json_handler -- update_data_metrics # =========================================================================== def test_update_data_metrics(tmp_path, monkeypatch): """update_data_metrics sets arbitrary metrics in data JSON.""" jh = _get_real_json_handler(tmp_path, monkeypatch) result = jh.update_data_metrics("testmod", score=95, status="ok") assert result is True data = jh.load_json("testmod", "data") assert data is not None assert data["score"] == 95 assert data["status"] == "ok" def test_update_data_metrics_overwrites(tmp_path, monkeypatch): """update_data_metrics overwrites existing metrics.""" jh = _get_real_json_handler(tmp_path, monkeypatch) jh.update_data_metrics("testmod", score=50) jh.update_data_metrics("testmod", score=99) data = jh.load_json("testmod", "data") assert data is not None assert data["score"] == 99 # =========================================================================== # 5. readme_ops -- resolve_branch # =========================================================================== def test_resolve_branch_found(tmp_path, monkeypatch): """resolve_branch finds a branch in the registry.""" registry_data = { "branches": [ {"name": "seedgo", "path": "/src/aipass/seedgo"}, {"name": "drone", "path": "/src/aipass/drone"}, ] } registry_path = tmp_path / "AIPASS_REGISTRY.json" registry_path.write_text(json.dumps(registry_data), encoding="utf-8") from aipass.seedgo.apps.handlers.readme import readme_ops monkeypatch.setattr(readme_ops, "_find_registry", lambda: registry_path) result = readme_ops.resolve_branch("@seedgo") assert result is not None assert result["name"] == "seedgo" def test_resolve_branch_not_found(tmp_path, monkeypatch): """resolve_branch returns None for unknown branch.""" registry_data = {"branches": [{"name": "seedgo"}]} registry_path = tmp_path / "AIPASS_REGISTRY.json" registry_path.write_text(json.dumps(registry_data), encoding="utf-8") from aipass.seedgo.apps.handlers.readme import readme_ops monkeypatch.setattr(readme_ops, "_find_registry", lambda: registry_path) result = readme_ops.resolve_branch("@nonexistent") assert result is None def test_resolve_branch_no_registry(tmp_path, monkeypatch): """resolve_branch returns None when registry does not exist.""" from aipass.seedgo.apps.handlers.readme import readme_ops monkeypatch.setattr(readme_ops, "_find_registry", lambda: tmp_path / "MISSING.json") result = readme_ops.resolve_branch("@seedgo") assert result is None def test_resolve_branch_alias(tmp_path, monkeypatch): """resolve_branch resolves aliases.""" registry_data = { "branches": [ {"name": "seedgo", "aliases": ["@sg", "@standards"]}, ] } registry_path = tmp_path / "AIPASS_REGISTRY.json" registry_path.write_text(json.dumps(registry_data), encoding="utf-8") from aipass.seedgo.apps.handlers.readme import readme_ops monkeypatch.setattr(readme_ops, "_find_registry", lambda: registry_path) result = readme_ops.resolve_branch("@sg") assert result is not None assert result["name"] == "seedgo" # =========================================================================== # 6. readme_ops -- get_all_branches # =========================================================================== def test_get_all_branches(tmp_path, monkeypatch): """get_all_branches returns all branches from registry.""" registry_data = { "branches": [ {"name": "seedgo"}, {"name": "drone"}, {"name": "spawn"}, ] } registry_path = tmp_path / "AIPASS_REGISTRY.json" registry_path.write_text(json.dumps(registry_data), encoding="utf-8") from aipass.seedgo.apps.handlers.readme import readme_ops monkeypatch.setattr(readme_ops, "_find_registry", lambda: registry_path) branches = readme_ops.get_all_branches() assert len(branches) == 3 def test_get_all_branches_empty_registry(tmp_path, monkeypatch): """get_all_branches returns empty list when registry has no branches.""" registry_path = tmp_path / "AIPASS_REGISTRY.json" registry_path.write_text(json.dumps({"branches": []}), encoding="utf-8") from aipass.seedgo.apps.handlers.readme import readme_ops monkeypatch.setattr(readme_ops, "_find_registry", lambda: registry_path) branches = readme_ops.get_all_branches() assert branches == [] def test_get_all_branches_no_registry(tmp_path, monkeypatch): """get_all_branches returns empty list when registry is missing.""" from aipass.seedgo.apps.handlers.readme import readme_ops monkeypatch.setattr(readme_ops, "_find_registry", lambda: tmp_path / "MISSING.json") branches = readme_ops.get_all_branches() assert branches == [] # =========================================================================== # 7. readme_generator -- generate_commands_section # =========================================================================== def test_generate_commands_section_no_entry_point(tmp_path): """generate_commands_section returns empty when no entry point exists.""" branch_dir = tmp_path / "mybranch" apps_dir = branch_dir / "apps" apps_dir.mkdir(parents=True) from aipass.seedgo.apps.handlers.readme.readme_generator import ( generate_commands_section, ) result = generate_commands_section(str(branch_dir)) assert result == "" def test_generate_commands_section_with_help(tmp_path): """generate_commands_section parses help output.""" branch_dir = tmp_path / "mybranch" apps_dir = branch_dir / "apps" apps_dir.mkdir(parents=True) entry = apps_dir / "mybranch.py" entry.write_text("pass", encoding="utf-8") from aipass.seedgo.apps.handlers.readme.readme_generator import ( generate_commands_section, ) with patch("subprocess.run") as mock_run: mock_run.return_value = MagicMock( stdout="Commands: audit, report, check\n", stderr="", ) result = generate_commands_section(str(branch_dir)) assert "audit" in result assert "report" in result # =========================================================================== # 8. readme_generator -- generate_header_section # =========================================================================== def test_generate_header_section_with_passport(tmp_path): """generate_header_section reads passport.json and produces header.""" branch_dir = tmp_path / "mybranch" trinity_dir = branch_dir / ".trinity" trinity_dir.mkdir(parents=True) passport = { "branch_info": { "branch_name": "MYBRANCH", "path": str(branch_dir), "profile": "library", "created": "2026-01-01", "role": "Testing branch", } } (trinity_dir / "passport.json").write_text(json.dumps(passport), encoding="utf-8") from aipass.seedgo.apps.handlers.readme.readme_generator import ( generate_header_section, ) result = generate_header_section(str(branch_dir)) assert "MYBRANCH" in result assert "library" in result assert "Testing branch" in result def test_generate_header_section_no_passport(tmp_path): """generate_header_section returns empty when passport is missing.""" branch_dir = tmp_path / "mybranch" branch_dir.mkdir() from aipass.seedgo.apps.handlers.readme.readme_generator import ( generate_header_section, ) result = generate_header_section(str(branch_dir)) assert result == "" # =========================================================================== # 9. readme_generator -- update_readme_auto_sections # =========================================================================== def test_update_readme_auto_sections_dry_run(tmp_path): """update_readme_auto_sections in dry_run mode does not modify the file.""" branch_dir = tmp_path / "mybranch" branch_dir.mkdir() readme = branch_dir / "README.md" original = "# Branch\n\n*Last Updated: 2025-01-01*\n\n" readme.write_text(original, encoding="utf-8") from aipass.seedgo.apps.handlers.readme.readme_generator import ( update_readme_auto_sections, ) result = update_readme_auto_sections(str(branch_dir), dry_run=True) assert result["dry_run"] is True # File should not be modified in dry run assert readme.read_text(encoding="utf-8") == original def test_update_readme_auto_sections_no_readme(tmp_path): """update_readme_auto_sections reports error when README is missing.""" branch_dir = tmp_path / "mybranch" branch_dir.mkdir() from aipass.seedgo.apps.handlers.readme.readme_generator import ( update_readme_auto_sections, ) result = update_readme_auto_sections(str(branch_dir)) assert "README.md not found" in result["errors"] def test_update_readme_auto_sections_missing_markers(tmp_path): """update_readme_auto_sections reports missing markers.""" branch_dir = tmp_path / "mybranch" branch_dir.mkdir() readme = branch_dir / "README.md" readme.write_text("# Branch\nNo markers here.\n", encoding="utf-8") from aipass.seedgo.apps.handlers.readme.readme_generator import ( update_readme_auto_sections, ) result = update_readme_auto_sections(str(branch_dir)) # Some sections should report missing markers assert len(result["missing_markers"]) > 0 or len(result["updated"]) == 0