# Sprint 4a TDD Plan

Testing framework: pytest with `asyncio_mode = "auto"`, class-based test organization, `unittest.mock.AsyncMock` for async mocking.

---

## Section 1: Godot Addon — Plugin Configuration and Lifecycle

No Python tests. GDScript files are validated manually in Godot editor.

---

## Section 2: Godot Addon — WebSocket Server

No Python tests. GDScript files are validated manually in Godot editor.

---

## Section 3: Godot Addon — EngineDebugger Bridge and Game Runtime

No Python tests. GDScript files are validated manually in Godot editor.

---

## Section 4: Python Bridge Layer — Protocol and Connection

### tests/test_bridge/test_protocol.py

```python
# TestBridgeRequest
# Test: to_json() with no params produces valid JSON with empty params dict
# Test: to_json() with params preserves all param values

# TestParseMessage
# Test: parse ok response extracts id, status, result correctly
# Test: parse error response extracts error code and message
# Test: parse event message returns BridgeEvent with event name and data
# Test: parse event with data preserves nested data fields
# Test: invalid JSON raises ValueError with "Invalid JSON" message
# Test: message without id or event raises ValueError
# Test: non-object JSON (string, array) raises ValueError

# TestTimeouts
# Test: known method returns configured timeout (screenshot=30, ping=2)
# Test: unknown method returns default 5.0
# Test: all configured timeouts are positive
```

### tests/test_bridge/test_connection.py

```python
# TestBridgeConnectionInit
# Test: default host is 127.0.0.1 and port is 6007
# Test: custom host and port are stored correctly
# Test: is_connected and is_game_running are False initially

# TestBridgeConnectionRequest
# Test: request() without connect() raises BridgeConnectionError

# TestBridgeConnectionEvents
# Test: _on_game_started sets is_game_running to True
# Test: _on_game_stopped sets is_game_running to False
# Test: registered event handler is called with correct data
# Test: multiple handlers for same event are all called

# TestBridgeConnectionErrorTypes
# Test: BridgeError stores code and error_message attributes
# Test: BridgeError str includes the error code
```

---

## Section 5: Python Bridge Layer — Session Manager and Tool Registration

### tests/test_bridge/test_tools.py (session-related)

```python
# Session manager tests are covered implicitly by tool tests
# which mock get_bridge() / get_bridge_or_none()
```

No separate session manager test file — the singleton behavior is tested through tool tests with mocked bridge.

---

## Section 6: Bridge MCP Tool Implementations

### tests/test_bridge/test_tools.py

```python
# TestScreenshotTool
# Test: default params call request_with_retry with scale=0.5, format=webp, viewport=game
# Test: invalid scale (negative) is clamped to 0.5
# Test: invalid format falls back to webp
# Test: result contains image, format, width, height

# TestRunTool
# Test: action=play calls bridge.request("run") with scene param
# Test: action=stop calls bridge.request("stop")
# Test: result contains action field

# TestPerfSnapshotTool
# Test: calls bridge.request("perf_snapshot")
# Test: result contains fps field

# TestEditorContextTool
# Test: when connected, result has addon_connected=True and editor_version
# Test: when disconnected, result has addon_connected=False and note field
```

---

## Section 7: Tests

This section IS the tests. The stubs above define what to write. Implementation follows the spec code exactly.

---

## Testing Approach Summary

- All tests use mocked bridge (no real WebSocket connection)
- `unittest.mock.AsyncMock` for async method mocking
- `unittest.mock.patch` for replacing module-level functions (get_bridge, get_bridge_or_none)
- Class-based organization matching existing project conventions
- No new test dependencies needed (pytest-asyncio already available)
