diff --git a/src/aipass/seedgo/tests/test_checkers_batch6.py b/src/aipass/seedgo/tests/test_checkers_batch6.py new file mode 100644 index 00000000..7026e3f4 --- /dev/null +++ b/src/aipass/seedgo/tests/test_checkers_batch6.py @@ -0,0 +1,900 @@ +"""Tests for seedgo checker sub-functions -- batch 6 (architecture, cli, cli_flags, documentation).""" + +# =================== META ==================== +# Name: test_checkers_batch6.py +# Description: Unit tests for checker sub-functions in architecture, cli, cli_flags, documentation +# Version: 1.0.0 +# Created: 2026-04-25 +# Modified: 2026-04-25 +# ============================================= + +from typing import List + +import pytest +from unittest.mock import MagicMock + + +def _lines(text: str) -> List[str]: + """Split text into lines, widening LiteralString to str for pyright.""" + return text.split("\n") + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + + +@pytest.fixture(autouse=True) +def _mock_infrastructure(monkeypatch): + """Mock heavy infrastructure imports for standards checkers.""" + 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 (used by architecture_check) ------------------------ + bypass_pkg = MagicMock() + bypass_ignore = MagicMock() + bypass_ignore.get_template_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, + ) + + # Force re-imports so checkers pick up fresh mocks + for mod_name in [ + "aipass.seedgo.apps.handlers.aipass_standards.architecture_check", + "aipass.seedgo.apps.handlers.aipass_standards.cli_check", + "aipass.seedgo.apps.handlers.aipass_standards.cli_flags_check", + "aipass.seedgo.apps.handlers.aipass_standards.documentation_check", + ]: + monkeypatch.delitem(sys.modules, mod_name, raising=False) + + +# =========================================================================== +# 1. architecture_check sub-functions +# =========================================================================== + + +# -- check_layer_location --------------------------------------------------- + + +class TestCheckLayerLocation: + """Tests for check_layer_location.""" + + def test_entry_point_passes(self): + """Entry point path is recognised as the entry-point layer.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_layer_location, + ) + + result = check_layer_location("/branch/apps/branch.py", True, False, False) + assert result["passed"] is True + assert "Entry point" in result["message"] + + def test_module_layer_passes(self): + """Module path is recognised as the module layer.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_layer_location, + ) + + result = check_layer_location("/branch/apps/modules/foo.py", False, True, False) + assert result["passed"] is True + assert "Module layer" in result["message"] + + def test_handler_layer_passes(self): + """Handler path is recognised as the handler layer.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_layer_location, + ) + + result = check_layer_location("/branch/apps/handlers/json/j.py", False, False, True) + assert result["passed"] is True + assert "Handler layer" in result["message"] + + def test_outside_3_layer_fails(self): + """Path outside the 3-layer structure fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_layer_location, + ) + + result = check_layer_location("/branch/random/foo.py", False, False, False) + assert result["passed"] is False + assert "not in standard 3-layer" in result["message"] + + +# -- check_file_size (architecture) ----------------------------------------- + + +class TestArchCheckFileSize: + """Tests for architecture_check.check_file_size.""" + + def test_small_file_passes(self): + """Under 300 lines is perfect.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_file_size, + ) + + lines: list[str] = ["x"] * 100 + result = check_file_size(lines, "small.py") + assert result["passed"] is True + assert "perfect" in result["message"] + + def test_medium_file_passes(self): + """300-500 lines is good.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_file_size, + ) + + lines: list[str] = ["x"] * 350 + result = check_file_size(lines, "medium.py") + assert result["passed"] is True + assert "good" in result["message"] + + def test_heavy_file_passes(self): + """500-700 lines is acceptable but heavy.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_file_size, + ) + + lines: list[str] = ["x"] * 600 + result = check_file_size(lines, "heavy.py") + assert result["passed"] is True + assert "getting heavy" in result["message"] + + def test_oversized_file_fails(self): + """700+ lines fails the size check.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_file_size, + ) + + lines: list[str] = ["x"] * 750 + result = check_file_size(lines, "huge.py") + assert result["passed"] is False + assert "consider splitting" in result["message"] + + +# -- check_handler_independence (architecture) -------------------------------- + + +class TestArchHandlerIndependence: + """Tests for architecture_check.check_handler_independence.""" + + def test_clean_handler_passes(self): + """Handler without parent module imports passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_handler_independence, + ) + + lines: list[str] = [ + "from aipass.prax import logger", + "from aipass.cli.apps.modules import display", + "", + "def do_work():", + " return True", + ] + result = check_handler_independence(lines, "/seedgo/apps/handlers/json/j.py") + assert result is not None + assert result["passed"] is True + + def test_parent_module_import_fails(self): + """Handler importing from its parent branch module fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_handler_independence, + ) + + lines: list[str] = [ + "from seedgo.apps.modules.audit import run_audit", + "", + "def do_work():", + " return True", + ] + result = check_handler_independence(lines, "/seedgo/apps/handlers/json/j.py") + assert result is not None + assert result["passed"] is False + assert "parent module" in result["message"] + + def test_allowed_service_imports_pass(self): + """Prax and CLI service imports are allowed in handlers.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_handler_independence, + ) + + lines: list[str] = [ + "from prax.apps.modules.logger import info", + "from cli.apps.modules.display import header", + ] + result = check_handler_independence(lines, "/seedgo/apps/handlers/json/j.py") + assert result is not None + assert result["passed"] is True + + def test_import_in_docstring_ignored(self): + """Imports inside docstrings are not flagged.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_handler_independence, + ) + + lines: list[str] = [ + '"""', + "from seedgo.apps.modules.audit import run_audit", + '"""', + "def do_work():", + " return True", + ] + result = check_handler_independence(lines, "/seedgo/apps/handlers/json/j.py") + assert result is not None + assert result["passed"] is True + + +# -- check_domain_organization ----------------------------------------------- + + +class TestCheckDomainOrganization: + """Tests for check_domain_organization.""" + + def test_domain_based_passes(self): + """Handler in a domain-named folder passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_domain_organization, + ) + + result = check_domain_organization("/branch/apps/handlers/json/json_handler.py") + assert result is not None + assert result["passed"] is True + assert "json" in result["message"] + + def test_technical_name_fails(self): + """Handler in a technical-named folder (utils) fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_domain_organization, + ) + + result = check_domain_organization("/branch/apps/handlers/utils/helper.py") + assert result is not None + assert result["passed"] is False + assert "Technical organization" in result["message"] + + def test_helpers_name_fails(self): + """Handler in a helpers/ folder fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_domain_organization, + ) + + result = check_domain_organization("/branch/apps/handlers/helpers/tool.py") + assert result is not None + assert result["passed"] is False + + def test_no_handler_domain_detected(self): + """Path ending at handlers/ with no subdirectory fails detection.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_domain_organization, + ) + + result = check_domain_organization("/branch/apps/handlers") + assert result is not None + assert result["passed"] is False + assert "Could not detect" in result["message"] + + +# -- check_template_baseline ------------------------------------------------- + + +class TestCheckTemplateBaseline: + """Tests for check_template_baseline.""" + + def test_no_branch_path_detected(self): + """Path without apps/ cannot resolve a branch.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_template_baseline, + ) + + result = check_template_baseline("/random/file.py") + assert len(result) >= 1 + assert result[0]["passed"] is False + assert "Could not detect branch path" in result[0]["message"] + + def test_missing_citizen_class(self, tmp_path): + """Branch without passport.json fails the citizen_class check.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_check import ( + check_template_baseline, + ) + + apps_dir = tmp_path / "mybranch" / "apps" + apps_dir.mkdir(parents=True) + entry = apps_dir / "mybranch.py" + entry.write_text('"""Entry."""\n', encoding="utf-8") + + result = check_template_baseline(str(entry)) + assert len(result) >= 1 + assert result[0]["passed"] is False + assert "citizen_class" in result[0]["message"] + + +# =========================================================================== +# 2. cli_check sub-functions +# =========================================================================== + + +# -- check_handler_separation ------------------------------------------------ + + +class TestCheckHandlerSeparation: + """Tests for check_handler_separation.""" + + def test_clean_handler_passes(self): + """Handler with no console output passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_handler_separation, + ) + + content = "def compute():\n return 42\n" + result = check_handler_separation(content) + assert result["passed"] is True + assert "No console output" in result["message"] + + def test_console_print_fails(self): + """Handler with console.print() fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_handler_separation, + ) + + content = "def show():\n console.print('hello')\n" + result = check_handler_separation(content) + assert result["passed"] is False + assert "console.print()" in result["message"] + + def test_bare_print_fails(self): + """Handler with bare print() fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_handler_separation, + ) + + content = "def show():\n print('hello')\n" + result = check_handler_separation(content) + assert result["passed"] is False + assert "print()" in result["message"] + + def test_cli_import_fails(self): + """Handler importing CLI services fails separation check.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_handler_separation, + ) + + content = "from aipass.cli.apps.modules.display import header\ndef show():\n pass\n" + result = check_handler_separation(content) + assert result["passed"] is False + assert "CLI services" in result["message"] + + def test_print_in_main_block_ignored(self): + """Print inside if __name__ block is allowed.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_handler_separation, + ) + + content = "def compute():\n return 42\nif __name__ == '__main__':\n print('test output')\n" + result = check_handler_separation(content) + assert result["passed"] is True + + def test_console_print_in_string_ignored(self): + """Console.print inside a string literal is not flagged.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_handler_separation, + ) + + content = "msg = 'use console.print() for output'\n" + result = check_handler_separation(content) + assert result["passed"] is True + + +# -- check_cli_imports ------------------------------------------------------- + + +class TestCheckCliImports: + """Tests for check_cli_imports.""" + + def test_has_cli_imports_passes(self): + """Module with CLI service imports passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_cli_imports, + ) + + content = "from aipass.cli.apps.modules.display import header\n" + result = check_cli_imports(content, "/seedgo/apps/modules/audit.py") + assert result is not None + assert result["passed"] is True + + def test_cli_branch_exempt(self): + """CLI branch itself is exempt from this check.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_cli_imports, + ) + + content = "from .display import header\n" + result = check_cli_imports(content, "/cli/apps/modules/something.py") + assert result is not None + assert result["passed"] is True + assert "CLI branch exempt" in result["message"] + + def test_output_without_cli_imports_fails(self): + """Module with output but no CLI imports fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_cli_imports, + ) + + content = "print('hello world')\n" + result = check_cli_imports(content, "/seedgo/apps/modules/audit.py") + assert result is not None + assert result["passed"] is False + assert "missing CLI service imports" in result["message"] + + def test_no_output_at_all_passes(self): + """Module with no output at all passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_cli_imports, + ) + + content = "def compute():\n return 42\n" + result = check_cli_imports(content, "/seedgo/apps/modules/audit.py") + assert result is not None + assert result["passed"] is True + assert "No CLI output needed" in result["message"] + + def test_shortcut_import_passes(self): + """Shortcut import via cli __init__ passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_cli_imports, + ) + + content = "from aipass.cli import header\n" + result = check_cli_imports(content, "/seedgo/apps/modules/audit.py") + assert result is not None + assert result["passed"] is True + + +# -- check_print_usage ------------------------------------------------------- + + +class TestCheckPrintUsage: + """Tests for check_print_usage.""" + + def test_console_print_passes(self): + """File using console.print passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_print_usage, + ) + + content = "console.print('hello')\n" + lines = _lines(content) + result = check_print_usage(content, lines, "/module.py") + assert result is not None + assert result["passed"] is True + + def test_bare_print_fails(self): + """Bare print() statement fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_print_usage, + ) + + content = "print('hello')\n" + lines = _lines(content) + result = check_print_usage(content, lines, "/module.py") + assert result is not None + assert result["passed"] is False + assert "print() statements" in result["message"] + + def test_parser_print_help_fails(self): + """parser.print_help() usage fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_print_usage, + ) + + content = "parser.print_help()\n" + lines = _lines(content) + result = check_print_usage(content, lines, "/module.py") + assert result is not None + assert result["passed"] is False + assert "parser.print_help()" in result["message"] + + def test_sys_stdout_write_fails(self): + """sys.stdout.write() usage fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_print_usage, + ) + + content = "import sys\nsys.stdout.write('hello')\n" + lines = _lines(content) + result = check_print_usage(content, lines, "/module.py") + assert result is not None + assert result["passed"] is False + assert "sys.stdout" in result["message"] + + def test_print_in_main_block_ignored(self): + """Print inside if __name__ block returns None (no violation).""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_print_usage, + ) + + content = "if __name__ == '__main__':\n print('test')\n" + lines = _lines(content) + result = check_print_usage(content, lines, "/module.py") + assert result is None + + def test_no_output_returns_none(self): + """File with no output returns None.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_print_usage, + ) + + content = "x = 42\n" + lines = _lines(content) + result = check_print_usage(content, lines, "/module.py") + assert result is None + + def test_bypass_rule_skips_line(self): + """Bypassed print line is not flagged.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_print_usage, + ) + + content = "print('hello')\n" + lines = _lines(content) + bypass = [{"standard": "cli", "file": "module.py", "lines": [1]}] + result = check_print_usage(content, lines, "/module.py", bypass_rules=bypass) + assert result is None + + +# -- check_help_flag --------------------------------------------------------- + + +class TestCheckHelpFlag: + """Tests for check_help_flag.""" + + def test_argparse_with_help_passes(self): + """Module with argparse and help/h flags passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_help_flag, + ) + + content = "import argparse\nparser = argparse.ArgumentParser()\n--help\n-h\n" + result = check_help_flag(content) + assert result is not None + assert result["passed"] is True + + def test_print_help_function_passes(self): + """Module with def print_help passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_help_flag, + ) + + content = "def print_help():\n pass\n" + result = check_help_flag(content) + assert result is not None + assert result["passed"] is True + + def test_executable_without_help_fails(self): + """Executable module without --help fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_help_flag, + ) + + content = "if __name__ == '__main__':\n main()\n" + result = check_help_flag(content) + assert result is not None + assert result["passed"] is False + assert "--help flag not implemented" in result["message"] + + def test_non_executable_returns_none(self): + """Non-executable module returns None (not applicable).""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_help_flag, + ) + + content = "def compute():\n return 42\n" + result = check_help_flag(content) + assert result is None + + +# -- check_duplicate_display_functions ---------------------------------------- + + +class TestCheckDuplicateDisplayFunctions: + """Tests for check_duplicate_display_functions.""" + + def test_no_duplicates_passes(self): + """Module without CLI display function duplicates passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_duplicate_display_functions, + ) + + content = "def compute():\n return 42\n" + result = check_duplicate_display_functions(content, "/seedgo/apps/modules/audit.py") + assert result is not None + assert result["passed"] is True + + def test_duplicate_header_fails(self): + """Module defining its own header() fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_duplicate_display_functions, + ) + + content = "def header(title):\n print(title)\n" + result = check_duplicate_display_functions(content, "/seedgo/apps/modules/audit.py") + assert result is not None + assert result["passed"] is False + assert "header" in result["message"] + + def test_duplicate_error_and_warning_fails(self): + """Module defining error() and warning() fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_duplicate_display_functions, + ) + + content = "def error(msg):\n pass\ndef warning(msg):\n pass\n" + result = check_duplicate_display_functions(content, "/seedgo/apps/modules/audit.py") + assert result is not None + assert result["passed"] is False + assert "error" in result["message"] + + def test_cli_branch_exempt(self): + """CLI branch is exempt (it defines these functions).""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_duplicate_display_functions, + ) + + content = "def header(title):\n print(title)\n" + result = check_duplicate_display_functions(content, "/cli/apps/modules/display.py") + assert result is None + + def test_prax_logger_exempt(self): + """Prax logger module is exempt (it is the logging system).""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_check import ( + check_duplicate_display_functions, + ) + + content = "def error(msg):\n pass\n" + result = check_duplicate_display_functions(content, "/prax/apps/modules/logger/log.py") + assert result is None + + +# =========================================================================== +# 3. cli_flags_check sub-functions +# =========================================================================== + + +# -- check_version_flag ------------------------------------------------------ + + +class TestCheckVersionFlag: + """Tests for check_version_flag.""" + + def test_version_flag_found_passes(self): + """Entry point with '--version' string passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_flags_check import ( + check_version_flag, + ) + + lines: list[str] = [ + "def main():", + " if '--version' in args:", + " print(VERSION)", + ] + result = check_version_flag(lines, "/branch/apps/branch.py", None) + assert result["passed"] is True + assert "version flag" in result["message"].lower() + + def test_short_version_flag_passes(self): + """Entry point with '-V' string passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_flags_check import ( + check_version_flag, + ) + + lines: list[str] = [ + "def main():", + " if '-V' in args:", + " print(VERSION)", + ] + result = check_version_flag(lines, "/branch/apps/branch.py", None) + assert result["passed"] is True + + def test_no_version_flag_fails(self): + """Entry point without any version flag handling fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_flags_check import ( + check_version_flag, + ) + + lines: list[str] = [ + "def main():", + " print('hello')", + ] + result = check_version_flag(lines, "/branch/apps/branch.py", None) + assert result["passed"] is False + assert "missing --version" in result["message"].lower() + + def test_version_in_docstring_ignored(self): + """Version flag mentioned only in a docstring does not count.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_flags_check import ( + check_version_flag, + ) + + lines: list[str] = [ + '"""', + "Supports --version flag", + '"""', + "def main():", + " pass", + ] + result = check_version_flag(lines, "/branch/apps/branch.py", None) + assert result["passed"] is False + + def test_bypassed_passes(self): + """Bypassed entry point passes regardless.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_flags_check import ( + check_version_flag, + ) + + lines: list[str] = ["def main():", " pass"] + bypass = [{"standard": "cli_flags", "file": "branch.py"}] + result = check_version_flag(lines, "/branch/apps/branch.py", bypass) + assert result["passed"] is True + assert "Bypassed" in result["message"] + + +# =========================================================================== +# 4. documentation_check sub-functions +# =========================================================================== + + +# -- check_module_docstring -------------------------------------------------- + + +class TestCheckModuleDocstring: + """Tests for check_module_docstring.""" + + def test_docstring_present_passes(self): + """File with a module-level docstring passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.documentation_check import ( + check_module_docstring, + ) + + lines: list[str] = [ + "# META block", + "", + '"""Module docstring."""', + "", + "def foo():", + ] + result = check_module_docstring(lines) + assert result["passed"] is True + + def test_docstring_missing_fails(self): + """File without a docstring in the first 30 lines fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.documentation_check import ( + check_module_docstring, + ) + + lines: list[str] = [ + "# Just a comment", + "import os", + "import sys", + "def foo():", + " pass", + ] + ["# more code"] * 30 + result = check_module_docstring(lines) + assert result["passed"] is False + assert "Missing module-level docstring" in result["message"] + + def test_single_quote_docstring_passes(self): + """Single-quote triple-quote docstring is accepted.""" + from aipass.seedgo.apps.handlers.aipass_standards.documentation_check import ( + check_module_docstring, + ) + + lines: list[str] = ["'''Module docstring.'''", "", "def foo():"] + result = check_module_docstring(lines) + assert result["passed"] is True + + def test_docstring_after_meta_block_passes(self): + """Docstring after META header block is accepted.""" + from aipass.seedgo.apps.handlers.aipass_standards.documentation_check import ( + check_module_docstring, + ) + + lines: list[str] = [ + "# ========= META =========", + "# Name: foo.py", + "# ========================", + "", + '"""', + "Multi-line docstring.", + '"""', + ] + result = check_module_docstring(lines) + assert result["passed"] is True + + +# -- check_function_docstrings ----------------------------------------------- + + +class TestCheckFunctionDocstrings: + """Tests for check_function_docstrings.""" + + def test_all_public_documented_passes(self): + """All public functions with docstrings passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.documentation_check import ( + check_function_docstrings, + ) + + content = 'def foo():\n """Does foo."""\n pass\ndef bar():\n """Does bar."""\n pass\n' + lines = _lines(content) + result = check_function_docstrings(content, lines) + assert result["passed"] is True + assert "2 public functions" in result["message"] + + def test_missing_docstring_fails(self): + """Public function without docstring fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.documentation_check import ( + check_function_docstrings, + ) + + content = "def foo():\n pass\n" + lines = _lines(content) + result = check_function_docstrings(content, lines) + assert result["passed"] is False + assert "foo" in result["message"] + + def test_private_functions_skipped(self): + """Private functions (starting with _) are not checked.""" + from aipass.seedgo.apps.handlers.aipass_standards.documentation_check import ( + check_function_docstrings, + ) + + content = "def _private_helper():\n pass\n" + lines = _lines(content) + result = check_function_docstrings(content, lines) + assert result["passed"] is True + assert "No public functions" in result["message"] + + def test_no_functions_passes(self): + """File with no functions passes vacuously.""" + from aipass.seedgo.apps.handlers.aipass_standards.documentation_check import ( + check_function_docstrings, + ) + + content = "x = 42\ny = 43\n" + lines = _lines(content) + result = check_function_docstrings(content, lines) + assert result["passed"] is True + + def test_multiline_signature_docstring_found(self): + """Docstring after a multi-line function signature is detected.""" + from aipass.seedgo.apps.handlers.aipass_standards.documentation_check import ( + check_function_docstrings, + ) + + content = ( + 'def compute(\n arg1: str,\n arg2: int,\n) -> bool:\n """Compute something."""\n return True\n' + ) + lines = _lines(content) + result = check_function_docstrings(content, lines) + assert result["passed"] is True diff --git a/src/aipass/seedgo/tests/test_checkers_batch7.py b/src/aipass/seedgo/tests/test_checkers_batch7.py new file mode 100644 index 00000000..30d81e37 --- /dev/null +++ b/src/aipass/seedgo/tests/test_checkers_batch7.py @@ -0,0 +1,1292 @@ +"""Tests for seedgo checker sub-functions -- batch 7 (encapsulation, imports, introspection, modules).""" + +# =================== META ==================== +# Name: test_checkers_batch7.py +# Description: Unit tests for checker sub-functions in encapsulation, imports, introspection, modules +# Version: 1.0.0 +# Created: 2026-04-25 +# Modified: 2026-04-25 +# ============================================= + +import ast +from typing import List + +import pytest +from unittest.mock import MagicMock + + +def _lines(text: str) -> List[str]: + """Split text into lines, widening LiteralString to str for pyright.""" + return text.split("\n") + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + + +@pytest.fixture(autouse=True) +def _mock_infrastructure(monkeypatch): + """Mock heavy infrastructure imports for standards checkers.""" + 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_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, + ) + + # Force re-imports so checkers pick up fresh mocks + for mod_name in [ + "aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check", + "aipass.seedgo.apps.handlers.aipass_standards.imports_check", + "aipass.seedgo.apps.handlers.aipass_standards.introspection_check", + "aipass.seedgo.apps.handlers.aipass_standards.modules_check", + ]: + monkeypatch.delitem(sys.modules, mod_name, raising=False) + + # Clear the handler guard cache between tests + enc_mod_name = "aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check" + enc_mod = sys.modules.get(enc_mod_name) + if enc_mod is not None and hasattr(enc_mod, "_handler_guard_cache"): + enc_mod._handler_guard_cache.clear() + + +# =========================================================================== +# 1. encapsulation_check sub-functions +# =========================================================================== + + +# -- extract_branch_from_import ---------------------------------------------- + + +class TestExtractBranchFromImport: + """Tests for extract_branch_from_import.""" + + def test_branch_dot_apps_handlers(self): + """Extract branch from 'from flow.apps.handlers...' pattern.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + extract_branch_from_import, + ) + + result = extract_branch_from_import("from flow.apps.handlers.plan.validator import X") + assert result == "flow" + + def test_aipass_dot_branch_pattern(self): + """Extract branch from 'from aipass.api.apps.handlers...' pattern.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + extract_branch_from_import, + ) + + result = extract_branch_from_import("from aipass.api.apps.handlers.openrouter import X") + assert result == "api" + + def test_local_import_returns_none(self): + """Local import without branch returns None.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + extract_branch_from_import, + ) + + result = extract_branch_from_import("from apps.handlers.json import X") + assert result is None + + def test_import_statement_form(self): + """Extract branch from 'import branch.apps.handlers...' pattern.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + extract_branch_from_import, + ) + + result = extract_branch_from_import("import seedgo.apps.handlers.json") + assert result == "seedgo" + + +# -- extract_handler_package ------------------------------------------------- + + +class TestExtractHandlerPackage: + """Tests for extract_handler_package.""" + + def test_extracts_json(self): + """Extract 'json' from handler import path.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + extract_handler_package, + ) + + result = extract_handler_package("from apps.handlers.json.json_handler import X") + assert result == "json" + + def test_extracts_dashboard(self): + """Extract 'dashboard' from handler import path.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + extract_handler_package, + ) + + result = extract_handler_package("from apps.handlers.dashboard.refresh import X") + assert result == "dashboard" + + def test_cross_branch_handler(self): + """Extract package from cross-branch handler import.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + extract_handler_package, + ) + + result = extract_handler_package("from flow.apps.handlers.plan.validator import X") + assert result == "plan" + + def test_no_handlers_returns_none(self): + """Import without apps.handlers returns None.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + extract_handler_package, + ) + + result = extract_handler_package("from apps.modules.audit import run") + assert result is None + + +# -- get_file_handler_package ------------------------------------------------ + + +class TestGetFileHandlerPackage: + """Tests for get_file_handler_package.""" + + def test_handler_file_returns_package(self): + """File in handlers/json/ returns 'json'.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + get_file_handler_package, + ) + + result = get_file_handler_package("/home/x/apps/handlers/json/json_handler.py") + assert result == "json" + + def test_non_handler_returns_none(self): + """File in modules/ returns None.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + get_file_handler_package, + ) + + result = get_file_handler_package("/home/x/apps/modules/something.py") + assert result is None + + +# -- check_handler_guard ----------------------------------------------------- + + +class TestCheckHandlerGuard: + """Tests for check_handler_guard.""" + + def test_guard_present_passes(self, tmp_path, monkeypatch): + """Branch with inspect.stack guard in handlers/__init__.py passes.""" + from aipass.seedgo.apps.handlers.aipass_standards import ( + encapsulation_check, + ) + + encapsulation_check._handler_guard_cache.clear() + + branch = tmp_path / "mybranch" + handlers_dir = branch / "apps" / "handlers" + handlers_dir.mkdir(parents=True) + init_file = handlers_dir / "__init__.py" + init_file.write_text( + "import inspect\n" + "def _guard_branch_access():\n" + " frame = inspect.stack()\n" + " raise ImportError('blocked')\n", + encoding="utf-8", + ) + + monkeypatch.setattr( + encapsulation_check, + "get_branch_from_path", + lambda fp: {"name": "mybranch", "path": str(branch)}, + ) + + result = encapsulation_check.check_handler_guard(str(handlers_dir / "json" / "handler.py")) + assert result is not None + assert result["passed"] is True + assert "guard present" in result["message"] + + def test_guard_missing_fails(self, tmp_path, monkeypatch): + """Branch without guard in handlers/__init__.py fails.""" + from aipass.seedgo.apps.handlers.aipass_standards import ( + encapsulation_check, + ) + + encapsulation_check._handler_guard_cache.clear() + + branch = tmp_path / "mybranch" + handlers_dir = branch / "apps" / "handlers" + handlers_dir.mkdir(parents=True) + init_file = handlers_dir / "__init__.py" + init_file.write_text("# empty init\n", encoding="utf-8") + + monkeypatch.setattr( + encapsulation_check, + "get_branch_from_path", + lambda fp: {"name": "mybranch", "path": str(branch)}, + ) + + result = encapsulation_check.check_handler_guard(str(handlers_dir / "json" / "handler.py")) + assert result is not None + assert result["passed"] is False + + def test_no_handlers_dir_returns_none(self, tmp_path, monkeypatch): + """Branch without handlers/ directory returns None.""" + from aipass.seedgo.apps.handlers.aipass_standards import ( + encapsulation_check, + ) + + encapsulation_check._handler_guard_cache.clear() + + branch = tmp_path / "mybranch" + branch.mkdir(parents=True) + + monkeypatch.setattr( + encapsulation_check, + "get_branch_from_path", + lambda fp: {"name": "mybranch", "path": str(branch)}, + ) + + result = encapsulation_check.check_handler_guard(str(branch / "apps" / "x.py")) + assert result is None + + +# -- check_cross_branch_imports ---------------------------------------------- + + +class TestCheckCrossBranchImports: + """Tests for check_cross_branch_imports.""" + + def test_no_cross_branch_passes(self): + """File with no cross-branch handler imports passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + check_cross_branch_imports, + ) + + lines = _lines("from apps.handlers.json import json_handler\n") + result = check_cross_branch_imports(lines, "/seedgo/apps/modules/a.py", "seedgo") + assert result["passed"] is True + + def test_cross_branch_import_fails(self): + """Importing another branch's handlers fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + check_cross_branch_imports, + ) + + lines = _lines("from flow.apps.handlers.plan.validator import X\n") + result = check_cross_branch_imports(lines, "/seedgo/apps/modules/a.py", "seedgo") + assert result["passed"] is False + assert "flow" in result["message"] + + def test_same_branch_import_passes(self): + """Importing own branch's handlers passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + check_cross_branch_imports, + ) + + lines = _lines("from seedgo.apps.handlers.json import json_handler\n") + result = check_cross_branch_imports(lines, "/seedgo/apps/modules/a.py", "seedgo") + assert result["passed"] is True + + def test_import_in_string_ignored(self): + """Handler import inside a string literal is not flagged.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + check_cross_branch_imports, + ) + + lines = _lines('"from flow.apps.handlers.plan import X"\n') + result = check_cross_branch_imports(lines, "/seedgo/apps/modules/a.py", "seedgo") + assert result["passed"] is True + + +# -- check_cross_package_imports --------------------------------------------- + + +class TestCheckCrossPackageImports: + """Tests for check_cross_package_imports.""" + + def test_same_package_passes(self): + """Importing from same handler package passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + check_cross_package_imports, + ) + + lines = _lines("from apps.handlers.json.utils import helper\n") + result = check_cross_package_imports(lines, "/branch/apps/handlers/json/handler.py", "json") + assert result["passed"] is True + + def test_cross_package_fails(self): + """Importing from a different handler package fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + check_cross_package_imports, + ) + + lines = _lines("from apps.handlers.error.error_handler import X\n") + result = check_cross_package_imports( + lines, + "/branch/apps/handlers/audit/checker.py", + "audit", + ) + assert result["passed"] is False + assert "error" in result["message"] + + def test_allowed_json_handler_passes(self): + """Importing json_handler (default allowed handler) passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + check_cross_package_imports, + ) + + lines = _lines("from apps.handlers.json.json_handler import log_op\n") + result = check_cross_package_imports( + lines, + "/branch/apps/handlers/audit/checker.py", + "audit", + ) + assert result["passed"] is True + + def test_relative_import_passes(self): + """Relative imports within same package pass.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + check_cross_package_imports, + ) + + lines = _lines("from .utils import helper\n") + result = check_cross_package_imports(lines, "/branch/apps/handlers/json/handler.py", "json") + assert result["passed"] is True + + +# -- check_direct_handler_imports -------------------------------------------- + + +class TestCheckDirectHandlerImports: + """Tests for check_direct_handler_imports.""" + + def test_no_handler_import_passes(self): + """Entry point without handler imports passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + check_direct_handler_imports, + ) + + lines = _lines("from apps.modules.audit import run_audit\n") + result = check_direct_handler_imports(lines, "/branch/apps/branch.py") + assert result["passed"] is True + + def test_direct_handler_import_fails(self): + """Entry point importing handlers directly fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + check_direct_handler_imports, + ) + + lines = _lines("from apps.handlers.openrouter.client import get_response\n") + result = check_direct_handler_imports(lines, "/branch/apps/branch.py") + assert result["passed"] is False + assert "Handler imported directly" in result["message"] + + def test_allowed_json_handler_passes(self): + """Default handlers (json_handler) are allowed from entry points.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_check import ( + check_direct_handler_imports, + ) + + lines = _lines("from apps.handlers.json.json_handler import log_op\n") + result = check_direct_handler_imports(lines, "/branch/apps/branch.py") + assert result["passed"] is True + + +# =========================================================================== +# 2. imports_check sub-functions +# =========================================================================== + + +# -- filter_docstrings ------------------------------------------------------- + + +class TestFilterDocstrings: + """Tests for filter_docstrings.""" + + def test_removes_multiline_docstring(self): + """Multi-line docstring lines are removed.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + filter_docstrings, + ) + + lines = _lines('"""\nThis is a docstring.\n"""\nimport os\n') + result = filter_docstrings(lines) + assert any("import os" in ln for ln in result) + assert not any("docstring" in ln for ln in result) + + def test_removes_single_line_docstring(self): + """Single-line docstring is removed.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + filter_docstrings, + ) + + lines = _lines('"""Module docstring."""\nimport os\n') + result = filter_docstrings(lines) + assert any("import os" in ln for ln in result) + assert not any("Module docstring" in ln for ln in result) + + def test_preserves_code(self): + """Non-docstring lines are preserved.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + filter_docstrings, + ) + + lines = _lines("import os\nimport sys\n") + result = filter_docstrings(lines) + assert len([ln for ln in result if ln.strip()]) == 2 + + +# -- find_import_section_end ------------------------------------------------- + + +class TestFindImportSectionEnd: + """Tests for find_import_section_end.""" + + def test_finds_def(self): + """Stops at first def statement.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + find_import_section_end, + ) + + lines = _lines("import os\nimport sys\n\ndef main():\n pass\n") + result = find_import_section_end(lines) + assert result == 3 # index of 'def main():' + + def test_finds_class(self): + """Stops at first class statement.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + find_import_section_end, + ) + + lines = _lines("import os\n\nclass Foo:\n pass\n") + result = find_import_section_end(lines) + assert result == 2 # index of 'class Foo:' + + def test_no_def_returns_length(self): + """File with no def/class returns length of lines.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + find_import_section_end, + ) + + lines = _lines("import os\nimport sys\nx = 42\n") + result = find_import_section_end(lines) + assert result == len(lines) + + +# -- check_no_aipass_root ---------------------------------------------------- + + +class TestCheckNoAipassRoot: + """Tests for check_no_aipass_root.""" + + def test_clean_file_passes(self): + """File without AIPASS_ROOT passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_no_aipass_root, + ) + + lines = _lines("import os\nfrom aipass.prax import logger\n") + result = check_no_aipass_root(lines, "/test.py") + assert result["passed"] is True + + def test_aipass_root_usage_fails(self): + """File using AIPASS_ROOT fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_no_aipass_root, + ) + + lines = _lines("root = AIPASS_ROOT / 'config'\n") + result = check_no_aipass_root(lines, "/test.py") + assert result["passed"] is False + assert "AIPASS_ROOT" in result["message"] + + +# -- check_no_sys_path ------------------------------------------------------ + + +class TestCheckNoSysPath: + """Tests for check_no_sys_path.""" + + def test_clean_file_passes(self): + """File without sys.path hacking passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_no_sys_path, + ) + + lines = _lines("import os\nimport sys\n") + result = check_no_sys_path(lines, "/test.py") + assert result["passed"] is True + + def test_sys_path_insert_fails(self): + """File with sys.path.insert fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_no_sys_path, + ) + + lines = _lines("import sys\nsys.path.insert(0, '/my/path')\n") + result = check_no_sys_path(lines, "/test.py") + assert result["passed"] is False + assert "sys.path" in result["message"] + + def test_sys_path_append_fails(self): + """File with sys.path.append fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_no_sys_path, + ) + + lines = _lines("import sys\nsys.path.append('/my/path')\n") + result = check_no_sys_path(lines, "/test.py") + assert result["passed"] is False + + +# -- check_prax_logger ------------------------------------------------------- + + +class TestCheckPraxLogger: + """Tests for check_prax_logger.""" + + def test_prax_import_passes(self): + """File with from aipass.prax import logger passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_prax_logger, + ) + + lines = _lines("from aipass.prax import logger\n") + result = check_prax_logger(lines, "/test.py") + assert result is not None + assert result["passed"] is True + + def test_missing_prax_import_fails(self): + """File without prax logger import fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_prax_logger, + ) + + lines = _lines("import os\nimport sys\n") + result = check_prax_logger(lines, "/test.py") + assert result is not None + assert result["passed"] is False + assert "Prax logger" in result["message"] + + +# -- check_handler_independence (imports) ------------------------------------ + + +class TestImportsHandlerIndependence: + """Tests for imports_check.check_handler_independence.""" + + def test_clean_handler_passes(self): + """Handler without parent module imports passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_handler_independence, + ) + + lines = _lines("from aipass.prax import logger\n") + result = check_handler_independence(lines, "/seedgo/apps/handlers/json/handler.py") + assert result is not None + assert result["passed"] is True + + def test_parent_module_import_fails(self): + """Handler importing from parent branch modules fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_handler_independence, + ) + + lines = _lines("from seedgo.apps.modules.audit import run\n") + result = check_handler_independence(lines, "/seedgo/apps/handlers/json/handler.py") + assert result is not None + assert result["passed"] is False + assert "parent module" in result["message"] + + def test_infrastructure_import_passes(self): + """Infrastructure imports (aipass.prax, aipass.cli) are allowed.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_handler_independence, + ) + + lines = _lines( + "from aipass.prax.apps.modules.logger import info\nfrom aipass.cli.apps.modules.display import header\n" + ) + result = check_handler_independence(lines, "/seedgo/apps/handlers/json/handler.py") + assert result is not None + assert result["passed"] is True + + +# -- check_import_order ------------------------------------------------------ + + +class TestCheckImportOrder: + """Tests for check_import_order.""" + + def test_correct_order_passes(self): + """Stdlib before aipass imports passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_import_order, + ) + + lines = _lines("import os\nimport sys\nfrom aipass.prax import logger\n") + result = check_import_order(lines, "/test.py") + assert result is not None + assert result["passed"] is True + + def test_wrong_order_fails(self): + """Aipass import before stdlib fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_import_order, + ) + + lines = _lines("from aipass.prax import logger\nimport os\n") + result = check_import_order(lines, "/test.py") + assert result is not None + assert result["passed"] is False + assert "before stdlib" in result["message"] + + def test_no_imports_returns_none(self): + """File with no imports returns None.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_import_order, + ) + + lines = _lines("x = 42\n") + result = check_import_order(lines, "/test.py") + assert result is None + + +# -- check_no_bare_imports --------------------------------------------------- + + +class TestCheckNoBareImports: + """Tests for check_no_bare_imports.""" + + def test_proper_namespace_passes(self): + """Import using aipass.* namespace passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_no_bare_imports, + ) + + lines = _lines("from aipass.seedgo.apps.handlers.json import json_handler\n") + result = check_no_bare_imports(lines, "/test.py") + assert result is not None + assert result["passed"] is True + + def test_bare_handler_import_fails(self): + """Bare 'from handlers.X' import fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_no_bare_imports, + ) + + lines = _lines("from handlers.json import json_handler\n") + result = check_no_bare_imports(lines, "/test.py") + assert result is not None + assert result["passed"] is False + assert "bare import" in result["message"] + + def test_bare_module_import_fails(self): + """Bare 'from drone.apps...' without aipass prefix fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_no_bare_imports, + ) + + lines = _lines("from drone.apps.modules.commander import route\n") + result = check_no_bare_imports(lines, "/test.py") + assert result is not None + assert result["passed"] is False + assert "missing aipass." in result["message"] + + def test_stdlib_import_passes(self): + """Standard library imports pass.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_check import ( + check_no_bare_imports, + ) + + lines = _lines("import os\nfrom pathlib import Path\n") + result = check_no_bare_imports(lines, "/test.py") + assert result is not None + assert result["passed"] is True + + +# =========================================================================== +# 3. introspection_check sub-functions +# =========================================================================== + + +# -- check_print_introspection_exists ---------------------------------------- + + +class TestCheckPrintIntrospectionExists: + """Tests for check_print_introspection_exists.""" + + def test_function_present_passes(self): + """File with def print_introspection passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_print_introspection_exists, + ) + + content = "def print_introspection():\n pass\n" + tree = ast.parse(content) + result = check_print_introspection_exists(tree, "test.py") + assert result["passed"] is True + + def test_function_missing_fails(self): + """File without print_introspection fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_print_introspection_exists, + ) + + content = "def main():\n pass\n" + tree = ast.parse(content) + result = check_print_introspection_exists(tree, "test.py") + assert result["passed"] is False + assert "Missing" in result["message"] + + +# -- check_execution_order -------------------------------------------------- + + +class TestCheckExecutionOrder: + """Tests for check_execution_order.""" + + def test_correct_order_passes(self): + """No-args check before --help check passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_execution_order, + ) + + content = ( + "def main():\n" + " if not args:\n" + " print_introspection()\n" + " if '--help' in args:\n" + " print_help()\n" + ) + tree = ast.parse(content) + result = check_execution_order(tree, content, "test.py") + assert result is not None + assert result["passed"] is True + + def test_wrong_order_fails(self): + """--help check before no-args check fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_execution_order, + ) + + content = ( + "def main():\n" + " if '--help' in args:\n" + " print_help()\n" + " if not args:\n" + " print_introspection()\n" + ) + tree = ast.parse(content) + result = check_execution_order(tree, content, "test.py") + assert result is not None + assert result["passed"] is False + + def test_no_main_skips(self): + """File without main() or __name__ block is skipped.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_execution_order, + ) + + content = "def compute():\n return 42\n" + tree = ast.parse(content) + result = check_execution_order(tree, content, "test.py") + assert result is not None + assert result["passed"] is True + assert "skipped" in result["message"].lower() + + +# -- check_module_handle_command_gate ---------------------------------------- + + +class TestCheckModuleHandleCommandGate: + """Tests for check_module_handle_command_gate.""" + + def test_gate_present_passes(self): + """handle_command with no-args gate calling print_introspection passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_module_handle_command_gate, + ) + + content = ( + "def handle_command(command, args):\n" + " if not args:\n" + " print_introspection()\n" + " return True\n" + " return False\n" + ) + tree = ast.parse(content) + result = check_module_handle_command_gate(tree, "test.py") + assert result is not None + assert result["passed"] is True + + def test_gate_missing_fails(self): + """handle_command without no-args gate fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_module_handle_command_gate, + ) + + content = "def handle_command(command, args):\n do_work(args)\n return True\n" + tree = ast.parse(content) + result = check_module_handle_command_gate(tree, "test.py") + assert result is not None + assert result["passed"] is False + + def test_no_handle_command_skips(self): + """File without handle_command is skipped.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_module_handle_command_gate, + ) + + content = "def compute():\n return 42\n" + tree = ast.parse(content) + result = check_module_handle_command_gate(tree, "test.py") + assert result is not None + assert result["passed"] is True + assert "skipped" in result["message"].lower() + + +# -- check_correct_dispatch ------------------------------------------------- + + +class TestCheckCorrectDispatch: + """Tests for check_correct_dispatch.""" + + def test_correct_dispatch_passes(self): + """No-args calls introspection and --help calls help passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_correct_dispatch, + ) + + content = ( + "def main():\n" + " if not args:\n" + " print_introspection()\n" + " if '--help' in args:\n" + " print_help()\n" + ) + tree = ast.parse(content) + result = check_correct_dispatch(tree, "test.py") + assert result is not None + assert result["passed"] is True + + def test_swapped_dispatch_fails(self): + """No-args calling print_help instead of print_introspection fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_correct_dispatch, + ) + + content = ( + "def main():\n" + " if not args:\n" + " print_help()\n" + " if '--help' in args:\n" + " print_introspection()\n" + ) + tree = ast.parse(content) + result = check_correct_dispatch(tree, "test.py") + assert result is not None + assert result["passed"] is False + + def test_no_main_returns_none(self): + """File without main function returns None.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_correct_dispatch, + ) + + content = "def compute():\n return 42\n" + tree = ast.parse(content) + result = check_correct_dispatch(tree, "test.py") + assert result is None + + +# -- check_content_references ------------------------------------------------ + + +class TestCheckContentReferences: + """Tests for check_content_references.""" + + def test_correct_references_passes(self): + """Introspection text without python3 references passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_content_references, + ) + + content = "def print_introspection():\n msg = 'Use: drone @mybranch command'\n return msg\n" + tree = ast.parse(content) + result = check_content_references(tree, "test.py") + assert result is not None + assert result["passed"] is True + + def test_python3_reference_fails(self): + """Introspection text referencing python3 fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_content_references, + ) + + content = "def print_introspection():\n msg = 'Run: python3 mybranch.py command'\n return msg\n" + tree = ast.parse(content) + result = check_content_references(tree, "test.py") + assert result is not None + assert result["passed"] is False + assert "python3" in result["message"] + + def test_no_relevant_funcs_returns_none(self): + """File without print_introspection or print_help returns None.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_content_references, + ) + + content = "def compute():\n return 42\n" + tree = ast.parse(content) + result = check_content_references(tree, "test.py") + assert result is None + + +# -- check_module_help_interception ------------------------------------------ + + +class TestCheckModuleHelpInterception: + """Tests for check_module_help_interception.""" + + def test_help_intercepted_passes(self): + """handle_command that intercepts --help passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_module_help_interception, + ) + + content = ( + "def handle_command(command, args):\n" + " if '--help' in args:\n" + " print_help()\n" + " return True\n" + " return False\n" + ) + tree = ast.parse(content) + result = check_module_help_interception(tree, "test.py") + assert result is not None + assert result["passed"] is True + + def test_help_not_intercepted_fails(self): + """handle_command without --help interception fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_module_help_interception, + ) + + content = "def handle_command(command, args):\n do_work(args)\n return True\n" + tree = ast.parse(content) + result = check_module_help_interception(tree, "test.py") + assert result is not None + assert result["passed"] is False + assert "does not intercept" in result["message"] + + def test_no_handle_command_returns_none(self): + """File without handle_command returns None.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_check import ( + check_module_help_interception, + ) + + content = "def compute():\n return 42\n" + tree = ast.parse(content) + result = check_module_help_interception(tree, "test.py") + assert result is None + + +# =========================================================================== +# 4. modules_check sub-functions +# =========================================================================== + + +# -- check_handle_command ---------------------------------------------------- + + +class TestCheckHandleCommand: + """Tests for check_handle_command.""" + + def test_correct_pattern_passes(self): + """Module with handle_command(command, args) -> bool passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_handle_command, + ) + + content = "def handle_command(command: str, args: list) -> bool:\n return True\n" + result = check_handle_command(content) + assert result is not None + assert result["passed"] is True + + def test_missing_handle_command_fails(self): + """Module without handle_command fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_handle_command, + ) + + content = "def do_work():\n return True\n" + result = check_handle_command(content) + assert result is not None + assert result["passed"] is False + assert "Missing handle_command" in result["message"] + + def test_missing_return_type_fails(self): + """handle_command without -> bool annotation fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_handle_command, + ) + + content = "def handle_command(command, args):\n return True\n" + result = check_handle_command(content) + assert result is not None + assert result["passed"] is False + assert "missing -> bool" in result["message"] + + +# -- check_file_size (modules) ----------------------------------------------- + + +class TestModulesCheckFileSize: + """Tests for modules_check.check_file_size.""" + + def test_simple_module_passes(self): + """Under 150 lines is perfect.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_file_size, + ) + + lines: list[str] = ["x"] * 100 + result = check_file_size(lines, "/branch/apps/modules/audit.py") + assert result["passed"] is True + assert "simple" in result["message"] + + def test_standard_module_passes(self): + """150-250 lines is standard.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_file_size, + ) + + lines: list[str] = ["x"] * 200 + result = check_file_size(lines, "/branch/apps/modules/audit.py") + assert result["passed"] is True + assert "standard" in result["message"] + + def test_oversized_module_fails(self): + """600+ lines fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_file_size, + ) + + lines: list[str] = ["x"] * 650 + result = check_file_size(lines, "/branch/apps/modules/audit.py") + assert result["passed"] is False + assert "too large" in result["message"] + + +# -- check_no_direct_file_ops ----------------------------------------------- + + +class TestCheckNoDirectFileOps: + """Tests for check_no_direct_file_ops.""" + + def test_clean_module_passes(self): + """Module without direct file operations passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_no_direct_file_ops, + ) + + content = "def do_work():\n return handler.load_data()\n" + lines = _lines(content) + result = check_no_direct_file_ops(content, lines) + assert result is not None + assert result["passed"] is True + + def test_open_call_fails(self): + """Module with bare open() call fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_no_direct_file_ops, + ) + + content = 'def do_work():\n f = open("data.json")\n' + lines = _lines(content) + result = check_no_direct_file_ops(content, lines) + assert result is not None + assert result["passed"] is False + assert "open" in result["message"] + + def test_json_dump_fails(self): + """Module with json.dump() call fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_no_direct_file_ops, + ) + + content = "def save():\n json.dump(data, fp)\n" + lines = _lines(content) + result = check_no_direct_file_ops(content, lines) + assert result is not None + assert result["passed"] is False + + def test_import_open_not_flagged(self): + """Import lines containing 'open' are not flagged.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_no_direct_file_ops, + ) + + content = "from pathlib import Path\nimport os\n" + lines = _lines(content) + result = check_no_direct_file_ops(content, lines) + assert result is not None + assert result["passed"] is True + + +# -- check_no_business_logic ------------------------------------------------- + + +class TestCheckNoBusinessLogic: + """Tests for check_no_business_logic.""" + + def test_clean_module_passes(self): + """Module without hardcoded data passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_no_business_logic, + ) + + content = "CONSTANT = 42\ndef do_work():\n return True\n" + lines = _lines(content) + result = check_no_business_logic(content, lines, "/module.py") + assert result is not None + assert result["passed"] is True + + def test_hardcoded_list_fails(self): + """Module-level hardcoded list fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_no_business_logic, + ) + + content = 'allowed_types = ["alpha", "beta", "gamma"]\n' + lines = _lines(content) + result = check_no_business_logic(content, lines, "/module.py") + assert result is not None + assert result["passed"] is False + assert "hardcoded" in result["message"] + + def test_all_caps_constant_passes(self): + """ALL_CAPS constant is not flagged.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_no_business_logic, + ) + + content = 'ALLOWED = ["alpha", "beta", "gamma"]\n' + lines = _lines(content) + result = check_no_business_logic(content, lines, "/module.py") + assert result is not None + assert result["passed"] is True + + def test_empty_list_passes(self): + """Empty list assignment is not flagged.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_no_business_logic, + ) + + content = "results = []\n" + lines = _lines(content) + result = check_no_business_logic(content, lines, "/module.py") + assert result is not None + assert result["passed"] is True + + +# -- check_thin_orchestration ------------------------------------------------ + + +class TestCheckThinOrchestration: + """Tests for check_thin_orchestration.""" + + def test_thin_module_passes(self): + """Module with only standard functions passes.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_thin_orchestration, + ) + + content = ( + "def handle_command(command, args):\n" + " return True\n" + "def print_help():\n" + " pass\n" + "def print_introspection():\n" + " pass\n" + ) + result = check_thin_orchestration(content, "/module.py") + assert result is not None + assert result["passed"] is True + + def test_implementation_function_fails(self): + """Module with large non-standard function fails.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_thin_orchestration, + ) + + # Build a function with > 40 lines (THIN_WRAPPER_MAX_LINES) + func_body = "\n".join(f" x{i} = {i}" for i in range(45)) + content = f"def compute_results(data):\n{func_body}\n" + result = check_thin_orchestration(content, "/module.py") + assert result is not None + assert result["passed"] is False + assert "compute_results" in result["message"] + + def test_private_helper_passes(self): + """Private helper functions (_prefixed) are allowed.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_thin_orchestration, + ) + + func_body = "\n".join(f" x{i} = {i}" for i in range(45)) + content = f"def _internal_helper():\n{func_body}\n" + result = check_thin_orchestration(content, "/module.py") + assert result is not None + assert result["passed"] is True + + def test_orchestration_prefix_passes(self): + """Functions with orchestration prefixes (handle_, show_, etc.) pass.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_thin_orchestration, + ) + + func_body = "\n".join(f" x{i} = {i}" for i in range(45)) + content = f"def handle_audit(args):\n{func_body}\n" + result = check_thin_orchestration(content, "/module.py") + assert result is not None + assert result["passed"] is True + + def test_small_function_passes(self): + """Non-standard function under 40 lines is treated as thin wrapper.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_check import ( + check_thin_orchestration, + ) + + content = "def compute_results(data):\n return data\n" + result = check_thin_orchestration(content, "/module.py") + assert result is not None + assert result["passed"] is True diff --git a/src/aipass/seedgo/tests/test_checkers_batch8.py b/src/aipass/seedgo/tests/test_checkers_batch8.py new file mode 100644 index 00000000..c2794b63 --- /dev/null +++ b/src/aipass/seedgo/tests/test_checkers_batch8.py @@ -0,0 +1,963 @@ +"""Tests for seedgo checker handlers — batch 8 (7 checkers).""" + +# =================== META ==================== +# Name: test_checkers_batch8.py +# Description: Unit tests for handlers, log_handler, log_level, log_structure, meta, naming, permission_flags +# Version: 1.0.0 +# Created: 2026-04-25 +# Modified: 2026-04-25 +# ============================================= + +import pytest +from typing import List +from unittest.mock import MagicMock + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _lines(text: str) -> List[str]: + """Split text into lines, widening LiteralString to str for pyright.""" + return text.split("\n") + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + + +@pytest.fixture(autouse=True) +def _mock_infrastructure(monkeypatch): + """Mock heavy infrastructure imports for standards checkers.""" + 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_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, + ) + + # Force re-imports so checkers pick up fresh mocks + for mod_name in [ + "aipass.seedgo.apps.handlers.aipass_standards.handlers_check", + "aipass.seedgo.apps.handlers.aipass_standards.log_handler_check", + "aipass.seedgo.apps.handlers.aipass_standards.log_level_check", + "aipass.seedgo.apps.handlers.aipass_standards.log_structure_check", + "aipass.seedgo.apps.handlers.aipass_standards.meta_check", + "aipass.seedgo.apps.handlers.aipass_standards.naming_check", + "aipass.seedgo.apps.handlers.aipass_standards.permission_flags_check", + ]: + monkeypatch.delitem(sys.modules, mod_name, raising=False) + + +# =========================================================================== +# 1. handlers_check -- check_handler_independence +# =========================================================================== + + +def test_handler_independence_clean(tmp_path): + """Handler with no cross-handler imports passes.""" + content = ( + '"""Clean handler."""\n' + "from aipass.seedgo.apps.handlers.json import json_handler\n" + "\ndef do_work():\n return True\n" + ) + handler_path = str(tmp_path / "apps" / "handlers" / "audit" / "clean.py") + + from aipass.seedgo.apps.handlers.aipass_standards.handlers_check import ( + check_handler_independence, + ) + + result = check_handler_independence(content, _lines(content), handler_path) + assert result["passed"] is True + + +def test_handler_independence_cross_import(tmp_path): + """Handler importing from another handler package fails.""" + content = ( + '"""Cross handler import."""\n' + "from aipass.seedgo.apps.handlers.error import error_handler\n" + "\ndef do_work():\n return True\n" + ) + handler_path = str(tmp_path / "apps" / "handlers" / "audit" / "cross.py") + + from aipass.seedgo.apps.handlers.aipass_standards.handlers_check import ( + check_handler_independence, + ) + + result = check_handler_independence(content, _lines(content), handler_path) + assert result["passed"] is False + assert "Cross-handler imports" in result["message"] + + +def test_handler_independence_same_package(tmp_path): + """Handler importing from same package passes.""" + content = ( + '"""Same package import."""\n' + "from aipass.seedgo.apps.handlers.audit import audit_helper\n" + "\ndef do_work():\n return True\n" + ) + handler_path = str(tmp_path / "apps" / "handlers" / "audit" / "same.py") + + from aipass.seedgo.apps.handlers.aipass_standards.handlers_check import ( + check_handler_independence, + ) + + result = check_handler_independence(content, _lines(content), handler_path) + assert result["passed"] is True + + +# =========================================================================== +# 2. handlers_check -- check_auto_detection +# =========================================================================== + + +def test_auto_detection_not_needed(): + """No module_name parameter means auto-detection is not needed (returns None).""" + content = "def do_work(file_path):\n return True\n" + + from aipass.seedgo.apps.handlers.aipass_standards.handlers_check import ( + check_auto_detection, + ) + + result = check_auto_detection(content) + assert result is None + + +def test_auto_detection_present(): + """Handler with module_name and inspect.stack() passes.""" + content = "import inspect\ndef do_work(module_name=None):\n frame = inspect.stack()\n return True\n" + + from aipass.seedgo.apps.handlers.aipass_standards.handlers_check import ( + check_auto_detection, + ) + + result = check_auto_detection(content) + assert result is not None + assert result["passed"] is True + + +def test_auto_detection_missing(): + """Handler with module_name but no inspect.stack() fails.""" + content = "def do_work(module_name=None):\n return True\n" + + from aipass.seedgo.apps.handlers.aipass_standards.handlers_check import ( + check_auto_detection, + ) + + result = check_auto_detection(content) + assert result is not None + assert result["passed"] is False + assert "missing auto-detection" in result["message"] + + +def test_auto_detection_with_get_caller(): + """Handler with _get_caller_module_name passes auto-detection.""" + content = "def _get_caller_module_name():\n pass\ndef do_work(module_name=None):\n return True\n" + + from aipass.seedgo.apps.handlers.aipass_standards.handlers_check import ( + check_auto_detection, + ) + + result = check_auto_detection(content) + assert result is not None + assert result["passed"] is True + + +# =========================================================================== +# 3. handlers_check -- check_no_orchestration +# =========================================================================== + + +def test_no_orchestration_clean(): + """Handler with no module imports passes.""" + content = '"""Clean handler."""\n\ndef do_work():\n return True\n' + + from aipass.seedgo.apps.handlers.aipass_standards.handlers_check import ( + check_no_orchestration, + ) + + result = check_no_orchestration(content, _lines(content)) + assert result is not None + assert result["passed"] is True + + +def test_no_orchestration_module_import(): + """Handler importing from apps.modules fails.""" + content = ( + '"""Bad handler."""\nfrom aipass.seedgo.apps.modules import audit_module\n\ndef do_work():\n return True\n' + ) + + from aipass.seedgo.apps.handlers.aipass_standards.handlers_check import ( + check_no_orchestration, + ) + + result = check_no_orchestration(content, _lines(content)) + assert result is not None + assert result["passed"] is False + assert "orchestration" in result["message"] + + +def test_no_orchestration_in_docstring(): + """Module import inside docstring is not flagged.""" + content = ( + '"""\nExample: from aipass.seedgo.apps.modules import audit_module\n"""\n\ndef do_work():\n return True\n' + ) + + from aipass.seedgo.apps.handlers.aipass_standards.handlers_check import ( + check_no_orchestration, + ) + + result = check_no_orchestration(content, _lines(content)) + assert result is not None + assert result["passed"] is True + + +# =========================================================================== +# 4. log_handler_check -- check_no_raw_file_handler +# =========================================================================== + + +def test_no_raw_file_handler_clean(): + """File without logging.FileHandler passes.""" + lines: List[str] = [ + '"""Clean module."""', + "import logging", + "logger = logging.getLogger(__name__)", + "", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.log_handler_check import ( + check_no_raw_file_handler, + ) + + result = check_no_raw_file_handler(lines, "/fake/path.py") + assert result["passed"] is True + + +def test_no_raw_file_handler_violation(): + """File with logging.FileHandler fails.""" + # seedgo:bypass standard=log_handler reason="test data for checker validation" + lines: List[str] = [ + '"""Bad module."""', + "import logging", + 'handler = logging.FileHandler("app.log")', + "", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.log_handler_check import ( + check_no_raw_file_handler, + ) + + result = check_no_raw_file_handler(lines, "/fake/path.py") + assert result["passed"] is False + assert "FileHandler" in result["message"] + + +# =========================================================================== +# 5. log_handler_check -- check_no_raw_stream_handler +# =========================================================================== + + +def test_no_raw_stream_handler_no_file_logging(): + """No file-based logging means stream handler check not applicable.""" + lines: List[str] = [ + "import logging", + "logger = logging.getLogger(__name__)", + "", + ] + content = "\n".join(lines) + + from aipass.seedgo.apps.handlers.aipass_standards.log_handler_check import ( + check_no_raw_stream_handler, + ) + + result = check_no_raw_stream_handler(lines, "/fake/path.py", content) + assert result["passed"] is True + assert "not applicable" in result["message"] + + +def test_no_raw_stream_handler_violation(): + """StreamHandler with file logging is a violation.""" + # seedgo:bypass standard=log_handler reason="test data for checker validation" + lines: List[str] = [ + "import logging", + 'handler = logging.FileHandler("app.log")', + "stream = logging.StreamHandler()", + "", + ] + content = "\n".join(lines) + + from aipass.seedgo.apps.handlers.aipass_standards.log_handler_check import ( + check_no_raw_stream_handler, + ) + + result = check_no_raw_stream_handler(lines, "/fake/path.py", content) + assert result["passed"] is False + assert "StreamHandler" in result["message"] + + +def test_no_raw_stream_handler_clean(): + """File logging present but no StreamHandler passes.""" + # seedgo:bypass standard=log_handler reason="test data for checker validation" + lines: List[str] = [ + "import logging", + 'handler = logging.FileHandler("app.log")', + "", + ] + content = "\n".join(lines) + + from aipass.seedgo.apps.handlers.aipass_standards.log_handler_check import ( + check_no_raw_stream_handler, + ) + + result = check_no_raw_stream_handler(lines, "/fake/path.py", content) + assert result["passed"] is True + + +# =========================================================================== +# 6. log_level_check -- check_error_not_user_input +# =========================================================================== + + +def test_error_not_user_input_clean(): + """ERROR used for system failures passes.""" + lines: List[str] = [ + '"""Module."""', + 'logger.error("System crash: %s", error)', + "", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.log_level_check import ( + check_error_not_user_input, + ) + + result = check_error_not_user_input(lines, "/fake/path.py") + assert result["passed"] is True + + +def test_error_not_user_input_violation(): + """ERROR used for user-input pattern fails.""" + # seedgo:bypass standard=log_level reason="test data for checker validation" + lines: List[str] = [ + '"""Module."""', + 'logger.error("Unknown command: %s", cmd)', + "", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.log_level_check import ( + check_error_not_user_input, + ) + + result = check_error_not_user_input(lines, "/fake/path.py") + assert result["passed"] is False + assert "user input" in result["message"] + + +def test_error_not_user_input_in_docstring(): + """ERROR pattern inside a docstring is ignored.""" + # seedgo:bypass standard=log_level reason="test data for checker validation" + lines: List[str] = [ + '"""', + 'logger.error("Unknown command: %s", cmd)', + '"""', + "pass", + "", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.log_level_check import ( + check_error_not_user_input, + ) + + result = check_error_not_user_input(lines, "/fake/path.py") + assert result["passed"] is True + + +# =========================================================================== +# 7. log_level_check -- check_command_routing_level +# =========================================================================== + + +def test_command_routing_level_no_routing(): + """File without command routing returns None.""" + content = "def do_work():\n pass\n" + + from aipass.seedgo.apps.handlers.aipass_standards.log_level_check import ( + check_command_routing_level, + ) + + result = check_command_routing_level(content, _lines(content), "/fake/path.py") + assert result is None + + +def test_command_routing_level_clean(): + """Command routing with proper WARNING level passes.""" + content = 'def route_command(cmd):\n logger.warning("Unknown command: %s", cmd)\n' + + from aipass.seedgo.apps.handlers.aipass_standards.log_level_check import ( + check_command_routing_level, + ) + + result = check_command_routing_level(content, _lines(content), "/fake/path.py") + assert result is not None + assert result["passed"] is True + + +def test_command_routing_level_violation(): + """Command routing with ERROR for user-input pattern fails.""" + # seedgo:bypass standard=log_level reason="test data for checker validation" + content = 'def route_command(cmd):\n logger.error("Unknown command: %s", cmd)\n' + + from aipass.seedgo.apps.handlers.aipass_standards.log_level_check import ( + check_command_routing_level, + ) + + result = check_command_routing_level(content, _lines(content), "/fake/path.py") + assert result is not None + assert result["passed"] is False + assert "ERROR" in result["message"] or "WARNING" in result["message"] + + +# =========================================================================== +# 8. log_structure_check -- check_branch_post +# =========================================================================== + + +def test_check_branch_post_no_logs(tmp_path): + """Branch with no logs returns empty violations.""" + branch = tmp_path / "mybranch" + branch.mkdir() + + from aipass.seedgo.apps.handlers.aipass_standards.log_structure_check import ( + check_branch_post, + ) + + violations, scores = check_branch_post(str(branch)) + assert violations == [] + assert scores == [] + + +def test_check_branch_post_with_system_logs(tmp_path): + """Branch with local logs and system logs passes.""" + branch = tmp_path / "mybranch" + branch.mkdir() + logs_dir = branch / "logs" + logs_dir.mkdir() + (logs_dir / "app.log").write_text("log line", encoding="utf-8") + + # Create repo-level registry and system_logs + registry = tmp_path / "AIPASS_REGISTRY.json" + registry.write_text("{}", encoding="utf-8") + sys_logs = tmp_path / "system_logs" + sys_logs.mkdir() + (sys_logs / "mybranch_system.log").write_text("system log", encoding="utf-8") + + from aipass.seedgo.apps.handlers.aipass_standards.log_structure_check import ( + check_branch_post, + ) + + violations, scores = check_branch_post(str(branch)) + assert 100 in scores + + +def test_check_branch_post_local_no_system(tmp_path): + """Branch with local logs but no system logs is flagged.""" + branch = tmp_path / "mybranch" + branch.mkdir() + logs_dir = branch / "logs" + logs_dir.mkdir() + (logs_dir / "app.log").write_text("log line", encoding="utf-8") + + # Create repo-level registry and system_logs, but no branch-specific system log + registry = tmp_path / "AIPASS_REGISTRY.json" + registry.write_text("{}", encoding="utf-8") + sys_logs = tmp_path / "system_logs" + sys_logs.mkdir() + + from aipass.seedgo.apps.handlers.aipass_standards.log_structure_check import ( + check_branch_post, + ) + + violations, scores = check_branch_post(str(branch)) + assert 50 in scores + assert len(violations) == 1 + assert "prax dispatch" in violations[0]["issues"][0] + + +# =========================================================================== +# 9. meta_check -- check_meta_presence +# =========================================================================== + + +def test_meta_presence_valid(): + """Content with both header and footer markers passes.""" + content = ( + "# =================== AIPass ====================\n" + "# Name: test.py\n" + "# Description: Test file\n" + "# Version: 1.0.0\n" + "# Created: 2026-01-01\n" + "# Modified: 2026-01-01\n" + "# =============================================\n" + ) + + from aipass.seedgo.apps.handlers.aipass_standards.meta_check import ( + check_meta_presence, + ) + + result = check_meta_presence(content) + assert result["passed"] is True + + +def test_meta_presence_missing_header(): + """Content without header marker fails.""" + content = "# Name: test.py\n# =============================================\n" + + from aipass.seedgo.apps.handlers.aipass_standards.meta_check import ( + check_meta_presence, + ) + + result = check_meta_presence(content) + assert result["passed"] is False + assert "header" in result["message"] + + +def test_meta_presence_legacy_header(): + """Content with legacy META header passes.""" + content = ( + "# =================== META ====================\n" + "# Name: test.py\n" + "# =============================================\n" + ) + + from aipass.seedgo.apps.handlers.aipass_standards.meta_check import ( + check_meta_presence, + ) + + result = check_meta_presence(content) + assert result["passed"] is True + + +# =========================================================================== +# 10. meta_check -- check_meta_placement +# =========================================================================== + + +def test_meta_placement_at_top(): + """META block at line 1 passes.""" + content = "# =================== AIPass ====================\nrest of file\n" + + from aipass.seedgo.apps.handlers.aipass_standards.meta_check import ( + check_meta_placement, + ) + + result = check_meta_placement(content) + assert result["passed"] is True + + +def test_meta_placement_not_at_top(): + """META block not at line 1 fails.""" + content = '"""Docstring first."""\n# =================== AIPass ====================\n' + + from aipass.seedgo.apps.handlers.aipass_standards.meta_check import ( + check_meta_placement, + ) + + result = check_meta_placement(content) + assert result["passed"] is False + assert "first line" in result["message"] + + +# =========================================================================== +# 11. meta_check -- check_required_fields +# =========================================================================== + + +def test_required_fields_all_present(): + """All required META fields present passes.""" + content = ( + "# =================== AIPass ====================\n" + "# Name: test.py\n" + "# Description: Test file\n" + "# Version: 1.0.0\n" + "# Created: 2026-01-01\n" + "# Modified: 2026-01-01\n" + "# =============================================\n" + ) + + from aipass.seedgo.apps.handlers.aipass_standards.meta_check import ( + check_required_fields, + ) + + results = check_required_fields(content, "test.py") + assert all(r["passed"] for r in results) + + +def test_required_fields_missing_version(): + """Missing Version field fails.""" + content = ( + "# =================== AIPass ====================\n" + "# Name: test.py\n" + "# Description: Test file\n" + "# Created: 2026-01-01\n" + "# Modified: 2026-01-01\n" + "# =============================================\n" + ) + + from aipass.seedgo.apps.handlers.aipass_standards.meta_check import ( + check_required_fields, + ) + + results = check_required_fields(content, "test.py") + version_result = [r for r in results if "Version" in r["name"]] + assert len(version_result) == 1 + assert version_result[0]["passed"] is False + + +def test_required_fields_wrong_name(): + """Name field not matching filename fails.""" + content = ( + "# =================== AIPass ====================\n" + "# Name: wrong_name.py\n" + "# Description: Test file\n" + "# Version: 1.0.0\n" + "# Created: 2026-01-01\n" + "# Modified: 2026-01-01\n" + "# =============================================\n" + ) + + from aipass.seedgo.apps.handlers.aipass_standards.meta_check import ( + check_required_fields, + ) + + results = check_required_fields(content, "test.py") + name_result = [r for r in results if "Name" in r["name"]] + assert len(name_result) == 1 + assert name_result[0]["passed"] is False + assert "wrong_name.py" in name_result[0]["message"] + + +# =========================================================================== +# 12. naming_check -- check_file_naming +# =========================================================================== + + +def test_file_naming_snake_case(tmp_path): + """Snake_case filename passes.""" + f = tmp_path / "good_module.py" + f.write_text("pass", encoding="utf-8") + + from aipass.seedgo.apps.handlers.aipass_standards.naming_check import ( + check_file_naming, + ) + + result = check_file_naming(str(f), f) + assert result["passed"] is True + + +def test_file_naming_bad_case(tmp_path): + """Uppercase filename fails.""" + f = tmp_path / "BadModule.py" + f.write_text("pass", encoding="utf-8") + + from aipass.seedgo.apps.handlers.aipass_standards.naming_check import ( + check_file_naming, + ) + + result = check_file_naming(str(f), f) + assert result["passed"] is False + assert "invalid characters" in result["message"] + + +def test_file_naming_redundant_prefix(tmp_path): + """Filename with redundant parent dir prefix fails.""" + parent = tmp_path / "audit" + parent.mkdir() + f = parent / "audit_ops.py" + f.write_text("pass", encoding="utf-8") + + from aipass.seedgo.apps.handlers.aipass_standards.naming_check import ( + check_file_naming, + ) + + result = check_file_naming(str(f), f) + assert result["passed"] is False + assert "redundant prefix" in result["message"] + + +def test_file_naming_init(tmp_path): + """__init__.py passes (Python-reserved).""" + f = tmp_path / "__init__.py" + f.write_text("", encoding="utf-8") + + from aipass.seedgo.apps.handlers.aipass_standards.naming_check import ( + check_file_naming, + ) + + result = check_file_naming(str(f), f) + assert result["passed"] is True + + +# =========================================================================== +# 13. naming_check -- check_function_naming +# =========================================================================== + + +def test_function_naming_all_snake(): + """All snake_case functions pass.""" + content = "def do_work():\n pass\n\ndef get_data():\n pass\n" + + from aipass.seedgo.apps.handlers.aipass_standards.naming_check import ( + check_function_naming, + ) + + result = check_function_naming(content) + assert result is not None + assert result["passed"] is True + + +def test_function_naming_camel_case(): + """CamelCase function fails.""" + content = "def DoWork():\n pass\n" + + from aipass.seedgo.apps.handlers.aipass_standards.naming_check import ( + check_function_naming, + ) + + result = check_function_naming(content) + assert result is not None + assert result["passed"] is False + assert "DoWork" in result["message"] + + +def test_function_naming_no_functions(): + """No functions returns None.""" + content = "X = 42\n" + + from aipass.seedgo.apps.handlers.aipass_standards.naming_check import ( + check_function_naming, + ) + + result = check_function_naming(content) + assert result is None + + +def test_function_naming_dunder_skipped(): + """Dunder methods are skipped from violation checks but still counted.""" + content = "class Foo:\n def __init__(self):\n pass\n" + + from aipass.seedgo.apps.handlers.aipass_standards.naming_check import ( + check_function_naming, + ) + + result = check_function_naming(content) + # __init__ is found but skipped from violation checks, so it passes + assert result is not None + assert result["passed"] is True + + +# =========================================================================== +# 14. naming_check -- check_constant_naming +# =========================================================================== + + +def test_constant_naming_upper(): + """UPPER_CASE constants pass.""" + content = 'MY_CONST = "hello"\nANOTHER = 42\n' + + from aipass.seedgo.apps.handlers.aipass_standards.naming_check import ( + check_constant_naming, + ) + + result = check_constant_naming(content) + assert result is not None + assert result["passed"] is True + + +def test_constant_naming_lowercase(): + """Lowercase constants fail.""" + content = 'my_const = "hello"\n' + + from aipass.seedgo.apps.handlers.aipass_standards.naming_check import ( + check_constant_naming, + ) + + result = check_constant_naming(content) + assert result is not None + assert result["passed"] is False + assert "my_const" in result["message"] + + +def test_constant_naming_function_call_ignored(): + """Constants assigned via function call are ignored.""" + content = "logger = logging.getLogger(__name__)\n" + + from aipass.seedgo.apps.handlers.aipass_standards.naming_check import ( + check_constant_naming, + ) + + result = check_constant_naming(content) + # Function call assignment is skipped, no constants to check + assert result is None + + +# =========================================================================== +# 15. naming_check -- check_class_naming +# =========================================================================== + + +def test_class_naming_pascal(): + """PascalCase class passes.""" + content = "class MyClass:\n pass\n" + + from aipass.seedgo.apps.handlers.aipass_standards.naming_check import ( + check_class_naming, + ) + + result = check_class_naming(content) + assert result is not None + assert result["passed"] is True + + +def test_class_naming_snake(): + """snake_case class fails.""" + content = "class my_class:\n pass\n" + + from aipass.seedgo.apps.handlers.aipass_standards.naming_check import ( + check_class_naming, + ) + + result = check_class_naming(content) + assert result is not None + assert result["passed"] is False + assert "my_class" in result["message"] + + +def test_class_naming_no_classes(): + """No classes returns None.""" + content = "def do_work():\n pass\n" + + from aipass.seedgo.apps.handlers.aipass_standards.naming_check import ( + check_class_naming, + ) + + result = check_class_naming(content) + assert result is None + + +# =========================================================================== +# 16. permission_flags_check -- check_no_dangerous_flags +# =========================================================================== + + +def test_no_dangerous_flags_clean(): + """File with only approved permission flags passes.""" + lines: List[str] = [ + '"""Clean module."""', + '"--permission-mode bypassPermissions"', + "", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.permission_flags_check import ( + check_no_dangerous_flags, + ) + + result = check_no_dangerous_flags(lines, "/fake/path.py") + assert result["passed"] is True + + +def test_no_dangerous_flags_violation(): + """File with prohibited permission bypass flag fails.""" + # seedgo:bypass standard=permission_flags reason="test data for checker validation" + lines: List[str] = [ + '"""Module."""', + 'cmd = "--dangerously-skip-permissions"', + "", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.permission_flags_check import ( + check_no_dangerous_flags, + ) + + result = check_no_dangerous_flags(lines, "/fake/path.py") + assert result["passed"] is False + assert "Dangerous" in result["message"] + + +def test_no_dangerous_flags_in_docstring(): + """Prohibited flag inside docstring is ignored.""" + # seedgo:bypass standard=permission_flags reason="test data for checker validation" + lines: List[str] = [ + '"""', + "Use --dangerously-skip-permissions for testing", + '"""', + "pass", + "", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.permission_flags_check import ( + check_no_dangerous_flags, + ) + + result = check_no_dangerous_flags(lines, "/fake/path.py") + assert result["passed"] is True + + +def test_no_dangerous_flags_skip_permissions(): + """File with --skip-permissions fails.""" + # seedgo:bypass standard=permission_flags reason="test data for checker validation" + lines: List[str] = [ + '"""Module."""', + 'cmd = "--skip-permissions"', + "", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.permission_flags_check import ( + check_no_dangerous_flags, + ) + + result = check_no_dangerous_flags(lines, "/fake/path.py") + assert result["passed"] is False + + +def test_no_dangerous_flags_bypass_rule(): + """Prohibited flag bypassed by rule passes.""" + # seedgo:bypass standard=permission_flags reason="test data for checker validation" + lines: List[str] = [ + '"""Module."""', + 'cmd = "--dangerously-skip-permissions"', + "", + ] + bypass_rules = [{"standard": "permission_flags", "file": "/fake/path.py"}] + + from aipass.seedgo.apps.handlers.aipass_standards.permission_flags_check import ( + check_no_dangerous_flags, + ) + + result = check_no_dangerous_flags(lines, "/fake/path.py", bypass_rules=bypass_rules) + assert result["passed"] is True diff --git a/src/aipass/seedgo/tests/test_checkers_batch9.py b/src/aipass/seedgo/tests/test_checkers_batch9.py new file mode 100644 index 00000000..cc9d93d1 --- /dev/null +++ b/src/aipass/seedgo/tests/test_checkers_batch9.py @@ -0,0 +1,710 @@ +"""Tests for seedgo checker handlers — batch 9 (readme_check, trigger_check).""" + +# =================== META ==================== +# Name: test_checkers_batch9.py +# Description: Unit tests for readme_check and trigger_check +# Version: 1.0.0 +# Created: 2026-04-25 +# Modified: 2026-04-25 +# ============================================= + +import pytest +from typing import List +from unittest.mock import MagicMock + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _lines(text: str) -> List[str]: + """Split text into lines, widening LiteralString to str for pyright.""" + return text.split("\n") + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + + +@pytest.fixture(autouse=True) +def _mock_infrastructure(monkeypatch): + """Mock heavy infrastructure imports for standards checkers.""" + 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_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, + ) + + # Force re-imports so checkers pick up fresh mocks + for mod_name in [ + "aipass.seedgo.apps.handlers.aipass_standards.readme_check", + "aipass.seedgo.apps.handlers.aipass_standards.trigger_check", + ]: + monkeypatch.delitem(sys.modules, mod_name, raising=False) + + +# =========================================================================== +# 1. readme_check -- check_readme_exists +# =========================================================================== + + +def test_readme_exists_present(tmp_path): + """README.md exists passes.""" + readme = tmp_path / "README.md" + readme.write_text("# Branch\n", encoding="utf-8") + + from aipass.seedgo.apps.handlers.aipass_standards.readme_check import ( + check_readme_exists, + ) + + result = check_readme_exists(readme) + assert result["passed"] is True + + +def test_readme_exists_missing(tmp_path): + """README.md missing fails.""" + readme = tmp_path / "README.md" + + from aipass.seedgo.apps.handlers.aipass_standards.readme_check import ( + check_readme_exists, + ) + + result = check_readme_exists(readme) + assert result["passed"] is False + + +# =========================================================================== +# 2. readme_check -- check_required_sections +# =========================================================================== + + +def test_required_sections_all_present(): + """README with all required section groups passes.""" + lines: List[str] = [ + "# Branch", + "", + "## Architecture", + "Some content here.", + "", + "## Commands", + "- cmd1", + "", + "## Integration Points", + "Details here.", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.readme_check import ( + check_required_sections, + ) + + result = check_required_sections(lines, "/fake/apps/entry.py") + assert result["passed"] is True + + +def test_required_sections_missing_commands(): + """README missing Commands/Usage section fails.""" + lines: List[str] = [ + "# Branch", + "", + "## Architecture", + "Some content.", + "", + "## Depends On", + "Details.", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.readme_check import ( + check_required_sections, + ) + + result = check_required_sections(lines, "/fake/apps/entry.py") + assert result["passed"] is False + assert "Commands/Usage" in result["message"] + + +def test_required_sections_alternate_names(): + """README with alternate section names (Usage, Directory Structure, Provides To) passes.""" + lines: List[str] = [ + "# Branch", + "", + "## Directory Structure", + "Tree here.", + "", + "## Usage", + "- usage1", + "", + "## Provides To", + "Other branches.", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.readme_check import ( + check_required_sections, + ) + + result = check_required_sections(lines, "/fake/apps/entry.py") + assert result["passed"] is True + + +# =========================================================================== +# 3. readme_check -- check_last_updated_freshness +# =========================================================================== + + +def test_last_updated_freshness_present(tmp_path): + """README with recent Last Updated passes.""" + from datetime import datetime + + today = datetime.now().strftime("%Y-%m-%d") + lines: List[str] = [ + "# Branch", + f"*Last Updated: {today}*", + "", + ] + branch_root = tmp_path / "mybranch" + branch_root.mkdir() + apps_dir = branch_root / "apps" + apps_dir.mkdir() + (apps_dir / "entry.py").write_text("pass", encoding="utf-8") + + from aipass.seedgo.apps.handlers.aipass_standards.readme_check import ( + check_last_updated_freshness, + ) + + result = check_last_updated_freshness(lines, branch_root, "/fake/apps/entry.py") + assert result["passed"] is True + + +def test_last_updated_freshness_missing(): + """README without Last Updated date fails.""" + lines: List[str] = [ + "# Branch", + "No date here.", + "", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.readme_check import ( + check_last_updated_freshness, + ) + + # branch_root doesn't matter since date is missing + from pathlib import Path + + result = check_last_updated_freshness(lines, Path("/nonexistent"), "/fake/apps/entry.py") + assert result["passed"] is False + assert "Last Updated" in result["message"] + + +# =========================================================================== +# 4. readme_check -- check_directory_tree +# =========================================================================== + + +def test_directory_tree_accurate(tmp_path): + """Directory tree listing valid directories passes.""" + branch_root = tmp_path / "mybranch" + apps_dir = branch_root / "apps" + modules_dir = apps_dir / "modules" + modules_dir.mkdir(parents=True) + + lines: List[str] = [ + "# Branch", + "", + "## Architecture", + "", + "```", + "mybranch/", + " apps/", + " modules/", + "```", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.readme_check import ( + check_directory_tree, + ) + + result = check_directory_tree(lines, branch_root, "/fake/apps/entry.py") + assert result["passed"] is True + + +def test_directory_tree_no_tree_section(): + """README without a tree block passes (optional).""" + lines: List[str] = [ + "# Branch", + "", + "## Other Section", + "Some content.", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.readme_check import ( + check_directory_tree, + ) + + from pathlib import Path + + result = check_directory_tree(lines, Path("/nonexistent"), "/fake/apps/entry.py") + assert result["passed"] is True + assert "optional" in result["message"].lower() or "No directory" in result["message"] + + +# =========================================================================== +# 5. readme_check -- check_module_list +# =========================================================================== + + +def test_module_list_all_mentioned(tmp_path): + """All modules in apps/modules/ mentioned in README passes.""" + branch_root = tmp_path / "mybranch" + modules_dir = branch_root / "apps" / "modules" + modules_dir.mkdir(parents=True) + (modules_dir / "audit.py").write_text("pass", encoding="utf-8") + (modules_dir / "report.py").write_text("pass", encoding="utf-8") + (modules_dir / "__init__.py").write_text("", encoding="utf-8") + + lines: List[str] = [ + "# Branch", + "This branch has audit and report modules.", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.readme_check import ( + check_module_list, + ) + + result = check_module_list(lines, branch_root, "/fake/apps/entry.py") + assert result["passed"] is True + + +def test_module_list_missing_module(tmp_path): + """Module not mentioned in README fails.""" + branch_root = tmp_path / "mybranch" + modules_dir = branch_root / "apps" / "modules" + modules_dir.mkdir(parents=True) + (modules_dir / "secret_module.py").write_text("pass", encoding="utf-8") + + lines: List[str] = [ + "# Branch", + "No modules mentioned here.", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.readme_check import ( + check_module_list, + ) + + result = check_module_list(lines, branch_root, "/fake/apps/entry.py") + assert result["passed"] is False + assert "secret_module" in result["message"] + + +def test_module_list_no_modules_dir(tmp_path): + """No apps/modules/ directory passes (skipped).""" + branch_root = tmp_path / "mybranch" + branch_root.mkdir() + + lines: List[str] = ["# Branch"] + + from aipass.seedgo.apps.handlers.aipass_standards.readme_check import ( + check_module_list, + ) + + result = check_module_list(lines, branch_root, "/fake/apps/entry.py") + assert result["passed"] is True + + +# =========================================================================== +# 6. readme_check -- check_command_list +# =========================================================================== + + +def test_command_list_present(): + """Commands section with content passes.""" + lines: List[str] = [ + "# Branch", + "", + "## Commands", + "- `audit` - Run audit", + "- `report` - Generate report", + "", + "## Other", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.readme_check import ( + check_command_list, + ) + + result = check_command_list(lines, "/fake/apps/entry.py") + assert result["passed"] is True + assert "2 content lines" in result["message"] + + +def test_command_list_empty(): + """Commands section with no content fails.""" + lines: List[str] = [ + "# Branch", + "", + "## Commands", + "", + "## Other Section", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.readme_check import ( + check_command_list, + ) + + result = check_command_list(lines, "/fake/apps/entry.py") + assert result["passed"] is False + assert "empty" in result["message"].lower() + + +def test_command_list_missing(): + """No Commands section at all fails.""" + lines: List[str] = [ + "# Branch", + "", + "## Architecture", + "Content here.", + ] + + from aipass.seedgo.apps.handlers.aipass_standards.readme_check import ( + check_command_list, + ) + + result = check_command_list(lines, "/fake/apps/entry.py") + assert result["passed"] is False + assert "No Commands" in result["message"] + + +# =========================================================================== +# 7. trigger_check -- is_handler_layer +# =========================================================================== + + +def test_is_handler_layer_true(): + """File in handlers/ directory is handler layer.""" + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + is_handler_layer, + ) + + assert is_handler_layer("/src/aipass/seedgo/apps/handlers/audit/ops.py") is True + + +def test_is_handler_layer_false(): + """File in modules/ directory is not handler layer.""" + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + is_handler_layer, + ) + + assert is_handler_layer("/src/aipass/seedgo/apps/modules/audit.py") is False + + +# =========================================================================== +# 8. trigger_check -- is_trigger_handler +# =========================================================================== + + +def test_is_trigger_handler_true(): + """File in trigger handlers/events/ is a trigger handler.""" + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + is_trigger_handler, + ) + + assert is_trigger_handler("/apps/handlers/events/trigger_on_audit.py") is True + + +def test_is_trigger_handler_false(): + """File not in trigger handlers/events/ is not a trigger handler.""" + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + is_trigger_handler, + ) + + assert is_trigger_handler("/apps/modules/audit.py") is False + + +# =========================================================================== +# 9. trigger_check -- check_no_logger_imports +# =========================================================================== + + +def test_no_logger_imports_clean(): + """Handler without prax logger imports passes.""" + content = "def handle_event(**kwargs):\n pass\n" + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_no_logger_imports, + ) + + result = check_no_logger_imports(content, lines, "/fake/handler.py") + assert result["passed"] is True + + +def test_no_logger_imports_violation(): + """Handler importing prax logger fails.""" + content = "from prax import logger\n\ndef handle_event(**kwargs):\n pass\n" + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_no_logger_imports, + ) + + result = check_no_logger_imports(content, lines, "/fake/handler.py") + assert result["passed"] is False + assert "recursion" in result["message"] + + +# =========================================================================== +# 10. trigger_check -- check_no_print_statements +# =========================================================================== + + +def test_no_print_statements_clean(): + """Handler without print statements passes.""" + content = "def handle_event(**kwargs):\n return True\n" + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_no_print_statements, + ) + + result = check_no_print_statements(content, lines, "/fake/handler.py") + assert result["passed"] is True + + +def test_no_print_statements_violation(): + """Handler with print() fails.""" + content = 'def handle_event(**kwargs):\n print("debug")\n' + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_no_print_statements, + ) + + result = check_no_print_statements(content, lines, "/fake/handler.py") + assert result["passed"] is False + assert "print()" in result["message"] + + +def test_no_print_in_main_block_ok(): + """print() inside __main__ block is allowed.""" + content = 'def handle_event(**kwargs):\n return True\n\nif __name__ == "__main__":\n print("testing")\n' + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_no_print_statements, + ) + + result = check_no_print_statements(content, lines, "/fake/handler.py") + assert result["passed"] is True + + +# =========================================================================== +# 11. trigger_check -- check_trigger_import_pattern +# =========================================================================== + + +def test_trigger_import_pattern_correct(): + """Correct trigger import pattern passes.""" + content = 'from trigger import trigger\n\ndef do_work():\n trigger.fire("event")\n' + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_trigger_import_pattern, + ) + + result = check_trigger_import_pattern(content, lines, "/fake/module.py") + assert result is not None + assert result["passed"] is True + + +def test_trigger_import_pattern_missing(): + """trigger.fire() without import fails.""" + content = 'def do_work():\n trigger.fire("event")\n' + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_trigger_import_pattern, + ) + + result = check_trigger_import_pattern(content, lines, "/fake/module.py") + assert result is not None + assert result["passed"] is False + assert "missing proper import" in result["message"] + + +def test_trigger_import_pattern_no_trigger(): + """File not using trigger returns None.""" + content = "def do_work():\n return True\n" + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_trigger_import_pattern, + ) + + result = check_trigger_import_pattern(content, lines, "/fake/module.py") + assert result is None + + +def test_trigger_import_pattern_trigger_branch(): + """Trigger branch file is exempt (self-reference).""" + content = 'def fire(event):\n trigger.fire("event")\n' + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_trigger_import_pattern, + ) + + result = check_trigger_import_pattern(content, lines, "/src/aipass/trigger/apps/modules/core.py") + assert result is not None + assert result["passed"] is True + + +# =========================================================================== +# 12. trigger_check -- check_handler_naming +# =========================================================================== + + +def test_handler_naming_correct(): + """Handler function with handle_ prefix passes.""" + content = "def handle_audit_complete(**kwargs):\n pass\n" + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_handler_naming, + ) + + result = check_handler_naming(content, lines, "/fake/handler.py") + assert result is not None + assert result["passed"] is True + + +def test_handler_naming_bad(): + """Handler function without handle_ prefix fails.""" + content = "def onHandleEvent(**kwargs):\n pass\n" + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_handler_naming, + ) + + result = check_handler_naming(content, lines, "/fake/handler.py") + assert result is not None + assert result["passed"] is False + + +def test_handler_naming_no_handlers(): + """File with no handler functions returns None.""" + content = "def do_work():\n pass\n" + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_handler_naming, + ) + + result = check_handler_naming(content, lines, "/fake/handler.py") + assert result is None + + +# =========================================================================== +# 13. trigger_check -- check_missing_trigger_events +# =========================================================================== + + +def test_missing_trigger_events_lifecycle(): + """Lifecycle function without trigger.fire() is flagged.""" + content = "def create_branch():\n pass\n" + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_missing_trigger_events, + ) + + result = check_missing_trigger_events(content, lines, "/fake/module.py") + assert result is not None + assert result["passed"] is False + assert "create_" in result["message"] + + +def test_missing_trigger_events_with_fire(): + """Lifecycle function with trigger.fire() passes (returns None).""" + content = 'def create_branch():\n trigger.fire("branch_created")\n' + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_missing_trigger_events, + ) + + result = check_missing_trigger_events(content, lines, "/fake/module.py") + assert result is None + + +def test_missing_trigger_events_no_patterns(): + """File with no event-like patterns returns None.""" + content = "def do_work():\n return True\n" + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_missing_trigger_events, + ) + + result = check_missing_trigger_events(content, lines, "/fake/module.py") + assert result is None + + +# =========================================================================== +# 14. trigger_check -- find_pattern_lines (via check_missing_trigger_events) +# =========================================================================== + + +def test_find_pattern_lines_detects_unlink(): + """Inline .unlink() without trigger.fire() is flagged.""" + content = "def cleanup():\n path.unlink()\n" + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_missing_trigger_events, + ) + + result = check_missing_trigger_events(content, lines, "/fake/module.py") + assert result is not None + assert result["passed"] is False + assert ".unlink()" in result["message"] + + +def test_find_pattern_lines_detects_rename(): + """Inline .rename() without trigger.fire() is flagged.""" + content = "def move_file():\n path.rename(new_path)\n" + lines = _lines(content) + + from aipass.seedgo.apps.handlers.aipass_standards.trigger_check import ( + check_missing_trigger_events, + ) + + result = check_missing_trigger_events(content, lines, "/fake/module.py") + assert result is not None + assert result["passed"] is False + assert ".rename()" in result["message"] diff --git a/src/aipass/seedgo/tests/test_content_functions.py b/src/aipass/seedgo/tests/test_content_functions.py new file mode 100644 index 00000000..fd66a5fe --- /dev/null +++ b/src/aipass/seedgo/tests/test_content_functions.py @@ -0,0 +1,1058 @@ +"""Tests for all 33 content functions (standards + proof).""" + +# =================== META ==================== +# Name: test_content_functions.py +# Description: Unit tests for all content handler functions +# Version: 1.0.0 +# Created: 2026-04-25 +# Modified: 2026-04-25 +# ============================================= + +import pytest +from unittest.mock import MagicMock + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + + +@pytest.fixture(autouse=True) +def _mock_infrastructure(monkeypatch): + """Mock heavy infrastructure imports for content handlers.""" + 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) + + # Force re-imports so content modules pick up fresh mocks + standards_prefix = "aipass.seedgo.apps.handlers.aipass_standards" + proof_prefix = "aipass.seedgo.apps.handlers.aipass_proof" + content_modules = [ + f"{standards_prefix}.architecture_content", + f"{standards_prefix}.cli_content", + f"{standards_prefix}.cli_flags_content", + f"{standards_prefix}.commented_logger_content", + f"{standards_prefix}.dead_code_content", + f"{standards_prefix}.debug_print_content", + f"{standards_prefix}.deep_nesting_content", + f"{standards_prefix}.documentation_content", + f"{standards_prefix}.encapsulation_content", + f"{standards_prefix}.error_handling_content", + f"{standards_prefix}.handlers_content", + f"{standards_prefix}.hardcoded_key_content", + f"{standards_prefix}.help_text_content", + f"{standards_prefix}.imports_content", + f"{standards_prefix}.introspection_content", + f"{standards_prefix}.json_structure_content", + f"{standards_prefix}.log_handler_content", + f"{standards_prefix}.log_level_content", + f"{standards_prefix}.log_structure_content", + f"{standards_prefix}.log_visibility_content", + f"{standards_prefix}.meta_content", + f"{standards_prefix}.modules_content", + f"{standards_prefix}.naming_content", + f"{standards_prefix}.permission_flags_content", + f"{standards_prefix}.readme_content", + f"{standards_prefix}.ruff_check_content", + f"{standards_prefix}.shebang_content", + f"{standards_prefix}.silent_catch_content", + f"{standards_prefix}.stderr_routing_content", + f"{standards_prefix}.test_quality_content", + f"{standards_prefix}.todo_content", + f"{standards_prefix}.trigger_content", + f"{standards_prefix}.unused_function_content", + f"{proof_prefix}.content_naming_content", + f"{proof_prefix}.interface_content", + f"{proof_prefix}.plugin_integrity_content", + f"{proof_prefix}.readme_currency_content", + f"{proof_prefix}.triplet_content", + ] + for mod_name in content_modules: + monkeypatch.delitem(sys.modules, mod_name, raising=False) + + +# =========================================================================== +# Helper +# =========================================================================== + + +def _assert_content_str(result: str, label: str) -> None: + """Validate that a content function returned a non-empty string.""" + assert isinstance(result, str), f"{label} should return str, got {type(result)}" + assert len(result) > 0, f"{label} should return non-empty string" + assert "\n" in result, f"{label} should be multi-line content" + + +# =========================================================================== +# PROOF CONTENT FUNCTIONS (5) +# =========================================================================== + + +# --------------------------------------------------------------------------- +# 1. content_naming_proof +# --------------------------------------------------------------------------- + + +def test_get_content_naming_proof_returns_str(): + """get_content_naming_proof returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_proof.content_naming_content import ( + get_content_naming_proof, + ) + + result = get_content_naming_proof() + _assert_content_str(result, "content_naming_proof") + + +def test_get_content_naming_proof_has_expected_content(): + """get_content_naming_proof mentions content naming concepts.""" + from aipass.seedgo.apps.handlers.aipass_proof.content_naming_content import ( + get_content_naming_proof, + ) + + result = get_content_naming_proof() + assert "CONTENT NAMING" in result or "content" in result.lower() + + +# --------------------------------------------------------------------------- +# 2. interface_proof +# --------------------------------------------------------------------------- + + +def test_get_interface_proof_returns_str(): + """get_interface_proof returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_proof.interface_content import ( + get_interface_proof, + ) + + result = get_interface_proof() + _assert_content_str(result, "interface_proof") + + +def test_get_interface_proof_has_expected_content(): + """get_interface_proof mentions interface/checker concepts.""" + from aipass.seedgo.apps.handlers.aipass_proof.interface_content import ( + get_interface_proof, + ) + + result = get_interface_proof() + assert "INTERFACE" in result or "interface" in result.lower() + + +# --------------------------------------------------------------------------- +# 3. plugin_integrity_proof +# --------------------------------------------------------------------------- + + +def test_get_plugin_integrity_proof_returns_str(): + """get_plugin_integrity_proof returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_proof.plugin_integrity_content import ( + get_plugin_integrity_proof, + ) + + result = get_plugin_integrity_proof() + _assert_content_str(result, "plugin_integrity_proof") + + +def test_get_plugin_integrity_proof_has_expected_content(): + """get_plugin_integrity_proof mentions plugin integrity concepts.""" + from aipass.seedgo.apps.handlers.aipass_proof.plugin_integrity_content import ( + get_plugin_integrity_proof, + ) + + result = get_plugin_integrity_proof() + assert "PLUGIN" in result or "plugin" in result.lower() + + +# --------------------------------------------------------------------------- +# 4. readme_currency_proof +# --------------------------------------------------------------------------- + + +def test_get_readme_currency_proof_returns_str(): + """get_readme_currency_proof returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_proof.readme_currency_content import ( + get_readme_currency_proof, + ) + + result = get_readme_currency_proof() + _assert_content_str(result, "readme_currency_proof") + + +def test_get_readme_currency_proof_has_expected_content(): + """get_readme_currency_proof mentions README currency concepts.""" + from aipass.seedgo.apps.handlers.aipass_proof.readme_currency_content import ( + get_readme_currency_proof, + ) + + result = get_readme_currency_proof() + assert "README" in result or "readme" in result.lower() + + +# --------------------------------------------------------------------------- +# 5. triplet_proof +# --------------------------------------------------------------------------- + + +def test_get_triplet_proof_returns_str(): + """get_triplet_proof returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_proof.triplet_content import ( + get_triplet_proof, + ) + + result = get_triplet_proof() + _assert_content_str(result, "triplet_proof") + + +def test_get_triplet_proof_has_expected_content(): + """get_triplet_proof mentions triplet/completeness concepts.""" + from aipass.seedgo.apps.handlers.aipass_proof.triplet_content import ( + get_triplet_proof, + ) + + result = get_triplet_proof() + assert "TRIPLET" in result or "triplet" in result.lower() + + +# =========================================================================== +# STANDARDS CONTENT FUNCTIONS (28) +# =========================================================================== + + +# --------------------------------------------------------------------------- +# 1. architecture_standards +# --------------------------------------------------------------------------- + + +def test_get_architecture_standards_returns_str(): + """get_architecture_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_content import ( + get_architecture_standards, + ) + + result = get_architecture_standards() + _assert_content_str(result, "architecture_standards") + + +def test_get_architecture_standards_has_expected_content(): + """get_architecture_standards mentions architecture concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.architecture_content import ( + get_architecture_standards, + ) + + result = get_architecture_standards() + assert "architecture" in result.lower() or "3-layer" in result.lower() + + +# --------------------------------------------------------------------------- +# 2. cli_standards +# --------------------------------------------------------------------------- + + +def test_get_cli_standards_returns_str(): + """get_cli_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_content import ( + get_cli_standards, + ) + + result = get_cli_standards() + _assert_content_str(result, "cli_standards") + + +def test_get_cli_standards_has_expected_content(): + """get_cli_standards mentions CLI/Rich output concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_content import ( + get_cli_standards, + ) + + result = get_cli_standards() + assert "Rich" in result or "console.print" in result + + +# --------------------------------------------------------------------------- +# 3. cli_flags_standards +# --------------------------------------------------------------------------- + + +def test_get_cli_flags_standards_returns_str(): + """get_cli_flags_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_flags_content import ( + get_cli_flags_standards, + ) + + result = get_cli_flags_standards() + _assert_content_str(result, "cli_flags_standards") + + +def test_get_cli_flags_standards_has_expected_content(): + """get_cli_flags_standards mentions CLI flag concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.cli_flags_content import ( + get_cli_flags_standards, + ) + + result = get_cli_flags_standards() + assert "--version" in result or "--help" in result or "flag" in result.lower() + + +# --------------------------------------------------------------------------- +# 4. commented_logger_standards +# --------------------------------------------------------------------------- + + +def test_get_commented_logger_standards_returns_str(): + """get_commented_logger_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.commented_logger_content import ( + get_commented_logger_standards, + ) + + result = get_commented_logger_standards() + _assert_content_str(result, "commented_logger_standards") + + +def test_get_commented_logger_standards_has_expected_content(): + """get_commented_logger_standards mentions commented logger concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.commented_logger_content import ( + get_commented_logger_standards, + ) + + result = get_commented_logger_standards() + assert "logger" in result.lower() or "comment" in result.lower() + + +# --------------------------------------------------------------------------- +# 5. dead_code_standards +# --------------------------------------------------------------------------- + + +def test_get_dead_code_standards_returns_str(): + """get_dead_code_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.dead_code_content import ( + get_dead_code_standards, + ) + + result = get_dead_code_standards() + _assert_content_str(result, "dead_code_standards") + + +def test_get_dead_code_standards_has_expected_content(): + """get_dead_code_standards mentions dead code concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.dead_code_content import ( + get_dead_code_standards, + ) + + result = get_dead_code_standards() + assert "dead" in result.lower() or "unused" in result.lower() + + +# --------------------------------------------------------------------------- +# 6. debug_print_standards +# --------------------------------------------------------------------------- + + +def test_get_debug_print_standards_returns_str(): + """get_debug_print_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.debug_print_content import ( + get_debug_print_standards, + ) + + result = get_debug_print_standards() + _assert_content_str(result, "debug_print_standards") + + +def test_get_debug_print_standards_has_expected_content(): + """get_debug_print_standards mentions debug print concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.debug_print_content import ( + get_debug_print_standards, + ) + + result = get_debug_print_standards() + assert "print" in result.lower() or "debug" in result.lower() + + +# --------------------------------------------------------------------------- +# 7. deep_nesting_standards +# --------------------------------------------------------------------------- + + +def test_get_deep_nesting_standards_returns_str(): + """get_deep_nesting_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.deep_nesting_content import ( + get_deep_nesting_standards, + ) + + result = get_deep_nesting_standards() + _assert_content_str(result, "deep_nesting_standards") + + +def test_get_deep_nesting_standards_has_expected_content(): + """get_deep_nesting_standards mentions nesting concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.deep_nesting_content import ( + get_deep_nesting_standards, + ) + + result = get_deep_nesting_standards() + assert "nesting" in result.lower() or "depth" in result.lower() + + +# --------------------------------------------------------------------------- +# 8. documentation_standards +# --------------------------------------------------------------------------- + + +def test_get_documentation_standards_returns_str(): + """get_documentation_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.documentation_content import ( + get_documentation_standards, + ) + + result = get_documentation_standards() + _assert_content_str(result, "documentation_standards") + + +def test_get_documentation_standards_has_expected_content(): + """get_documentation_standards mentions documentation concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.documentation_content import ( + get_documentation_standards, + ) + + result = get_documentation_standards() + assert "docstring" in result.lower() or "documentation" in result.lower() + + +# --------------------------------------------------------------------------- +# 9. encapsulation_standards +# --------------------------------------------------------------------------- + + +def test_get_encapsulation_standards_returns_str(): + """get_encapsulation_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_content import ( + get_encapsulation_standards, + ) + + result = get_encapsulation_standards() + _assert_content_str(result, "encapsulation_standards") + + +def test_get_encapsulation_standards_has_expected_content(): + """get_encapsulation_standards mentions encapsulation concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.encapsulation_content import ( + get_encapsulation_standards, + ) + + result = get_encapsulation_standards() + assert "encapsulation" in result.lower() or "handler" in result.lower() + + +# --------------------------------------------------------------------------- +# 10. error_handling_standards +# --------------------------------------------------------------------------- + + +def test_get_error_handling_standards_returns_str(): + """get_error_handling_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.error_handling_content import ( + get_error_handling_standards, + ) + + result = get_error_handling_standards() + _assert_content_str(result, "error_handling_standards") + + +def test_get_error_handling_standards_has_expected_content(): + """get_error_handling_standards mentions error handling concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.error_handling_content import ( + get_error_handling_standards, + ) + + result = get_error_handling_standards() + assert "error" in result.lower() or "exception" in result.lower() + + +# --------------------------------------------------------------------------- +# 11. handlers_standards +# --------------------------------------------------------------------------- + + +def test_get_handlers_standards_returns_str(): + """get_handlers_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.handlers_content import ( + get_handlers_standards, + ) + + result = get_handlers_standards() + _assert_content_str(result, "handlers_standards") + + +def test_get_handlers_standards_has_expected_content(): + """get_handlers_standards mentions handler concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.handlers_content import ( + get_handlers_standards, + ) + + result = get_handlers_standards() + assert "handler" in result.lower() + + +# --------------------------------------------------------------------------- +# 12. hardcoded_key_standards +# --------------------------------------------------------------------------- + + +def test_get_hardcoded_key_standards_returns_str(): + """get_hardcoded_key_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.hardcoded_key_content import ( + get_hardcoded_key_standards, + ) + + result = get_hardcoded_key_standards() + _assert_content_str(result, "hardcoded_key_standards") + + +def test_get_hardcoded_key_standards_has_expected_content(): + """get_hardcoded_key_standards mentions hardcoded key concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.hardcoded_key_content import ( + get_hardcoded_key_standards, + ) + + result = get_hardcoded_key_standards() + assert "hardcoded" in result.lower() or "key" in result.lower() + + +# --------------------------------------------------------------------------- +# 13. help_text_standards +# --------------------------------------------------------------------------- + + +def test_get_help_text_standards_returns_str(): + """get_help_text_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.help_text_content import ( + get_help_text_standards, + ) + + result = get_help_text_standards() + _assert_content_str(result, "help_text_standards") + + +def test_get_help_text_standards_has_expected_content(): + """get_help_text_standards mentions help text concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.help_text_content import ( + get_help_text_standards, + ) + + result = get_help_text_standards() + assert "help" in result.lower() + + +# --------------------------------------------------------------------------- +# 14. imports_standards +# --------------------------------------------------------------------------- + + +def test_get_imports_standards_returns_str(): + """get_imports_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_content import ( + get_imports_standards, + ) + + result = get_imports_standards() + _assert_content_str(result, "imports_standards") + + +def test_get_imports_standards_has_expected_content(): + """get_imports_standards mentions import concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.imports_content import ( + get_imports_standards, + ) + + result = get_imports_standards() + assert "import" in result.lower() + + +# --------------------------------------------------------------------------- +# 15. introspection_standards +# --------------------------------------------------------------------------- + + +def test_get_introspection_standards_returns_str(): + """get_introspection_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_content import ( + get_introspection_standards, + ) + + result = get_introspection_standards() + _assert_content_str(result, "introspection_standards") + + +def test_get_introspection_standards_has_expected_content(): + """get_introspection_standards mentions introspection concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.introspection_content import ( + get_introspection_standards, + ) + + result = get_introspection_standards() + assert "introspection" in result.lower() or "meta" in result.lower() + + +# --------------------------------------------------------------------------- +# 16. json_structure_standards +# --------------------------------------------------------------------------- + + +def test_get_json_structure_standards_returns_str(): + """get_json_structure_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.json_structure_content import ( + get_json_structure_standards, + ) + + result = get_json_structure_standards() + _assert_content_str(result, "json_structure_standards") + + +def test_get_json_structure_standards_has_expected_content(): + """get_json_structure_standards mentions JSON structure concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.json_structure_content import ( + get_json_structure_standards, + ) + + result = get_json_structure_standards() + assert "json" in result.lower() + + +# --------------------------------------------------------------------------- +# 17. log_handler_standards +# --------------------------------------------------------------------------- + + +def test_get_log_handler_standards_returns_str(): + """get_log_handler_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.log_handler_content import ( + get_log_handler_standards, + ) + + result = get_log_handler_standards() + _assert_content_str(result, "log_handler_standards") + + +def test_get_log_handler_standards_has_expected_content(): + """get_log_handler_standards mentions log handler concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.log_handler_content import ( + get_log_handler_standards, + ) + + result = get_log_handler_standards() + assert "log" in result.lower() or "handler" in result.lower() + + +# --------------------------------------------------------------------------- +# 18. log_level_standards +# --------------------------------------------------------------------------- + + +def test_get_log_level_standards_returns_str(): + """get_log_level_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.log_level_content import ( + get_log_level_standards, + ) + + result = get_log_level_standards() + _assert_content_str(result, "log_level_standards") + + +def test_get_log_level_standards_has_expected_content(): + """get_log_level_standards mentions log level concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.log_level_content import ( + get_log_level_standards, + ) + + result = get_log_level_standards() + assert "log" in result.lower() or "level" in result.lower() + + +# --------------------------------------------------------------------------- +# 19. log_structure_standards +# --------------------------------------------------------------------------- + + +def test_get_log_structure_standards_returns_str(): + """get_log_structure_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.log_structure_content import ( + get_log_structure_standards, + ) + + result = get_log_structure_standards() + _assert_content_str(result, "log_structure_standards") + + +def test_get_log_structure_standards_has_expected_content(): + """get_log_structure_standards mentions log structure concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.log_structure_content import ( + get_log_structure_standards, + ) + + result = get_log_structure_standards() + assert "log" in result.lower() or "structure" in result.lower() + + +# --------------------------------------------------------------------------- +# 20. log_visibility_standards +# --------------------------------------------------------------------------- + + +def test_get_log_visibility_standards_returns_str(): + """get_log_visibility_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.log_visibility_content import ( + get_log_visibility_standards, + ) + + result = get_log_visibility_standards() + _assert_content_str(result, "log_visibility_standards") + + +def test_get_log_visibility_standards_has_expected_content(): + """get_log_visibility_standards mentions log visibility concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.log_visibility_content import ( + get_log_visibility_standards, + ) + + result = get_log_visibility_standards() + assert "log" in result.lower() or "visibility" in result.lower() + + +# --------------------------------------------------------------------------- +# 21. meta_standards +# --------------------------------------------------------------------------- + + +def test_get_meta_standards_returns_str(): + """get_meta_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.meta_content import ( + get_meta_standards, + ) + + result = get_meta_standards() + _assert_content_str(result, "meta_standards") + + +def test_get_meta_standards_has_expected_content(): + """get_meta_standards mentions meta header concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.meta_content import ( + get_meta_standards, + ) + + result = get_meta_standards() + assert "meta" in result.lower() or "header" in result.lower() + + +# --------------------------------------------------------------------------- +# 22. modules_standards +# --------------------------------------------------------------------------- + + +def test_get_modules_standards_returns_str(): + """get_modules_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_content import ( + get_modules_standards, + ) + + result = get_modules_standards() + _assert_content_str(result, "modules_standards") + + +def test_get_modules_standards_has_expected_content(): + """get_modules_standards mentions module concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.modules_content import ( + get_modules_standards, + ) + + result = get_modules_standards() + assert "module" in result.lower() + + +# --------------------------------------------------------------------------- +# 23. naming_standards +# --------------------------------------------------------------------------- + + +def test_get_naming_standards_returns_str(): + """get_naming_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.naming_content import ( + get_naming_standards, + ) + + result = get_naming_standards() + _assert_content_str(result, "naming_standards") + + +def test_get_naming_standards_has_expected_content(): + """get_naming_standards mentions naming concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.naming_content import ( + get_naming_standards, + ) + + result = get_naming_standards() + assert "naming" in result.lower() or "snake_case" in result.lower() + + +# --------------------------------------------------------------------------- +# 24. permission_flags_standards +# --------------------------------------------------------------------------- + + +def test_get_permission_flags_standards_returns_str(): + """get_permission_flags_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.permission_flags_content import ( + get_permission_flags_standards, + ) + + result = get_permission_flags_standards() + _assert_content_str(result, "permission_flags_standards") + + +def test_get_permission_flags_standards_has_expected_content(): + """get_permission_flags_standards mentions permission/flag concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.permission_flags_content import ( + get_permission_flags_standards, + ) + + result = get_permission_flags_standards() + assert "permission" in result.lower() or "flag" in result.lower() + + +# --------------------------------------------------------------------------- +# 25. readme_standards +# --------------------------------------------------------------------------- + + +def test_get_readme_standards_returns_str(): + """get_readme_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.readme_content import ( + get_readme_standards, + ) + + result = get_readme_standards() + _assert_content_str(result, "readme_standards") + + +def test_get_readme_standards_has_expected_content(): + """get_readme_standards mentions README concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.readme_content import ( + get_readme_standards, + ) + + result = get_readme_standards() + assert "readme" in result.lower() + + +# --------------------------------------------------------------------------- +# 26. ruff_check_standards +# --------------------------------------------------------------------------- + + +def test_get_ruff_check_standards_returns_str(): + """get_ruff_check_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.ruff_check_content import ( + get_ruff_check_standards, + ) + + result = get_ruff_check_standards() + _assert_content_str(result, "ruff_check_standards") + + +def test_get_ruff_check_standards_has_expected_content(): + """get_ruff_check_standards mentions ruff/linting concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.ruff_check_content import ( + get_ruff_check_standards, + ) + + result = get_ruff_check_standards() + assert "ruff" in result.lower() or "lint" in result.lower() + + +# --------------------------------------------------------------------------- +# 27. shebang_standards +# --------------------------------------------------------------------------- + + +def test_get_shebang_standards_returns_str(): + """get_shebang_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.shebang_content import ( + get_shebang_standards, + ) + + result = get_shebang_standards() + _assert_content_str(result, "shebang_standards") + + +def test_get_shebang_standards_has_expected_content(): + """get_shebang_standards mentions shebang concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.shebang_content import ( + get_shebang_standards, + ) + + result = get_shebang_standards() + assert "shebang" in result.lower() or "#!" in result + + +# --------------------------------------------------------------------------- +# 28. silent_catch_standards +# --------------------------------------------------------------------------- + + +def test_get_silent_catch_standards_returns_str(): + """get_silent_catch_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.silent_catch_content import ( + get_silent_catch_standards, + ) + + result = get_silent_catch_standards() + _assert_content_str(result, "silent_catch_standards") + + +def test_get_silent_catch_standards_has_expected_content(): + """get_silent_catch_standards mentions silent catch/exception concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.silent_catch_content import ( + get_silent_catch_standards, + ) + + result = get_silent_catch_standards() + assert "silent" in result.lower() or "except" in result.lower() + + +# --------------------------------------------------------------------------- +# 29. stderr_routing_standards +# --------------------------------------------------------------------------- + + +def test_get_stderr_routing_standards_returns_str(): + """get_stderr_routing_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.stderr_routing_content import ( + get_stderr_routing_standards, + ) + + result = get_stderr_routing_standards() + _assert_content_str(result, "stderr_routing_standards") + + +def test_get_stderr_routing_standards_has_expected_content(): + """get_stderr_routing_standards mentions stderr routing concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.stderr_routing_content import ( + get_stderr_routing_standards, + ) + + result = get_stderr_routing_standards() + assert "stderr" in result.lower() + + +# --------------------------------------------------------------------------- +# 30. test_quality_standards +# --------------------------------------------------------------------------- + + +def test_get_test_quality_standards_returns_str(): + """get_test_quality_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.test_quality_content import ( + get_test_quality_standards, + ) + + result = get_test_quality_standards() + _assert_content_str(result, "test_quality_standards") + + +def test_get_test_quality_standards_has_expected_content(): + """get_test_quality_standards mentions test quality concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.test_quality_content import ( + get_test_quality_standards, + ) + + result = get_test_quality_standards() + assert "test" in result.lower() + + +# --------------------------------------------------------------------------- +# 31. todo_standards +# --------------------------------------------------------------------------- + + +def test_get_todo_standards_returns_str(): + """get_todo_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.todo_content import ( + get_todo_standards, + ) + + result = get_todo_standards() + _assert_content_str(result, "todo_standards") + + +def test_get_todo_standards_has_expected_content(): + """get_todo_standards mentions TODO concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.todo_content import ( + get_todo_standards, + ) + + result = get_todo_standards() + assert "todo" in result.lower() or "TODO" in result + + +# --------------------------------------------------------------------------- +# 32. trigger_standards +# --------------------------------------------------------------------------- + + +def test_get_trigger_standards_returns_str(): + """get_trigger_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.trigger_content import ( + get_trigger_standards, + ) + + result = get_trigger_standards() + _assert_content_str(result, "trigger_standards") + + +def test_get_trigger_standards_has_expected_content(): + """get_trigger_standards mentions trigger concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.trigger_content import ( + get_trigger_standards, + ) + + result = get_trigger_standards() + assert "trigger" in result.lower() + + +# --------------------------------------------------------------------------- +# 33. unused_function_standards +# --------------------------------------------------------------------------- + + +def test_get_unused_function_standards_returns_str(): + """get_unused_function_standards returns a non-empty multi-line string.""" + from aipass.seedgo.apps.handlers.aipass_standards.unused_function_content import ( + get_unused_function_standards, + ) + + result = get_unused_function_standards() + _assert_content_str(result, "unused_function_standards") + + +def test_get_unused_function_standards_has_expected_content(): + """get_unused_function_standards mentions unused function concepts.""" + from aipass.seedgo.apps.handlers.aipass_standards.unused_function_content import ( + get_unused_function_standards, + ) + + result = get_unused_function_standards() + assert "unused" in result.lower() or "function" in result.lower() diff --git a/src/aipass/seedgo/tests/test_handler_functions.py b/src/aipass/seedgo/tests/test_handler_functions.py new file mode 100644 index 00000000..8cb0258b --- /dev/null +++ b/src/aipass/seedgo/tests/test_handler_functions.py @@ -0,0 +1,607 @@ +"""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) + + # -- file handler (for hooks_ext) --------------------------------------- + file_handler_mod = MagicMock() + file_handler_mod.read_lines_safe = MagicMock(return_value=[]) + file_handler_mod.read_text_safe = MagicMock(return_value=None) + monkeypatch.setitem(sys.modules, "aipass.seedgo.apps.handlers.file", file_handler_mod) + + # -- hooks handler (for hooks_ext) -------------------------------------- + hooks_handler_mod = MagicMock() + hooks_handler_mod.run_pytest_file = MagicMock(return_value=(3, 0, 1.5)) + monkeypatch.setitem(sys.modules, "aipass.seedgo.apps.handlers.hooks", hooks_handler_mod) + + # -- rich.table (for hooks_ext) ----------------------------------------- + rich_table_mod = MagicMock() + monkeypatch.setitem(sys.modules, "rich.table", rich_table_mod) + monkeypatch.setitem(sys.modules, "rich", MagicMock()) + monkeypatch.setitem(sys.modules, "rich.console", MagicMock()) + + # 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", + "aipass.seedgo.apps.modules.hooks_ext", + ]: + 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 + + +# =========================================================================== +# 10. hooks_ext -- run_hooks_test +# =========================================================================== + + +def test_run_hooks_test_no_files(tmp_path): + """run_hooks_test with no test files shows warning.""" + from aipass.seedgo.apps.modules.hooks_ext import run_hooks_test + + # repo_root with no test_hooks*.py files + run_hooks_test(tmp_path) + # Should call warning() -- verify through mock + import sys + + cli_modules = sys.modules["aipass.cli.apps.modules"] + cli_modules.warning.assert_called() + + +def test_run_hooks_test_with_files(tmp_path): + """run_hooks_test with test files builds and prints table.""" + import sys + + # Create the expected directory structure + test_dir = tmp_path / "src" / "aipass" / "seedgo" / "tests" + test_dir.mkdir(parents=True) + (test_dir / "test_hooks_basic.py").write_text("pass", encoding="utf-8") + + from aipass.seedgo.apps.modules.hooks_ext import run_hooks_test + + run_hooks_test(tmp_path) + + # Verify console.print was called (table output) + cli_mod = sys.modules["aipass.cli"] + assert cli_mod.console.print.called