# Section 7: Tests

## Implementation Status: COMPLETE

Only `test_token_budget.py` was created in this section. The other two files (`test_truncate_output.py` and `test_detail_levels.py`) were already created during sections 01-06 as tests were written alongside implementation (TDD).

**Files created:** `tests/test_tools/test_token_budget.py` (2 integration tests, 12 tools covered)
**Files already present:** `test_truncate_output.py` (section 01), `test_detail_levels.py` (sections 02-06)

**Final test count:** 944 tests passing across the full suite.

## Overview

This section adds three new test files to validate every aspect of the Sprint 8a token optimization work. These tests cover the new `truncate_output()` function, input validation on `apply_output_limit()`, per-tool detail level enforcement, the `impact_check` parameter rename, and an integration token-budget test.

**Dependencies:** Sections 01 through 06 must be implemented first. This section tests the code produced by all preceding sections.

**Files to create (do NOT modify existing test files):**

| File | Purpose |
|------|---------|
| `tests/test_tools/test_truncate_output.py` | Unit tests for `truncate_output()` and `apply_output_limit()` input validation |
| `tests/test_tools/test_detail_levels.py` | Per-tool detail parameter tests (new tools added in sections 02-05) and existing tool verification |
| `tests/test_tools/test_token_budget.py` | Integration test: combined multi-tool workflow stays within budget |

**Important:** The file `tests/test_detail_levels.py` (at root of tests/) already exists. The new file goes in `tests/test_tools/test_detail_levels.py` — a different path. Do not modify the existing file.

---

## Test File 1: `tests/test_tools/test_truncate_output.py`

Tests the `truncate_output()` function and `apply_output_limit()` input validation from Section 1.

### What to import

```python
from godotiq.tools.output_limit import truncate_output, apply_output_limit
```

### TestTruncateOutput class

- **test_empty_string_unchanged** — Empty string input returns empty string for all detail levels.

- **test_under_brief_limit_unchanged** — Text shorter than 2000 chars with `detail="brief"` returns unchanged.

- **test_over_brief_limit_truncated** — Text of ~5000 chars with `detail="brief"` is truncated. Returned string must be at most 2000 chars. Truncation notice must appear at the end.

- **test_under_normal_limit_unchanged** — Text of ~5000 chars with `detail="normal"` returns unchanged (under 8000 limit).

- **test_over_normal_limit_truncated** — Text of ~15000 chars with `detail="normal"` is truncated to at most 8000 chars. Notice appears.

- **test_full_never_truncated** — Very large text (50k+ chars) with `detail="full"` returns unchanged.

- **test_truncation_notice_has_line_count** — Generate text with known line count (e.g., 100 lines of 100 chars each = 10000 chars). Truncate with `detail="brief"`. Notice must include accurate count of remaining lines (parse the notice string to verify).

- **test_exactly_at_limit_unchanged** — Text exactly at the brief limit (2000 chars) returns unchanged (boundary condition).

- **test_unknown_detail_treated_as_normal** — `detail="verbose"` uses normal limits (8000 chars). Text of 5000 chars returns unchanged. Text of 15000 chars is truncated.

### TestApplyOutputLimitValidation class

- **test_unknown_detail_normalized_to_normal** — Call `apply_output_limit(big_result, "verbose")` where `big_result` exceeds normal limit (8000) but not full. Verify output is truncated (`output_truncated` is True).

- **test_empty_string_detail_normalized** — `detail=""` applies normal limits, not unlimited.

- **test_valid_values_unchanged** — `detail="brief"`, `"normal"`, `"full"` work as before.

- **test_typo_detail_normalized** — `detail="nomal"` (typo) normalizes to normal behavior. Build result dict with large list (~50 items, each ~200 chars). Verify it is truncated.

---

## Test File 2: `tests/test_tools/test_detail_levels.py`

Per-tool detail parameter tests for tools modified in sections 02-06. Uses the `session` fixture from `tests/test_tools/conftest.py`.

### Constants and helpers

```python
import inspect
import json

BRIEF_LIMIT = 3_000   # 2000 + buffer for truncation metadata overhead
NORMAL_LIMIT = 10_000  # 8000 + buffer

def _output_size(result: dict) -> int:
    """Return JSON-serialized character count of a result dict."""
    return len(json.dumps(result, default=str, ensure_ascii=False))
```

### TestSuggestScaleDetail (Section 2)

- **test_accepts_detail_parameter** — `inspect.signature(_build_suggest_scale)` has `detail`
- **test_default_returns_dict** — Call with no detail kwarg, returns dict
- **test_brief_output_under_3000_chars** — detail="brief", size < 3000
- **test_normal_output_under_10000_chars** — detail="normal", size < 10000

### TestImpactCheckRename (Section 3)

- **test_accepts_change_description_parameter** — `inspect.signature` has `change_description`
- **test_returned_dict_has_change_description_field** — Key `"change_description"` in result, NOT `"detail"` as data field
- **test_safe_alternative_uses_change_description** — action="add_parameter", change_description="tax_rate" → "tax_rate" appears in safe_alternative
- **test_detail_brief_output_under_3000** — detail="brief", size < 3000
- **test_detail_normal_output_under_10000** — detail="normal", size < 10000
- **test_default_detail_is_normal** — `inspect.signature` shows default="normal"

### TestPlacementDetail (Section 4)

- **test_accepts_detail_parameter** — `inspect.signature` has `detail`

### TestBridgeToolSignatures (Section 5)

For each bridge tool needing detail:
- **test_{tool_name}_accepts_detail** — Import wrapper, use `inspect.signature` to verify `detail` exists with default `"normal"`

Tools: `screenshot`, `run`, `editor_context`, `node_ops`, `script_ops`, `file_ops`, `input`, `watch`, `undo_history`, `save_scene`, `camera`, plus `ping` from `server.py`.

### TestExistingToolOutputSizes (Section 6)

Verify existing tools with `detail` stay within limits:

- **test_asset_registry_brief_under_3000** — detail="brief", size < 3000
- **test_asset_registry_normal_under_10000** — detail="normal", size < 10000
- **test_scene_map_brief_under_3000** — detail="brief", size < 3000
- **test_scene_map_normal_under_10000** — detail="normal", size < 10000
- **test_scene_map_full_keeps_30_cap** — detail="full", nodes list has at most 30 entries
- **test_signal_map_brief_under_3000** — detail="brief", size < 3000
- **test_dependency_graph_brief_under_3000** — detail="brief", size < 3000
- **test_validate_brief_under_3000** — detail="brief", size < 3000
- **test_spatial_audit_brief_under_3000** — detail="brief", size < 3000
- **test_project_summary_brief_under_3000** — detail="brief", size < 3000
- **test_animation_info_brief_under_3000** — detail="brief", size < 3000
- **test_animation_audit_brief_under_3000** — detail="brief", size < 3000
- **test_file_context_brief_under_3000** — detail="brief", size < 3000
- **test_trace_flow_brief_under_3000** — detail="brief", size < 3000

Each test should gracefully skip (`pytest.skip`) if fixtures lack the needed data.

---

## Test File 3: `tests/test_tools/test_token_budget.py`

Integration test verifying combined multi-tool workflow stays within budget.

### What to import

```python
import json
import pytest
from godotiq.session import GodotIQSession
from godotiq.tools.memory import _build_project_summary
from godotiq.tools.code import _build_signal_map, _build_dependency_graph
from godotiq.tools.spatial import _build_scene_map, _build_spatial_audit
from godotiq.tools.assets import _build_asset_registry
```

### TestTokenBudget class

- **test_combined_workflow_under_30000_chars** — Call multiple tools with brief/normal detail:
  1. `_build_project_summary(session, detail="brief")`
  2. `_build_scene_map(session, scene, detail="normal")` (first scene from fixture)
  3. `_build_asset_registry(session, detail="brief")`
  4. `_build_spatial_audit(session, scene, detail="brief")`
  5. `_build_signal_map(session, detail="brief")`

  Sum JSON-serialized char lengths, assert total < 30000. Skip if no scenes in fixture.

- **test_all_offline_tools_default_within_normal** — Call every offline tool with default detail (no parameter). Assert each individual result is under 10000 chars. Validates the system-wide default of `"normal"` keeps every tool manageable.

## Implementation Notes

### Test patterns to follow

- Classes group related tests
- The `session` fixture from `conftest.py` provides a loaded `GodotIQSession`
- JSON serialization for size checks uses `json.dumps(result, default=str)`
- Tests gracefully skip when fixtures lack data
- `_build_*` helpers are sync functions — no asyncio needed
- Only the `@mcp.tool()` wrappers are async

### Bridge tool testing

Bridge tools require a running Godot editor. Use `inspect.signature` to verify function signatures accept `detail` with default `"normal"`. This is the same approach used in existing tests.
