File size: 3,880 Bytes
39ff632
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37e3d5a
39ff632
 
 
 
37e3d5a
 
 
 
39ff632
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"""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