TICKET-006 (backend): OFF normalization, scan/search/refresh, restore-on-rescan
- 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)
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
# Must be set before any app module is imported (engine binds at import time)
|
||||
@@ -20,3 +21,34 @@ 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
|
||||
|
||||
Reference in New Issue
Block a user