"""Shared pytest fixtures for the img2threejs Space test suite.""" from __future__ import annotations import json import struct import sys import zlib from pathlib import Path import pytest REPO_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(REPO_ROOT)) FIXTURES = Path(__file__).resolve().parent / "fixtures" def write_png(path: Path, w: int = 64, h: int = 64) -> Path: """Write a minimal valid RGB PNG with a gradient (no Pillow needed).""" raw = bytearray() for y in range(h): raw.append(0) for x in range(w): raw += bytes(((x * 4) % 256, (y * 4) % 256, ((x + y) * 2) % 256)) def chunk(tag: bytes, data: bytes) -> bytes: c = struct.pack(">I", len(data)) + tag + data return c + struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF) ihdr = struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0) png = (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr) + chunk(b"IDAT", zlib.compress(bytes(raw), 9)) + chunk(b"IEND", b"")) path.write_bytes(png) return path @pytest.fixture(scope="session") def ref_png_bytes() -> bytes: import tempfile with tempfile.TemporaryDirectory() as tmp: path = write_png(Path(tmp) / "ref.png") return path.read_bytes() @pytest.fixture(scope="session") def canned_spec() -> dict: return json.loads((FIXTURES / "canned_spec.json").read_text(encoding="utf-8")) @pytest.fixture(scope="session") def canned_spec_text() -> str: return (FIXTURES / "canned_spec.json").read_text(encoding="utf-8") class MockLLM: """Drop-in for app.llm.LLMClient returning queued canned replies. replies: list of strings returned in order (last one repeats). Records every call for assertions. """ def __init__(self, replies: list[str]) -> None: from app.llm import LLMResponse self._response_cls = LLMResponse self.replies = list(replies) self.calls: list[dict] = [] async def complete_vision(self, *, system: str, messages: list[dict]): self.calls.append({"system": system, "messages": messages}) index = min(len(self.calls) - 1, len(self.replies) - 1) return self._response_cls( text=self.replies[index], stop_reason="end_turn", style="anthropic") @pytest.fixture() def make_settings(): """Build Settings instances from a controlled environment.""" from app.config import Settings def _make(**overrides): import os runs_dir = overrides.pop("runs_dir", "/tmp/i2t-test-runs") env = { "LLM_API_KEY": "test-key-not-real", "LLM_MODEL": "test-model", "LLM_BASE_URL": "https://llm.test/api", "RUNS_DIR": runs_dir, "GALLERY_DIR": overrides.pop( "gallery_dir", str(Path(runs_dir).parent / "gallery") ), } env.update({k: str(v) for k, v in overrides.items()}) old = {k: os.environ.get(k) for k in env} os.environ.update(env) try: return Settings() finally: for key, value in old.items(): if value is None: os.environ.pop(key, None) else: os.environ[key] = value return _make @pytest.fixture(scope="session") def factory_fixture_ts() -> Path: """tests/fixtures/factory_fixture.ts, regenerated through the real pipeline when missing (pure stdlib — always runnable).""" target = FIXTURES / "factory_fixture.ts" if not target.exists(): import subprocess proc = subprocess.run( [sys.executable, str(REPO_ROOT / "scripts" / "build_fixture_factory.py")], capture_output=True, text=True) assert proc.returncode == 0, f"fixture build failed:\n{proc.stdout}\n{proc.stderr}" return target