Merge pull request #449 from AIOSAI/system/devpulse-fix-add-handler-import-to-memory-appsinitpy-for-py

feat(system): fix: add handler import to memory apps/__init__.py for Python 3.10 mock.patch compat
This commit is contained in:
AIPass
2026-04-25 23:56:48 -07:00
committed by GitHub
12 changed files with 10607 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
from . import handlers # noqa: F401
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,596 @@
# ===================AIPASS====================
# META DATA HEADER
# Name: tests/test_search_extras.py
# Date: 2026-04-25
# Version: 1.0.0
# Category: memory/tests
# =============================================
"""Tests for search handler internals (query_executor and vector_search).
Covers:
from aipass.memory.apps.handlers.search.query_executor import encode_query_subprocess
from aipass.memory.apps.handlers.search.query_executor import search_vectors_subprocess
from aipass.memory.apps.handlers.search.vector_search import search_collection
from aipass.memory.apps.handlers.search.vector_search import encode_query
from aipass.memory.apps.handlers.search.vector_search import search_all_collections
Tests subprocess-based encoding/search, ChromaDB collection queries,
query encoding via the singleton QueryEncoder, and multi-collection search.
All tests use mocks -- no live subprocess, ML model, or ChromaDB access.
"""
import json
import subprocess
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch
# ---------------------------------------------------------------------------
# Helpers: prepare the mock graph needed to import search handlers
# ---------------------------------------------------------------------------
def _prepare_query_executor_mocks(monkeypatch):
"""Insert mocks for query_executor module-level imports.
Returns a dict of key mock objects so tests can assert against them.
"""
# Mock the chroma_subprocess and embed_subprocess script paths
mock_chroma_script = MagicMock()
mock_embed_script = MagicMock()
return {
"chroma_script": mock_chroma_script,
"embed_script": mock_embed_script,
}
def _import_query_executor(monkeypatch):
"""Prepare mocks and import (or reimport) query_executor.
Returns (module, mocks_dict).
"""
mocks = _prepare_query_executor_mocks(monkeypatch)
# Remove cached module so it gets re-imported with our mocks
sys.modules.pop("aipass.memory.apps.handlers.search.query_executor", None)
# Clear parent package attribute so Python re-executes module code
parent = sys.modules.get("aipass.memory.apps.handlers.search")
if parent is not None and hasattr(parent, "query_executor"):
delattr(parent, "query_executor")
from aipass.memory.apps.handlers.search import query_executor # noqa: E402
return query_executor, mocks
def _prepare_vector_search_mocks(monkeypatch):
"""Insert mocks for vector_search module-level imports.
Returns a dict of key mock objects for assertions.
"""
# Mock chromadb client via the chroma module
mock_client = MagicMock()
mock_chroma = MagicMock()
mock_chroma.get_client = MagicMock(return_value=mock_client)
monkeypatch.setitem(
sys.modules,
"aipass.memory.apps.handlers.storage.chroma",
mock_chroma,
)
monkeypatch.setitem(
sys.modules,
"aipass.memory.apps.handlers.storage",
MagicMock(),
)
# Mock sentence_transformers and torch for QueryEncoder
mock_model = MagicMock()
mock_model.encode.return_value = MagicMock(tolist=MagicMock(return_value=[0.1] * 384))
mock_model.to.return_value = mock_model
mock_st_cls = MagicMock(return_value=mock_model)
mock_sentence_transformers = MagicMock()
mock_sentence_transformers.SentenceTransformer = mock_st_cls
monkeypatch.setitem(sys.modules, "sentence_transformers", mock_sentence_transformers)
mock_torch = MagicMock()
mock_torch.cuda.is_available.return_value = False
monkeypatch.setitem(sys.modules, "torch", mock_torch)
return {
"client": mock_client,
"model": mock_model,
"st_cls": mock_st_cls,
"torch": mock_torch,
}
def _import_vector_search(monkeypatch):
"""Prepare mocks and import (or reimport) vector_search.
Returns (module, mocks_dict).
"""
mocks = _prepare_vector_search_mocks(monkeypatch)
# Remove cached modules so they get re-imported with our mocks
sys.modules.pop("aipass.memory.apps.handlers.search.vector_search", None)
# Clear parent package attribute so Python re-executes module code
parent = sys.modules.get("aipass.memory.apps.handlers.search")
if parent is not None and hasattr(parent, "vector_search"):
delattr(parent, "vector_search")
from aipass.memory.apps.handlers.search import vector_search # noqa: E402
# Reset singletons for a clean slate
setattr(vector_search, "_query_encoder", None)
setattr(vector_search, "_search_service", None)
setattr(vector_search, "_local_services", {})
return vector_search, mocks
# ===========================================================================
# Tests: encode_query_subprocess
# ===========================================================================
class TestEncodeQuerySubprocess:
"""Verify encode_query_subprocess subprocess-based encoding."""
def test_encode_returns_embedding_on_success(self, monkeypatch):
"""Successful subprocess returns embedding and dimension."""
mod, mocks = _import_query_executor(monkeypatch)
fake_output = json.dumps({"success": True, "embeddings": [[0.1, 0.2, 0.3]], "dimension": 3})
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = fake_output
with patch.object(subprocess, "run", return_value=mock_result) as mock_run:
result = mod.encode_query_subprocess("test query")
assert result["success"] is True
assert result["embedding"] == [0.1, 0.2, 0.3]
assert result["dimension"] == 3
mock_run.assert_called_once()
def test_encode_returns_error_on_nonzero_exit(self, monkeypatch):
"""Non-zero return code produces error dict."""
mod, mocks = _import_query_executor(monkeypatch)
mock_result = MagicMock()
mock_result.returncode = 1
mock_result.stderr = "Model not found"
with patch.object(subprocess, "run", return_value=mock_result):
result = mod.encode_query_subprocess("test query")
assert result["success"] is False
assert "Model not found" in result["error"]
def test_encode_handles_timeout(self, monkeypatch):
"""TimeoutExpired produces a timeout error."""
mod, mocks = _import_query_executor(monkeypatch)
with patch.object(subprocess, "run", side_effect=subprocess.TimeoutExpired(cmd="python", timeout=120)):
result = mod.encode_query_subprocess("slow query")
assert result["success"] is False
assert "timed out" in result["error"].lower()
def test_encode_handles_invalid_json(self, monkeypatch):
"""Invalid JSON from subprocess produces error dict."""
mod, mocks = _import_query_executor(monkeypatch)
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "not valid json"
with patch.object(subprocess, "run", return_value=mock_result):
result = mod.encode_query_subprocess("test query")
assert result["success"] is False
assert "json" in result["error"].lower()
def test_encode_handles_empty_embeddings(self, monkeypatch):
"""Response with empty embeddings list produces error."""
mod, mocks = _import_query_executor(monkeypatch)
fake_output = json.dumps({"success": True, "embeddings": [], "dimension": 384})
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = fake_output
with patch.object(subprocess, "run", return_value=mock_result):
result = mod.encode_query_subprocess("test query")
assert result["success"] is False
assert "no embedding" in result["error"].lower()
def test_encode_handles_generic_exception(self, monkeypatch):
"""Unexpected exception produces error dict."""
mod, mocks = _import_query_executor(monkeypatch)
with patch.object(subprocess, "run", side_effect=OSError("Cannot execute")):
result = mod.encode_query_subprocess("test query")
assert result["success"] is False
assert "Cannot execute" in result["error"]
# ===========================================================================
# Tests: search_vectors_subprocess
# ===========================================================================
class TestSearchVectorsSubprocess:
"""Verify search_vectors_subprocess subprocess-based search."""
def test_search_returns_results_on_success(self, monkeypatch):
"""Successful subprocess returns parsed results."""
mod, mocks = _import_query_executor(monkeypatch)
fake_output = json.dumps(
{"success": True, "results": [{"document": "hello", "distance": 0.1}], "total_results": 1}
)
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = fake_output
with patch.object(subprocess, "run", return_value=mock_result) as mock_run:
result = mod.search_vectors_subprocess(
query_embedding=[0.1, 0.2, 0.3],
branch="TEST",
n_results=5,
)
assert result["success"] is True
assert result["total_results"] == 1
mock_run.assert_called_once()
def test_search_returns_error_on_nonzero_exit(self, monkeypatch):
"""Non-zero exit code produces error dict."""
mod, mocks = _import_query_executor(monkeypatch)
mock_result = MagicMock()
mock_result.returncode = 1
mock_result.stderr = "DB not found"
with patch.object(subprocess, "run", return_value=mock_result):
result = mod.search_vectors_subprocess(query_embedding=[0.1])
assert result["success"] is False
assert "DB not found" in result["error"]
def test_search_handles_timeout(self, monkeypatch):
"""TimeoutExpired produces a timeout error."""
mod, mocks = _import_query_executor(monkeypatch)
with patch.object(subprocess, "run", side_effect=subprocess.TimeoutExpired(cmd="python", timeout=60)):
result = mod.search_vectors_subprocess(query_embedding=[0.1])
assert result["success"] is False
assert "timed out" in result["error"].lower()
def test_search_handles_invalid_json(self, monkeypatch):
"""Invalid JSON from subprocess produces error dict."""
mod, mocks = _import_query_executor(monkeypatch)
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "{{bad json"
with patch.object(subprocess, "run", return_value=mock_result):
result = mod.search_vectors_subprocess(query_embedding=[0.1])
assert result["success"] is False
assert "json" in result["error"].lower()
def test_search_passes_db_path_as_string(self, monkeypatch):
"""db_path is converted to string in the input data."""
mod, mocks = _import_query_executor(monkeypatch)
fake_output = json.dumps({"success": True, "results": []})
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = fake_output
with patch.object(subprocess, "run", return_value=mock_result) as mock_run:
mod.search_vectors_subprocess(
query_embedding=[0.1],
db_path=Path("/tmp/test_chroma"),
)
call_args = mock_run.call_args
input_data = json.loads(call_args.kwargs.get("input", call_args[1].get("input", "")))
assert input_data["db_path"] == "/tmp/test_chroma"
def test_search_handles_generic_exception(self, monkeypatch):
"""Unexpected exception produces error dict."""
mod, mocks = _import_query_executor(monkeypatch)
with patch.object(subprocess, "run", side_effect=OSError("Cannot execute")):
result = mod.search_vectors_subprocess(query_embedding=[0.1])
assert result["success"] is False
assert "Cannot execute" in result["error"]
# ===========================================================================
# Tests: search_collection (vector_search)
# ===========================================================================
class TestSearchCollection:
"""Verify search_collection wraps SearchService.query_collection."""
def test_search_returns_results_on_success(self, monkeypatch, tmp_path):
"""Successful collection query returns success with results."""
mod, mocks = _import_vector_search(monkeypatch)
mock_collection = MagicMock()
mock_collection.query.return_value = {
"ids": [["id1", "id2"]],
"documents": [["doc1", "doc2"]],
"metadatas": [[{"branch": "TEST"}, {"branch": "TEST"}]],
"distances": [[0.1, 0.2]],
}
mocks["client"].get_collection.return_value = mock_collection
result = mod.search_collection(
query_embedding=[0.1] * 384,
collection_name="test_collection",
n_results=5,
db_path=tmp_path / ".chroma",
)
assert result["success"] is True
assert result["count"] == 2
assert "doc1" in result["documents"]
def test_search_returns_error_for_missing_collection(self, monkeypatch, tmp_path):
"""Missing collection returns success=False with error message."""
mod, mocks = _import_vector_search(monkeypatch)
mocks["client"].get_collection.side_effect = ValueError("Collection not found")
result = mod.search_collection(
query_embedding=[0.1] * 384,
collection_name="nonexistent",
db_path=tmp_path / ".chroma",
)
assert result["success"] is False
assert "not found" in result["error"].lower()
def test_search_returns_error_for_empty_embedding(self, monkeypatch):
"""Empty query embedding returns error."""
mod, mocks = _import_vector_search(monkeypatch)
result = mod.search_collection(
query_embedding=[],
collection_name="test_collection",
)
assert result["success"] is False
assert "no query embedding" in result["error"].lower()
def test_search_accepts_string_db_path(self, monkeypatch, tmp_path):
"""String db_path is converted to Path internally."""
mod, mocks = _import_vector_search(monkeypatch)
mock_collection = MagicMock()
mock_collection.query.return_value = {
"ids": [["id1"]],
"documents": [["doc1"]],
"metadatas": [[{}]],
"distances": [[0.05]],
}
mocks["client"].get_collection.return_value = mock_collection
result = mod.search_collection(
query_embedding=[0.1] * 384,
collection_name="test_collection",
db_path=str(tmp_path / ".chroma"),
)
assert result["success"] is True
def test_search_passes_where_filter(self, monkeypatch, tmp_path):
"""Metadata where filter is forwarded to collection.query."""
mod, mocks = _import_vector_search(monkeypatch)
mock_collection = MagicMock()
mock_collection.query.return_value = {
"ids": [[]],
"documents": [[]],
"metadatas": [[]],
"distances": [[]],
}
mocks["client"].get_collection.return_value = mock_collection
mod.search_collection(
query_embedding=[0.1] * 384,
collection_name="test_collection",
where={"branch": "SEEDGO"},
db_path=tmp_path / ".chroma",
)
call_kwargs = mock_collection.query.call_args.kwargs
assert call_kwargs["where"] == {"branch": "SEEDGO"}
# ===========================================================================
# Tests: encode_query (vector_search)
# ===========================================================================
class TestEncodeQuery:
"""Verify encode_query uses QueryEncoder singleton."""
def test_encode_returns_embedding(self, monkeypatch):
"""Successful encoding returns embedding with dimension."""
mod, mocks = _import_vector_search(monkeypatch)
result = mod.encode_query("test query")
assert result["success"] is True
assert len(result["embedding"]) == 384
assert result["dimension"] == 384
assert result["model"] == "all-MiniLM-L6-v2"
def test_encode_rejects_empty_query(self, monkeypatch):
"""Empty query string returns error."""
mod, mocks = _import_vector_search(monkeypatch)
result = mod.encode_query("")
assert result["success"] is False
assert "empty" in result["error"].lower()
def test_encode_rejects_whitespace_only_query(self, monkeypatch):
"""Whitespace-only query string returns error."""
mod, mocks = _import_vector_search(monkeypatch)
result = mod.encode_query(" ")
assert result["success"] is False
assert "empty" in result["error"].lower()
def test_encode_handles_model_error(self, monkeypatch):
"""If model.encode raises, error is caught and returned."""
mod, mocks = _import_vector_search(monkeypatch)
mocks["model"].encode.side_effect = RuntimeError("CUDA out of memory")
result = mod.encode_query("test query")
assert result["success"] is False
assert "CUDA" in result["error"]
def test_encode_uses_singleton(self, monkeypatch):
"""Successive calls reuse the same QueryEncoder instance."""
mod, mocks = _import_vector_search(monkeypatch)
mod.encode_query("first query")
mod.encode_query("second query")
# SentenceTransformer should only be constructed once
mocks["st_cls"].assert_called_once()
# ===========================================================================
# Tests: search_all_collections (vector_search)
# ===========================================================================
class TestSearchAllCollections:
"""Verify search_all_collections aggregates results across collections."""
def test_search_all_returns_aggregated_results(self, monkeypatch, tmp_path):
"""Searching all collections returns results from each."""
mod, mocks = _import_vector_search(monkeypatch)
mock_coll_a = MagicMock()
mock_coll_a.name = "coll_a"
mock_coll_b = MagicMock()
mock_coll_b.name = "coll_b"
mocks["client"].list_collections.return_value = [mock_coll_a, mock_coll_b]
mock_collection = MagicMock()
mock_collection.query.return_value = {
"ids": [["id1"]],
"documents": [["doc1"]],
"metadatas": [[{"branch": "TEST"}]],
"distances": [[0.1]],
}
mocks["client"].get_collection.return_value = mock_collection
result = mod.search_all_collections(
query_embedding=[0.1] * 384,
n_results=3,
db_path=tmp_path / ".chroma",
)
assert result["success"] is True
assert result["collections_searched"] == 2
assert result["total_results"] == 2
assert "coll_a" in result["results"]
assert "coll_b" in result["results"]
def test_search_all_returns_empty_when_no_collections(self, monkeypatch, tmp_path):
"""No collections returns success with empty results."""
mod, mocks = _import_vector_search(monkeypatch)
mocks["client"].list_collections.return_value = []
result = mod.search_all_collections(
query_embedding=[0.1] * 384,
db_path=tmp_path / ".chroma",
)
assert result["success"] is True
assert result["results"] == {}
assert "no collections" in result["message"].lower()
def test_search_all_returns_error_for_empty_embedding(self, monkeypatch):
"""Empty embedding returns error."""
mod, mocks = _import_vector_search(monkeypatch)
result = mod.search_all_collections(query_embedding=[])
assert result["success"] is False
assert "no query embedding" in result["error"].lower()
def test_search_all_handles_exception(self, monkeypatch, tmp_path):
"""Exception during search returns error dict."""
mod, mocks = _import_vector_search(monkeypatch)
mocks["client"].list_collections.side_effect = RuntimeError("DB corrupted")
result = mod.search_all_collections(
query_embedding=[0.1] * 384,
db_path=tmp_path / ".chroma",
)
assert result["success"] is False
assert "DB corrupted" in result["error"]
def test_search_all_accepts_string_db_path(self, monkeypatch, tmp_path):
"""String db_path is accepted and converted."""
mod, mocks = _import_vector_search(monkeypatch)
mocks["client"].list_collections.return_value = []
result = mod.search_all_collections(
query_embedding=[0.1] * 384,
db_path=str(tmp_path / ".chroma"),
)
assert result["success"] is True
def test_search_all_skips_nonexistent_collections(self, monkeypatch, tmp_path):
"""Collections that fail get_collection are excluded from results."""
mod, mocks = _import_vector_search(monkeypatch)
mock_coll_a = MagicMock()
mock_coll_a.name = "coll_a"
mocks["client"].list_collections.return_value = [mock_coll_a]
# get_collection raises => query_collection returns exists=False
mocks["client"].get_collection.side_effect = ValueError("Collection not found")
result = mod.search_all_collections(
query_embedding=[0.1] * 384,
db_path=tmp_path / ".chroma",
)
assert result["success"] is True
assert result["collections_searched"] == 0
assert result["total_results"] == 0
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+492
View File
@@ -0,0 +1,492 @@
# ===================AIPASS====================
# META DATA HEADER
# Name: tests/test_watcher.py
# Date: 2026-04-25
# Version: 1.0.0
# Category: memory/tests
# =============================================
"""Tests for the memory watcher handler.
Covers:
from aipass.memory.apps.handlers.monitor.memory_watcher import start_memory_watcher
from aipass.memory.apps.handlers.monitor.memory_watcher import stop_memory_watcher
from aipass.memory.apps.handlers.monitor.memory_watcher import is_memory_watcher_active
from aipass.memory.apps.handlers.monitor.memory_watcher import get_watcher_status
from aipass.memory.apps.handlers.monitor.memory_watcher import MemoryFileWatcher
Tests watcher lifecycle (start/stop/status), the MemoryFileWatcher.on_modified
callback, and edge cases like missing watchdog or already-running observers.
All tests use mocks -- no live filesystem watchers or infrastructure access.
"""
import sys
from unittest.mock import MagicMock
# ---------------------------------------------------------------------------
# Helpers: prepare the mock graph needed to import memory_watcher
# ---------------------------------------------------------------------------
def _prepare_watcher_mocks(monkeypatch):
"""Insert mocks for every module-level import memory_watcher.py touches.
Returns a dict of key mock objects so tests can assert against them.
"""
# Mock line_counter
mock_update_line_count = MagicMock(return_value={"success": True, "lines": 150})
mock_line_counter = MagicMock()
mock_line_counter.update_line_count = mock_update_line_count
monkeypatch.setitem(
sys.modules,
"aipass.memory.apps.handlers.tracking.line_counter",
mock_line_counter,
)
# Mock detector
mock_check_single_file = MagicMock(return_value={"success": True, "should_rollover": False})
mock_detector = MagicMock()
mock_detector.check_single_file = mock_check_single_file
monkeypatch.setitem(
sys.modules,
"aipass.memory.apps.handlers.monitor.detector",
mock_detector,
)
# Mock watchdog
mock_observer_instance = MagicMock()
mock_observer_instance.is_alive.return_value = True
mock_observer_cls = MagicMock(return_value=mock_observer_instance)
mock_watchdog_observers = MagicMock()
mock_watchdog_observers.Observer = mock_observer_cls
mock_fse_handler = type("FileSystemEventHandler", (), {"__init__": lambda self: None})
mock_watchdog_events = MagicMock()
mock_watchdog_events.FileSystemEventHandler = mock_fse_handler
monkeypatch.setitem(sys.modules, "watchdog", MagicMock())
monkeypatch.setitem(sys.modules, "watchdog.observers", mock_watchdog_observers)
monkeypatch.setitem(sys.modules, "watchdog.events", mock_watchdog_events)
# Mock rollover orchestrator (lazy import inside on_modified)
mock_execute_rollover = MagicMock(return_value={"success": True})
mock_orchestrator = MagicMock()
mock_orchestrator.execute_rollover = mock_execute_rollover
monkeypatch.setitem(
sys.modules,
"aipass.memory.apps.handlers.rollover",
MagicMock(),
)
monkeypatch.setitem(
sys.modules,
"aipass.memory.apps.handlers.rollover.orchestrator",
mock_orchestrator,
)
return {
"update_line_count": mock_update_line_count,
"check_single_file": mock_check_single_file,
"observer_instance": mock_observer_instance,
"observer_cls": mock_observer_cls,
"execute_rollover": mock_execute_rollover,
}
def _import_watcher(monkeypatch):
"""Prepare mocks and import (or reimport) memory_watcher.
Returns (module, mocks_dict).
"""
mocks = _prepare_watcher_mocks(monkeypatch)
# Remove cached module so it gets re-imported with our mocks
sys.modules.pop("aipass.memory.apps.handlers.monitor.memory_watcher", None)
# Also clear parent package's cached attribute so Python re-executes
# the module code with fresh mocks instead of returning a stale ref.
parent = sys.modules.get("aipass.memory.apps.handlers.monitor")
if parent is not None and hasattr(parent, "memory_watcher"):
delattr(parent, "memory_watcher")
from aipass.memory.apps.handlers.monitor import memory_watcher # noqa: E402
# Reset the global _observer to None for a clean slate each test
setattr(memory_watcher, "_observer", None)
return memory_watcher, mocks
# ===========================================================================
# Tests: start_memory_watcher
# ===========================================================================
class TestStartMemoryWatcher:
"""Verify start_memory_watcher lifecycle."""
def test_start_returns_success_with_paths(self, monkeypatch, tmp_path):
"""Starting watcher with valid branch paths returns success."""
mod, mocks = _import_watcher(monkeypatch)
branch_path = tmp_path / "src" / "aipass" / "test_branch"
branch_path.mkdir(parents=True)
monkeypatch.setattr(mod, "_get_branch_paths", lambda: [branch_path])
result = mod.start_memory_watcher()
assert result["success"] is True
assert result["count"] == 1
assert str(branch_path) in result["watched_paths"]
mocks["observer_cls"].assert_called_once()
mocks["observer_instance"].start.assert_called_once()
def test_start_fails_when_already_running(self, monkeypatch, tmp_path):
"""Starting when observer is already alive returns error."""
mod, mocks = _import_watcher(monkeypatch)
# Simulate already running observer
mock_existing = MagicMock()
mock_existing.is_alive.return_value = True
setattr(mod, "_observer", mock_existing)
result = mod.start_memory_watcher()
assert result["success"] is False
assert "already running" in result["error"].lower()
def test_start_fails_when_no_branches(self, monkeypatch):
"""Starting with no branch paths returns error."""
mod, mocks = _import_watcher(monkeypatch)
monkeypatch.setattr(mod, "_get_branch_paths", lambda: [])
result = mod.start_memory_watcher()
assert result["success"] is False
assert "no branch" in result["error"].lower()
def test_start_handles_schedule_error_gracefully(self, monkeypatch, tmp_path):
"""If scheduling a path raises an exception, other paths still work."""
mod, mocks = _import_watcher(monkeypatch)
good_path = tmp_path / "good_branch"
good_path.mkdir(parents=True)
bad_path = tmp_path / "bad_branch"
bad_path.mkdir(parents=True)
call_count = 0
def _mock_schedule(handler, path, recursive=False):
"""Mock observer.schedule that fails on the first call."""
nonlocal call_count
call_count += 1
if call_count == 1:
raise OSError("Permission denied")
mocks["observer_instance"].schedule = _mock_schedule
monkeypatch.setattr(mod, "_get_branch_paths", lambda: [bad_path, good_path])
result = mod.start_memory_watcher()
assert result["success"] is True
# Only the second path should succeed
assert result["count"] == 1
# ===========================================================================
# Tests: stop_memory_watcher
# ===========================================================================
class TestStopMemoryWatcher:
"""Verify stop_memory_watcher lifecycle."""
def test_stop_returns_success(self, monkeypatch):
"""Stopping a running watcher returns success."""
mod, mocks = _import_watcher(monkeypatch)
# Set up a running observer
mock_obs = MagicMock()
mock_obs.is_alive.return_value = True
setattr(mod, "_observer", mock_obs)
result = mod.stop_memory_watcher()
assert result["success"] is True
mock_obs.stop.assert_called_once()
mock_obs.join.assert_called_once()
assert mod._observer is None # type: ignore[union-attr]
def test_stop_fails_when_not_running(self, monkeypatch):
"""Stopping when no watcher is running returns error."""
mod, mocks = _import_watcher(monkeypatch)
result = mod.stop_memory_watcher()
assert result["success"] is False
assert "not running" in result["error"].lower()
def test_stop_fails_when_observer_not_alive(self, monkeypatch):
"""Stopping when observer exists but is not alive returns error."""
mod, mocks = _import_watcher(monkeypatch)
mock_obs = MagicMock()
mock_obs.is_alive.return_value = False
setattr(mod, "_observer", mock_obs)
result = mod.stop_memory_watcher()
assert result["success"] is False
# ===========================================================================
# Tests: is_memory_watcher_active
# ===========================================================================
class TestIsMemoryWatcherActive:
"""Verify is_memory_watcher_active boolean checks."""
def test_active_when_observer_alive(self, monkeypatch):
"""Returns True when observer is alive."""
mod, mocks = _import_watcher(monkeypatch)
mock_obs = MagicMock()
mock_obs.is_alive.return_value = True
setattr(mod, "_observer", mock_obs)
assert mod.is_memory_watcher_active() is True
def test_inactive_when_no_observer(self, monkeypatch):
"""Returns False when _observer is None."""
mod, mocks = _import_watcher(monkeypatch)
assert mod.is_memory_watcher_active() is False
def test_inactive_when_observer_not_alive(self, monkeypatch):
"""Returns False when observer exists but is_alive returns False."""
mod, mocks = _import_watcher(monkeypatch)
mock_obs = MagicMock()
mock_obs.is_alive.return_value = False
setattr(mod, "_observer", mock_obs)
assert mod.is_memory_watcher_active() is False
# ===========================================================================
# Tests: get_watcher_status
# ===========================================================================
class TestGetWatcherStatus:
"""Verify get_watcher_status returns correct status info."""
def test_status_when_inactive(self, monkeypatch):
"""Returns inactive status when watcher is not running."""
mod, mocks = _import_watcher(monkeypatch)
result = mod.get_watcher_status()
assert result["active"] is False
assert "not running" in result["message"].lower()
def test_status_when_active(self, monkeypatch, tmp_path):
"""Returns active status with directory count when watcher is running."""
mod, mocks = _import_watcher(monkeypatch)
mock_obs = MagicMock()
mock_obs.is_alive.return_value = True
setattr(mod, "_observer", mock_obs)
branch_path = tmp_path / "branch1"
branch_path.mkdir()
monkeypatch.setattr(mod, "_get_branch_paths", lambda: [branch_path])
result = mod.get_watcher_status()
assert result["active"] is True
assert result["watched_directories"] == 1
assert str(branch_path) in result["paths"]
def test_status_returns_multiple_paths(self, monkeypatch, tmp_path):
"""Returns all watched directory paths."""
mod, mocks = _import_watcher(monkeypatch)
mock_obs = MagicMock()
mock_obs.is_alive.return_value = True
setattr(mod, "_observer", mock_obs)
paths = []
for name in ["branch_a", "branch_b", "branch_c"]:
p = tmp_path / name
p.mkdir()
paths.append(p)
monkeypatch.setattr(mod, "_get_branch_paths", lambda: paths)
result = mod.get_watcher_status()
assert result["watched_directories"] == 3
assert len(result["paths"]) == 3
# ===========================================================================
# Tests: MemoryFileWatcher.on_modified
# ===========================================================================
class TestMemoryFileWatcherOnModified:
"""Verify MemoryFileWatcher.on_modified callback behavior."""
def test_ignores_directory_events(self, monkeypatch):
"""Directory modification events are ignored."""
mod, mocks = _import_watcher(monkeypatch)
watcher = mod.MemoryFileWatcher()
event = MagicMock()
event.is_directory = True
event.src_path = "/some/.trinity/local.json"
watcher.on_modified(event)
mocks["update_line_count"].assert_not_called()
def test_ignores_non_memory_files(self, monkeypatch):
"""Non-memory files (not in .trinity/) are ignored."""
mod, mocks = _import_watcher(monkeypatch)
watcher = mod.MemoryFileWatcher()
event = MagicMock()
event.is_directory = False
event.src_path = "/some/dir/config.json"
watcher.on_modified(event)
mocks["update_line_count"].assert_not_called()
def test_processes_memory_file_modification(self, monkeypatch):
"""Valid memory file modification triggers line count update and check."""
mod, mocks = _import_watcher(monkeypatch)
watcher = mod.MemoryFileWatcher()
event = MagicMock()
event.is_directory = False
event.src_path = "/some/branch/.trinity/local.json"
watcher.on_modified(event)
mocks["update_line_count"].assert_called_once()
mocks["check_single_file"].assert_called_once()
def test_rollover_triggered_when_threshold_exceeded(self, monkeypatch):
"""When check_single_file says should_rollover, execute_rollover is called."""
mod, mocks = _import_watcher(monkeypatch)
mocks["check_single_file"].return_value = {
"success": True,
"should_rollover": True,
"trigger": "lines exceeded 600",
}
watcher = mod.MemoryFileWatcher()
event = MagicMock()
event.is_directory = False
event.src_path = "/some/branch/.trinity/local.json"
watcher.on_modified(event)
mocks["execute_rollover"].assert_called_once()
def test_skips_recently_modified_file(self, monkeypatch):
"""Files already in the recent modifications set are skipped."""
mod, mocks = _import_watcher(monkeypatch)
watcher = mod.MemoryFileWatcher()
file_path = "/some/branch/.trinity/local.json"
watcher._recent_modifications.add(file_path)
event = MagicMock()
event.is_directory = False
event.src_path = file_path
watcher.on_modified(event)
# Should skip and not call update_line_count
mocks["update_line_count"].assert_not_called()
# The file key should be removed from recent modifications after skip
assert file_path not in watcher._recent_modifications
def test_handles_line_count_update_failure(self, monkeypatch):
"""When update_line_count fails, check_single_file is not called."""
mod, mocks = _import_watcher(monkeypatch)
mocks["update_line_count"].return_value = {
"success": False,
"error": "File not found",
}
watcher = mod.MemoryFileWatcher()
event = MagicMock()
event.is_directory = False
event.src_path = "/some/branch/.trinity/local.json"
watcher.on_modified(event)
mocks["update_line_count"].assert_called_once()
mocks["check_single_file"].assert_not_called()
def test_handles_check_failure(self, monkeypatch):
"""When check_single_file fails, rollover is not triggered."""
mod, mocks = _import_watcher(monkeypatch)
mocks["check_single_file"].return_value = {
"success": False,
"error": "Read error",
}
watcher = mod.MemoryFileWatcher()
event = MagicMock()
event.is_directory = False
event.src_path = "/some/branch/.trinity/local.json"
watcher.on_modified(event)
mocks["check_single_file"].assert_called_once()
mocks["execute_rollover"].assert_not_called()
def test_processes_observations_json(self, monkeypatch):
"""observations.json files in .trinity/ are also processed."""
mod, mocks = _import_watcher(monkeypatch)
watcher = mod.MemoryFileWatcher()
event = MagicMock()
event.is_directory = False
event.src_path = "/some/branch/.trinity/observations.json"
watcher.on_modified(event)
mocks["update_line_count"].assert_called_once()
def test_rollover_exception_does_not_propagate(self, monkeypatch):
"""If execute_rollover raises, the exception is caught."""
mod, mocks = _import_watcher(monkeypatch)
mocks["check_single_file"].return_value = {
"success": True,
"should_rollover": True,
"trigger": "lines exceeded",
}
mocks["execute_rollover"].side_effect = RuntimeError("Rollover crashed")
watcher = mod.MemoryFileWatcher()
event = MagicMock()
event.is_directory = False
event.src_path = "/some/branch/.trinity/local.json"
# Should not raise
watcher.on_modified(event)
mocks["execute_rollover"].assert_called_once()
@@ -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