# Phase 5: Phase Summary — Tests for Phases 2–4

## What Was Built

A comprehensive test suite (255 tests across 10 modules) covering the data layer and static analysis pipeline built in Phases 2–4. Tests exercise Pydantic model validation, SQLite repository CRUD operations, file scoring and selection, import graph construction, module clustering, test file detection, tree-sitter parsing utilities, language adapter dispatch, and security scanning (secrets detection + vulnerability auditing). All tests are isolated — in-memory SQLite for DB tests, `unittest.mock.patch` for external tools (detect-secrets, pip-audit, npm audit), and `tmp_path` for any filesystem needs. A shared `conftest.py` provides fixtures for database connections and tree-sitter parsers.

## Key Decisions

**Tests written from interface contracts, not implementation.** Tests were initially authored against the Phase 2–4 plan's documented interfaces, which surfaced several real mismatches between contracts and code. These were corrected during review.

**Mock at the import boundary, not the module boundary.** `scan_for_secrets()` imports `detect_secrets` inside its function body (lazy import for optional dependency). The correct mock target is `detect_secrets.SecretsCollection`, not `unfamiliar.analysis.security.secrets.SecretsCollection`. This pattern applies anywhere lazy imports are used.

**`score_files` uses `list[dict]` not `list[EntryPoint]`.** The scoring function was written in Phase 3 before `EntryPoint` was a dataclass (Phase 4). Tests now match the implementation's dict-based interface. A future cleanup could unify this.

**`sqlite_sequence` filtered from schema assertions.** SQLite auto-creates this table when AUTOINCREMENT columns exist. It's an implementation detail, not part of the application schema.

**Hypothesis property tests with sampled strategies.** Rather than generating arbitrary strings (which tree-sitter can't parse meaningfully), property tests use `st.sampled_from` with realistic path/extension sets. This keeps tests fast and meaningful.

## Complex Logic Explained

### Permission-stripping bug in `init_database()`

`PRAGMA database_list` returns `""` (empty string) for `:memory:` connections. `Path("")` silently resolves to the current working directory. `Path("").exists()` returns `True`. So `os.chmod(Path(""), 0o600)` was stripping the execute bit from the project directory, making all subsequent file operations fail with "Permission denied." The fix: check the raw string is truthy before constructing a `Path`, and verify `is_file()` before applying permissions.

### Tree-sitter cursor node identity

`traverse()` uses `TreeCursor` for iterative traversal. The cursor yields lightweight node wrapper objects that Python garbage-collects between iterations. `id()` returns memory addresses, which get reused for new allocations. So `id(node)` can return the same value for different tree positions. The test was fixed to use `(node.type, node.start_byte, node.end_byte)` as a stable identity.

### Security mock targeting for lazy imports

When code does `from detect_secrets import SecretsCollection` inside a function body, the name `SecretsCollection` never exists as an attribute of the containing module. `unittest.mock.patch("module.SecretsCollection")` raises `AttributeError`. The correct target is the original module: `patch("detect_secrets.SecretsCollection")`. This works because the lazy import re-executes each call, picking up the patched object from the original module.

## Connection to Previous Phases

**Builds on**: Phase 2 (data models, DB schema, repositories), Phase 3 (scoring, import graphs, clustering, test detection), Phase 4 (language adapters, tree-sitter utilities, security scanning). Every public function from these phases has at least one test.

**Sets up for**: Phase 6+ (LLM integration, campaign orchestration, TUI). The test suite provides regression coverage so future phases can refactor the analysis pipeline with confidence. The `conftest.py` fixtures (`db_conn`, `sample_campaign`, parser fixtures) are reusable in future test modules.

**Incident response**: The `connection.py` chmod bug exposed the need for system-level guardrails when running AI agents. The PreToolUse hook (`~/.claude/hooks/block-dangerous.py`) and permission deny rules now prevent agents from executing `chmod`, `chown`, `sudo`, and other permission-modifying commands regardless of model provider.
