# Sprint 3e — TDD Plan

Testing framework: pytest with pytest-asyncio (`asyncio_mode = "auto"`).
Test location: `tests/test_tools/`
Pattern: Import internal `_build_*` functions directly, assert on result dicts. Use `session` fixture from `conftest.py` (loads `tests/fixtures/sample_project/`). Programmatic injection into `session._gd_scripts` for custom scenarios.

---

## Fix 1 — signal_map: Project-Wide Missing Signal Check

**File:** `tests/test_tools/test_signal_map.py`

### Tests to write BEFORE implementation

```python
# Test: signal emitted in file A, defined in file B → NOT missing
# Setup: inject events_bus.gd (signals: [order_accepted, day_started])
#        inject bus_emitter.gd (emissions: [order_accepted])
# Call: _build_signal_map(session, find="missing")
# Assert: no missing signals with name "order_accepted"

# Test: signal emitted but not defined anywhere → IS missing
# Setup: inject broken_emitter.gd (emissions: [totally_fake_signal])
#        no script defines signal "totally_fake_signal"
# Call: _build_signal_map(session, find="missing")
# Assert: exactly 1 missing signal with name "totally_fake_signal"
```

Use existing `GdScript`, `GdSignal`, `GdSignalEmission` dataclasses to construct test objects. Follow the pattern already in `test_signal_map_missing` (lines 52-61).

---

## Fix 2 — spatial_audit: Severity Filtering + Sibling Z-Fighting Downgrade

**File:** `tests/test_tools/test_spatial_audit.py`

### Tests to write BEFORE implementation

```python
# Test: min_severity="warning" filters out info issues
# Setup: scene with z-fighting siblings (will be info) + scale mismatch (warning)
# Call: _build_spatial_audit(session, scene, min_severity="warning")
# Assert: issues list has no "info" entries
# Assert: by_severity["info"] > 0 (counts show all)
# Assert: shown_issues < total_issues

# Test: min_severity="info" shows everything
# Setup: same scene
# Call: _build_spatial_audit(session, scene, min_severity="info")
# Assert: issues list includes info entries
# Assert: shown_issues == total_issues

# Test: sibling z-fighting gets severity "info"
# Setup: two MeshInstance3D nodes at same Y, same parent (e.g., "Room/Floor/TileA" and "Room/Floor/TileB")
# Call: _check_z_fighting(world_nodes)
# Assert: z_fighting issue has severity "info"

# Test: non-sibling z-fighting stays severity "warning"
# Setup: two MeshInstance3D nodes at same Y, different parents (e.g., "Room/Floor/TileA" and "Room/Rugs/RugB")
# Call: _check_z_fighting(world_nodes)
# Assert: z_fighting issue has severity "warning"
```

Construct WorldNode objects programmatically with specific `full_path` values to control parent relationships.

---

## Fix 3 — suggest_scale: Reference Mode Without Model

**File:** `tests/test_tools/test_suggest_scale.py`

### Tests to write BEFORE implementation

```python
# Test: reference mode returns matching nodes with scale stats
# Setup: scene with nodes named "*Printer*" at known scales
# Call: _build_suggest_scale(session, scene, model=None, intended_use="printer")
# Assert: mode == "reference"
# Assert: matches_found > 0
# Assert: recommended_scale is median of matched scales

# Test: reference mode with no matches
# Setup: same scene, intended_use="dragon"
# Call: _build_suggest_scale(session, scene, model=None, intended_use="dragon")
# Assert: matches_found == 0
# Assert: recommended_scale is None

# Test: both model and intended_use empty → error
# Call: _build_suggest_scale(session, scene, model=None, intended_use="")
# Assert: "error" key in result

# Test: existing model-based behavior unchanged
# Call: _build_suggest_scale(session, scene, model="res://assets/models/printers/printer_standard.glb")
# Assert: mode == "model" (or original behavior preserved)
# Assert: recommended_scale is not None
```

---

## Fix 4 — scene_map: Detail-Dependent Caps and Token Hints

**File:** `tests/test_tools/test_scene_map.py`

### Tests to write BEFORE implementation

```python
# Test: brief detail caps nearby at 10
# Setup: scene with 15+ nodes within radius of focus node
# Call: _build_scene_map(session, scene, focus=node, detail="brief")
# Assert: len(nodes) <= 10
# Assert: nearby_capped == True
# Assert: nearby_total_in_radius >= 15

# Test: normal detail caps nearby at 20
# Setup: same scene with 25+ nodes
# Call: _build_scene_map(session, scene, focus=node, detail="normal")
# Assert: len(nodes) <= 20

# Test: brief nearby nodes have compact keys only
# Setup: scene with focus and nearby nodes
# Call: _build_scene_map(session, scene, focus=node, detail="brief")
# Assert: each nearby node dict has ONLY keys: name, type, distance_to_focus, direction_from_focus
# Assert: "groups" not in node, "script" not in node, "properties" not in node

# Test: capped output includes nearby_total_in_radius
# Setup: scene where cap is hit
# Call: _build_scene_map(session, scene, focus=node, detail="brief")
# Assert: "nearby_total_in_radius" in result
# Assert: result["nearby_total_in_radius"] > 10
```

---

## Fix 5 — project_summary: Architectural Enrichment

**File:** `tests/test_tools/test_project_summary.py`

### Tests to write BEFORE implementation

```python
# Test: full detail has enriched autoload analysis
# Call: _build_project_summary(session, detail="full")
# Assert: autoload_analysis entries have "signals_defined" key (int)
# Assert: autoload_analysis entries have "referenced_by_count" key (int)

# Test: full detail has signal_health
# Call: _build_project_summary(session, detail="full")
# Assert: "signal_health" in result
# Assert: signal_health has keys: total_defined, total_emissions, total_connections, orphan_count, top_signals
# Assert: top_signals is list, each entry has "name" and "listeners"

# Test: full detail has health
# Call: _build_project_summary(session, detail="full")
# Assert: "health" in result
# Assert: health has keys: orphan_signals, scripts_without_class_name, note
# Assert: orphan_signals is int >= 0

# Test: normal detail does NOT have signal_health or health
# Call: _build_project_summary(session, detail="normal")
# Assert: "signal_health" not in result
# Assert: "health" not in result

# Test: brief detail unchanged
# Call: _build_project_summary(session, detail="brief")
# Assert: "signal_health" not in result
# Assert: "health" not in result
# Assert: "autoload_analysis" not in result
```
