# Section 05: Build and Verify Wheel

## Overview

This section covers building the Python wheel, verifying its contents include all expected files (Python modules, addon package data, prompt files), and confirming that the installed package works end-to-end. This is the integration verification gate that must pass before the README can be updated (section-06).

**Dependencies:** This section assumes all prior sections are complete:
- **section-01** (pyproject.toml with `force-include`, `addon/__init__.py`)
- **section-02** (`__version__` in `__init__.py`, `server.py` using dynamic version)
- **section-03** (`cli.py` with argparse, `__main__.py` routing through `cli_main()`)
- **section-04** (`install-addon` subcommand fully implemented in `cli.py`)

## Background

The project uses Hatchling as its build backend. After sections 01-04, the `pyproject.toml` should contain:

- `[tool.hatch.build.targets.wheel]` with `packages = ["src/godotiq"]`
- `[tool.hatch.build.targets.wheel.force-include]` mapping `"godot-addon/addons/godotiq" = "godotiq/addon"`
- `[project.scripts]` pointing to `godotiq = "godotiq.cli:cli_main"`
- `build` added to `[project.optional-dependencies] dev`

The wheel must contain three categories of files:
1. **Python modules** -- all `.py` files under `src/godotiq/` (including subdirectories for tools, parsers, cache, bridge, etc.)
2. **Addon package data** -- `plugin.cfg` and 5 `.gd` files mapped from `godot-addon/addons/godotiq/` into `godotiq/addon/`
3. **Prompt files** -- `godot_development.md` (and any other `.md` files) from `src/godotiq/prompts/`

The wheel must NOT contain `tests/`, `docs/`, `.git/`, or other development artifacts.

## Tests First

Create the file `/Users/salvatorefarruggio/Desktop/godotiq/tests/test_build_verify.py` with the following tests.

### Wheel Content Tests

These tests inspect the built wheel to confirm all expected files are present and no unexpected files are included. They require a wheel to have been built first (via `python -m build`). Use `zipfile` to inspect the `.whl` file (which is a zip archive).

```python
"""Build verification tests for the GodotIQ wheel.

These tests inspect the contents of the built wheel to confirm
all expected package data and modules are included.

Requires: a wheel already built in dist/ (run `python -m build` first).
"""

import zipfile
from pathlib import Path

import pytest

DIST_DIR = Path(__file__).resolve().parent.parent / "dist"


def _find_wheel() -> Path:
    """Find the most recent .whl file in dist/."""
    wheels = sorted(DIST_DIR.glob("godotiq-*.whl"))
    if not wheels:
        pytest.skip("No wheel found in dist/. Run `python -m build` first.")
    return wheels[-1]


def _wheel_namelist() -> list[str]:
    """Return the list of file paths inside the wheel."""
    whl = _find_wheel()
    with zipfile.ZipFile(whl) as zf:
        return zf.namelist()


class TestWheelContainsAddonFiles:
    """Wheel must include addon package data from force-include."""

    def test_wheel_contains_plugin_cfg(self) -> None:
        names = _wheel_namelist()
        assert any(n.endswith("addon/plugin.cfg") for n in names)

    def test_wheel_contains_gd_files(self) -> None:
        names = _wheel_namelist()
        gd_files = [n for n in names if "addon/" in n and n.endswith(".gd")]
        assert len(gd_files) >= 5, f"Expected >=5 .gd files in addon/, got {gd_files}"


class TestWheelContainsPrompts:
    """Wheel must include prompt markdown files."""

    def test_wheel_contains_godot_development_md(self) -> None:
        names = _wheel_namelist()
        assert any("prompts/godot_development.md" in n for n in names)


class TestWheelExclusions:
    """Wheel must NOT contain test or doc files."""

    def test_wheel_excludes_tests_directory(self) -> None:
        names = _wheel_namelist()
        test_files = [n for n in names if n.startswith("tests/")]
        assert test_files == [], f"Unexpected test files in wheel: {test_files}"

    def test_wheel_excludes_docs_directory(self) -> None:
        names = _wheel_namelist()
        doc_files = [n for n in names if n.startswith("docs/")]
        assert doc_files == [], f"Unexpected doc files in wheel: {doc_files}"


class TestInstalledPackage:
    """Tests that verify the installed package works correctly."""

    def test_version_importable(self) -> None:
        """__version__ is importable from the top-level package."""
        from godotiq import __version__
        assert isinstance(__version__, str)
        assert __version__ != "0.0.0-dev"

    def test_addon_package_importable(self) -> None:
        """godotiq.addon is importable and its directory exists."""
        import godotiq.addon
        addon_dir = Path(godotiq.addon.__file__).parent
        assert addon_dir.is_dir()
        assert (addon_dir / "plugin.cfg").exists()

    def test_addon_has_gd_files(self) -> None:
        """Addon directory contains at least 5 .gd files."""
        import godotiq.addon
        addon_dir = Path(godotiq.addon.__file__).parent
        gd_files = list(addon_dir.glob("*.gd"))
        assert len(gd_files) >= 5
```

## Implementation Steps

### Step 1: Build the Wheel

Run the build from the project root:

```bash
python -m build
```

This produces two files in `dist/`:
- `godotiq-0.1.0-py3-none-any.whl` (the wheel)
- `godotiq-0.1.0.tar.gz` (the sdist)

If `build` is not installed, install it first: `pip install build`.

### Step 2: Inspect Wheel Contents

Verify the wheel contains all required files:

```bash
unzip -l dist/godotiq-0.1.0-py3-none-any.whl
```

**Expected contents checklist:**

| Category | Files | Expected |
|----------|-------|----------|
| Python package | `godotiq/__init__.py` | Has `__version__` |
| Python package | `godotiq/server.py` | Uses dynamic version in ping |
| Python package | `godotiq/cli.py` | New CLI module |
| Python package | `godotiq/__main__.py` | Routes through `cli_main` |
| Addon data | `godotiq/addon/__init__.py` | Path anchor |
| Addon data | `godotiq/addon/plugin.cfg` | Godot plugin config |
| Addon data | `godotiq/addon/godotiq_plugin.gd` | Plugin script |
| Addon data | `godotiq/addon/godotiq_runtime.gd` | Runtime script |
| Addon data | `godotiq/addon/godotiq_debugger.gd` | Debugger script |
| Addon data | `godotiq/addon/godotiq_server.gd` | Server script |
| Addon data | `godotiq/addon/godotiq_logger.gd` | Logger script |
| Prompts | `godotiq/prompts/godot_development.md` | MCP prompt file |
| Metadata | `godotiq-0.1.0.dist-info/METADATA` | PyPI metadata |
| Metadata | `godotiq-0.1.0.dist-info/entry_points.txt` | `godotiq = godotiq.cli:cli_main` |

**Must NOT contain:**
- `tests/` directory or any test files
- `docs/` directory
- `godot-addon/` (the source path -- mapped to `godotiq/addon/`)

### Step 3: Install in a Clean Virtual Environment

```bash
python -m venv /tmp/godotiq-verify-venv
source /tmp/godotiq-verify-venv/bin/activate
pip install dist/godotiq-0.1.0-py3-none-any.whl
```

### Step 4: Verify CLI Commands

```bash
godotiq --version    # Expected: godotiq 0.1.0
godotiq --help       # Expected: shows serve (default) and install-addon
godotiq install-addon --help  # Expected: shows path argument and --dry-run
```

### Step 5: Verify MCP Server Starts

```bash
python -c "from godotiq import __version__; print(__version__)"
# Expected: 0.1.0

python -c "
from godotiq.server import mcp
import asyncio

async def check():
    tools = await mcp.list_tools()
    print(f'Tools registered: {len(tools)}')
    assert len(tools) >= 35, f'Expected >= 35 tools, got {len(tools)}'
    print('OK')

asyncio.run(check())
"
```

### Step 6: Verify install-addon with a Mock Project

```bash
mkdir -p /tmp/test-godot-project
touch /tmp/test-godot-project/project.godot

godotiq install-addon --dry-run /tmp/test-godot-project/
godotiq install-addon /tmp/test-godot-project/

ls -la /tmp/test-godot-project/addons/godotiq/
cat /tmp/test-godot-project/GODOTIQ_RULES.md | head -5

rm -rf /tmp/test-godot-project /tmp/godotiq-verify-venv
```

### Step 7: Verify python -m godotiq

```bash
python -m godotiq --version
python -m godotiq --help
```

### Step 8: Verify entry_points.txt in Wheel

```bash
python -c "
import zipfile
from pathlib import Path

whl = sorted(Path('dist').glob('godotiq-*.whl'))[-1]
with zipfile.ZipFile(whl) as zf:
    for name in zf.namelist():
        if 'entry_points' in name:
            print(zf.read(name).decode())
"
```

Expected output should contain:
```
[console_scripts]
godotiq = godotiq.cli:cli_main
```

### Step 9: Run Full Test Suite

```bash
pytest -v  # All 1128+ tests must pass
pytest tests/test_build_verify.py -v  # New build verification tests
```

## Files to Create

| File | Action | Purpose |
|------|--------|---------|
| `tests/test_build_verify.py` | **Create** | Automated wheel content and install verification tests |

No other code files are created or modified in this section.

## Troubleshooting

- **Addon files missing from wheel:** Check `[tool.hatch.build.targets.wheel.force-include]` in pyproject.toml. Rebuild.
- **Prompt files missing:** Hatchling includes VCS-tracked files by default. If missing, add to `force-include`.
- **`godotiq` command not found:** Verify `[project.scripts]` entry point. Reinstall.
- **Tool count < 35:** Check tool module imports in `server.py` lines 83-89.
- **`test_build_verify.py` skips:** No `.whl` in `dist/`. Run `python -m build` first.

## Success Criteria

1. `python -m build` completes without errors
2. Wheel contains all addon `.gd` files and `plugin.cfg` under `godotiq/addon/`
3. Wheel contains `godotiq/prompts/godot_development.md`
4. Wheel does NOT contain `tests/` or `docs/`
5. `godotiq --version` prints correct version in clean venv
6. `godotiq install-addon` copies all files correctly
7. `python -m godotiq --version` works
8. All existing tests pass
9. All new `test_build_verify.py` tests pass

## Implementation Notes (Post-Build)

### Build results
- `python -m build` produced `godotiq-0.1.0-py3-none-any.whl` and `godotiq-0.1.0.tar.gz`
- Wheel contains 65 files total: all Python modules, 5 .gd + plugin.cfg in addon/, 2 prompt .md files
- No tests/ or docs/ in wheel
- Entry point: `godotiq = godotiq.cli:cli_main`
- 35 MCP tools registered
- `python -m godotiq --version` prints `godotiq 0.1.0`

### Files created
- `tests/test_build_verify.py` — 8 tests (7 pass, 1 skip in editable mode)

### Test results
- 1169 passed, 3 skipped (full suite, no regressions)
