feat(system): test(seedgo): cover all 117 untested functions per TDPLAN-0003 — 826 tests passing, 100% function coverage
Co-Authored-By: @devpulse <devpulse@aipass>
This commit is contained in:
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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"]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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<!-- AUTO:LAST_UPDATED -->\n*Last Updated: 2025-01-01*\n<!-- /AUTO:LAST_UPDATED -->\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
|
||||
Reference in New Issue
Block a user