"""Shared fixtures for Marionette tests.""" import io import json import struct import wave from pathlib import Path import pytest from fastapi.testclient import TestClient from marionette.main import Marionette, create_app def make_wav_bytes(duration: float = 1.0, sample_rate: int = 44100) -> bytes: """Build a valid WAV file in memory using only stdlib. Returns raw bytes suitable for uploading via TestClient. """ n_frames = int(duration * sample_rate) buf = io.BytesIO() with wave.open(buf, "wb") as wf: wf.setnchannels(1) wf.setsampwidth(2) # 16-bit wf.setframerate(sample_rate) # Write silence (all zeros) wf.writeframes(struct.pack(f"<{n_frames}h", *([0] * n_frames))) return buf.getvalue() def pytest_collection_modifyitems(config, items): """Auto-skip @pytest.mark.hardware tests unless -m hardware is given.""" marker_expr = config.getoption("-m", default="") if "hardware" in marker_expr: return skip_hw = pytest.mark.skip(reason="hardware tests require -m hardware") for item in items: if "hardware" in item.keywords: item.add_marker(skip_hw) @pytest.fixture() def tmp_registry(tmp_path: Path) -> Path: """Return a path for a temporary dataset registry file.""" return tmp_path / "dataset_registry.json" @pytest.fixture() def tmp_dataset_root(tmp_path: Path) -> Path: """Return a temporary directory for datasets.""" root = tmp_path / "datasets" root.mkdir() return root @pytest.fixture() def marionette_app(tmp_registry: Path, tmp_dataset_root: Path) -> tuple[TestClient, Marionette]: """Create a Marionette app with TestClient, using isolated temp paths.""" app, m = create_app(registry_path=tmp_registry, dataset_root=tmp_dataset_root) client = TestClient(app) return client, m @pytest.fixture() def client(marionette_app: tuple[TestClient, Marionette]) -> TestClient: """Convenience fixture: just the TestClient.""" return marionette_app[0] @pytest.fixture() def marionette(marionette_app: tuple[TestClient, Marionette]) -> Marionette: """Convenience fixture: just the Marionette instance.""" return marionette_app[1] @pytest.fixture() def sample_move_json() -> dict: """A minimal valid move JSON structure matching RecordedMove expectations.""" timestamps = [i * 0.01 for i in range(500)] # 5 seconds at 100Hz frames = [] for _ in timestamps: frames.append({ "head": [ [1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0], ], "antennas": [0.0, 0.0], "body_yaw": 0.0, "check_collision": False, }) return { "description": "test move", "time": timestamps, "set_target_data": frames, }