# Sprint 5f TDD Plan — Spatial Validation in node_ops

Testing framework: **pytest** with **pytest-asyncio** (asyncio_mode = "auto"). Mocking via `unittest.mock.AsyncMock` and `unittest.mock.patch`.

Test file: `tests/test_bridge/test_spatial_validator.py`

---

## Section 1: Spatial Validator Module

Write these tests BEFORE implementing `spatial_validator.py`.

### validate_move tests

```python
# Test: empty scene data returns OK severity
# Test: position with fewer than 3 values returns BLOCKED
# Test: position far from all walls returns OK with valid=True
# Test: position < 0.2m from StaticBody3D returns BLOCKED, message mentions wall name
# Test: position 0.2-0.5m from wall returns WARNING
# Test: position same as self node returns OK (self-exclusion)
# Test: position < 0.05m from same-depth node returns BLOCKED with "overlap" in message
# Test: position same as node at different depth returns OK (depth filtering)
# Test: position 50m from Shelf_01-03 siblings returns WARNING with "sibling" in message
# Test: wall at Y=0, target at Y=5 → OK (Y-axis sanity check skips wall)
# Test: wall identified by name containing "Wall" (not just StaticBody type)
# Test: wall identified by name containing "Floor" or "Ceiling" or "Ground"
```

### validate_scale tests

```python
# Test: scale matching siblings returns OK
# Test: scale 40x smaller than siblings returns WARNING
# Test: unique node name with no siblings returns OK
# Test: scale with fewer than 3 values returns BLOCKED
```

### _get_name_prefix tests

```python
# Test: "Shelf_01" → "Shelf_"
# Test: "Shelf_02" → "Shelf_" (same prefix as 01)
# Test: "PrinterSlot_P3" → "PrinterSlot_" (3-char alpha suffix)
# Test: "Wall_East" → "" (4-char suffix exceeds max 3)
# Test: "MainCamera" → "" (no underscore pattern)
```

### _extract_nodes tests

```python
# Test: extracts nodes from "nodes" key
# Test: returns empty list for empty dict
# Test: handles nodes with full field names (name, type, position, scale)
```

## Section 2: node_ops Integration

Write these tests BEFORE modifying `node_ops.py`.

```python
# Test: operations without validate:true bypass validation entirely (bridge called normally)
# Test: operations with validate:true but scene_data=None bypass validation (bridge called normally)
# Test: BLOCKED validation prevents bridge.request from being called
# Test: BLOCKED returns {"status": "BLOCKED", "validation": [...], "operations_blocked": N}
# Test: WARNING validation allows bridge.request, result includes "validation" key
# Test: mixed batch with one BLOCKED op blocks ALL ops (atomic), bridge NOT called
# Test: add_child with position and validate:true triggers validate_move
```

## Section 3: MCP Wrapper Updates

These tests verify the `__init__.py` changes. They require more complex mocking.

```python
# Test: scene parameter not provided + no validate ops → normal bridge call (no session access)
# Test: validate ops + ctx=None → validation skipped, bridge called normally
# Test: validate ops + session=None → validation skipped, bridge called normally
# Test: validate ops + session + scene → resolve_scene_spatial called, scene_data passed to node_ops
# Test: resolve_scene_spatial returns None → validation skipped gracefully
```

## Section 4: MCP Prompt Update

No tests needed — this is a documentation-only change.

## Section 5: Testing

This section IS the tests — no meta-tests needed. Verification:

```python
# Verify: all tests pass with `pytest tests/test_bridge/test_spatial_validator.py -v`
# Verify: full suite passes with `pytest tests/ -v` (no regressions)
```
