32461b7405
- services/off.py: single normalizer module (kcal/kJ fallback, not-found
rule); fetch_product/search_off degrade gracefully on upstream errors
(503/timeout → None / []), never 500; User-Agent + timeouts on all calls
- routers/off.py: thin routes for GET /api/off/product/{barcode},
GET /api/off/search, POST /api/off/refresh/{food_id} (404 unknown id,
400 no-barcode)
- services/foods.py: restore-on-rescan (§3.1) — barcode collision on a
soft-deleted food clears deleted_at + updates row instead of 409;
GET /api/foods/recent ordered by most-recent daily_log appearance,
deduped, soft-deleted excluded
- tests: httpx mocked at the boundary (MockTransport, no real OFF);
48 new tests (normalizer unit + router + restore + recent + error paths)
- 152 passing (was 104)
55 lines
1.4 KiB
Python
55 lines
1.4 KiB
Python
"""Test fixtures: fresh temp-file SQLite DB per test session (spec §8.4)."""
|
|
|
|
import os
|
|
import tempfile
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
# Must be set before any app module is imported (engine binds at import time)
|
|
_tmpdir = tempfile.mkdtemp(prefix="calcount-test-")
|
|
os.environ["CALCOUNT_DATABASE_URL"] = f"sqlite:///{_tmpdir}/test.db"
|
|
|
|
from fastapi.testclient import TestClient # noqa: E402
|
|
|
|
from database import run_migrations # noqa: E402
|
|
from main import app # noqa: E402
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def client():
|
|
run_migrations()
|
|
with TestClient(app) as c:
|
|
yield c
|
|
|
|
|
|
@pytest.fixture
|
|
def off_mock(monkeypatch):
|
|
"""Replace _default_client() in services.off with a mock client backed
|
|
by httpx.MockTransport. Tests assign off_mock.handler to control
|
|
responses.
|
|
|
|
Usage:
|
|
def test_foo(client, off_mock):
|
|
def handler(request):
|
|
return httpx.Response(200, json={...})
|
|
off_mock.handler = handler
|
|
resp = client.get("/api/off/product/123")
|
|
"""
|
|
state = _MockState()
|
|
|
|
def _make_mock_client():
|
|
return httpx.Client(
|
|
transport=httpx.MockTransport(lambda req: state.handler(req))
|
|
)
|
|
|
|
import services.off as off_mod
|
|
monkeypatch.setattr(off_mod, "_default_client", _make_mock_client)
|
|
return state
|
|
|
|
|
|
class _MockState:
|
|
"""Mutable state so tests can set .handler after fixture injection."""
|
|
def __init__(self):
|
|
self.handler = None
|