# Section 01: Fixtures

## Overview

This section creates the minimal fixture project at `tests/fixtures/trace_flow/` and the associated `trace_flow_session` pytest fixture. It also adds the `FlowStep` and `FailurePoint` dataclasses to `src/godotiq/tools/flow/__init__.py`. These are the foundational building blocks that all subsequent sections depend on.

No other sections need to be completed before this one.

---

## Background

GodotIQ's test infrastructure uses fixture projects -- small Godot project directories under `tests/fixtures/` containing `project.godot`, `.gd` scripts, and optionally `.tscn` scenes. A `GodotIQSession` is loaded from these directories to provide parsed project state for tests.

The existing pattern (visible in `tests/test_tools/conftest.py`) creates a `Path` fixture pointing at the fixture directory and a `GodotIQSession` fixture that calls `.load()` on it:

```python
@pytest.fixture
def sample_root() -> Path:
    return Path(__file__).resolve().parent.parent / "fixtures" / "sample_project"

@pytest.fixture
def session(sample_root: Path) -> GodotIQSession:
    s = GodotIQSession(sample_root)
    s.load()
    return s
```

The `project.godot` format for autoloads uses the pattern:
```ini
[autoload]
AutoloadName="*res://path/to/script.gd"
```

The `*` prefix indicates the autoload is enabled. The `scan_project` parser strips it and stores `{"AutoloadName": "res://path/to/script.gd"}` in `ProjectIndex.autoloads`.

Key parser dataclasses used by trace_flow (from `src/godotiq/parsers/gd_parser.py`):

- **GdScript** -- parsed `.gd` file with `signals`, `functions`, `signal_connections`, `signal_emissions`, `autoload_refs`
- **GdSignal** -- `name: str`, `line: int`
- **GdFunction** -- `name: str`, `line: int`, `end_line: int`
- **GdSignalConnection** -- `signal_source: str`, `target: str`, `line: int`
- **GdSignalEmission** -- `signal_name: str`, `line: int`
- **GdAutoloadRef** -- `autoload_name: str`, `access: str`, `pattern: str`, `line: int`

The parser recognizes autoloads by name (passed via `known_autoloads` parameter to `parse_gd`). The `pattern` field is one of `"direct"`, `"has_node"`, `"get_node"`, `"get_node_or_null"`.

---

## Files to Create

### `tests/fixtures/trace_flow/project.godot`

Minimal Godot project file declaring two autoloads: `Events` and `PrinterManager`.

```ini
[application]

config/name="Trace Flow Test"

[autoload]

Events="*res://events.gd"
PrinterManager="*res://printer_manager.gd"
```

### `tests/fixtures/trace_flow/events.gd`

Signal bus autoload. Defines two signals, no functions beyond what is needed.

```gdscript
extends Node

signal order_accepted
signal printer_reserved
```

### `tests/fixtures/trace_flow/order_manager.gd`

Contains `accept_order()` which emits `Events.order_accepted`.

```gdscript
extends Node

func accept_order() -> void:
	Events.order_accepted.emit()
```

### `tests/fixtures/trace_flow/pipeline_controller.gd`

Connects to `Events.order_accepted` in `_ready()`, and the handler calls `PrinterManager.reserve_printer()`.

```gdscript
extends Node

func _ready() -> void:
	Events.order_accepted.connect(_on_order_accepted)

func _on_order_accepted() -> void:
	PrinterManager.reserve_printer()
```

### `tests/fixtures/trace_flow/printer_manager.gd`

Autoload with `reserve_printer()` that has an early return guard and emits `printer_reserved`. Also includes extra functions for failure point testing.

```gdscript
extends Node

var has_available: bool = true

func reserve_printer() -> void:
	if not has_available:
		return
	Events.printer_reserved.emit()

func check_target() -> void:
	if target == null:
		return

func check_queue() -> void:
	if queue.is_empty():
		return

func check_node() -> void:
	if not is_instance_valid(node):
		return
```

### `tests/fixtures/trace_flow/edge_lambda.gd`

Edge case: a script with a lambda connection target.

```gdscript
extends Node

func _ready() -> void:
	Events.order_accepted.connect(func(): pass)
```

### `tests/fixtures/trace_flow/edge_get_node.gd`

Edge case: a script with `has_node`/`get_node` autoload references.

```gdscript
extends Node

func check_events() -> void:
	if has_node("/root/Events"):
		var e = get_node("/root/Events")
```

### `tests/fixtures/trace_flow/edge_property.gd`

Edge case: autoload property access (no parentheses).

```gdscript
extends Node

var cached_name: String = ""

func read_name() -> void:
	cached_name = PrinterManager.name
```

---

## Files to Modify

### `tests/test_tools/conftest.py`

Add `trace_flow_root` and `trace_flow_session` fixtures alongside existing fixtures, following the same pattern as `sample_root`/`session`.

### `src/godotiq/tools/flow/__init__.py`

Currently contains only a module docstring. Add the `FlowStep` and `FailurePoint` dataclasses. Use `field(default_factory=list)` for the list fields in `FlowStep`.

```python
@dataclass
class FlowStep:
    file: str           # res:// path
    function: str       # function name
    line: int           # line number
    action: str         # human-readable description
    emits: list[str] = field(default_factory=list)
    calls: list[str] = field(default_factory=list)

@dataclass
class FailurePoint:
    step: int           # index into flow list
    condition: str      # the "if" condition
    consequence: str    # what happens
```

---

## Tests

Create `tests/test_tools/test_trace_flow.py` with fixture validation and dataclass tests.

**test_fixture_session_loads** -- `trace_flow_session._index` is not None, and `trace_flow_session._gd_scripts` is non-empty.

**test_fixture_autoloads_declared** -- `trace_flow_session._index.autoloads` contains keys `"Events"` and `"PrinterManager"` with expected `res://` paths.

**test_fixture_events_signals** -- The parsed script at `"res://events.gd"` has two signals named `"order_accepted"` and `"printer_reserved"`.

**test_fixture_order_manager_function** -- The parsed script at `"res://order_manager.gd"` has a function named `"accept_order"`.

**test_fixture_order_manager_emission** -- The parsed script at `"res://order_manager.gd"` has a signal emission with `signal_name="order_accepted"`.

**test_fixture_pipeline_controller_connection** -- The parsed script at `"res://pipeline_controller.gd"` has a `GdSignalConnection` with `signal_source` containing `"order_accepted"` and `target="_on_order_accepted"`.

**test_fixture_pipeline_controller_autoload_ref** -- The parsed script at `"res://pipeline_controller.gd"` has a `GdAutoloadRef` with `autoload_name="PrinterManager"` and `access="reserve_printer"`.

**test_fixture_printer_manager_emission** -- The parsed script at `"res://printer_manager.gd"` has a signal emission with `signal_name="printer_reserved"`.

**test_fixture_lambda_connection** -- The parsed script at `"res://edge_lambda.gd"` has a `GdSignalConnection` whose `target` starts with `"func"`.

**test_fixture_property_access_ref** -- The parsed script at `"res://edge_property.gd"` has a `GdAutoloadRef` with `autoload_name="PrinterManager"`, `access="name"`, and `pattern="direct"`.

**test_flow_step_defaults** -- Creating `FlowStep(file="", function="", line=0, action="")` produces an object with `emits=[]` and `calls=[]`.

**test_failure_point_fields** -- Creating `FailurePoint(step=0, condition="not x", consequence="stops")` stores all three fields correctly.

---

## Verification Criteria

1. `pytest tests/test_tools/test_trace_flow.py -v` passes all tests
2. All 695+ existing tests still pass (`pytest -v`)
3. `from godotiq.tools.flow import FlowStep, FailurePoint` works
4. The fixture project directory `tests/fixtures/trace_flow/` contains all 7 `.gd` files and 1 `project.godot`

---

## Dependencies

- **Depends on**: Nothing (this is the first section)
- **Blocks**: All subsequent sections (02 through 07)
