Automate checklist-backend updates

This commit is contained in:
sriram
2026-08-31 15:32:30 +05:30
parent 3df2dc5991
commit 164ef90b31
6 changed files with 221 additions and 21 deletions

View File

@@ -0,0 +1,121 @@
"""The reachability probe in front of every Ollama call.
WHY THIS FILE EXISTS
--------------------
`_ensure_client()` asks Ollama for `/api/tags` with a 5-second timeout, and
`stage_2_row_intake` calls it once per ROW through `fetch_product_details`.
Uncached, a 2000-row sheet ingested with `use_llm` on, against a configured but
unreachable Ollama, spends up to ~2.8 hours doing nothing but timing out - and
shows as a batch that has hung, not one that has failed.
That was survivable only while `use_llm` defaulted to false everywhere. It no
longer does: `UPLOAD_AUTORUN_USE_LLM` is true, so every auto-started upload now
takes this path. The cache is what makes that default safe, and the first test
below is the one that stops it being quietly removed in a later refactor.
`/api/health` calls the same function, so a down Ollama also stops adding five
seconds to every health request.
"""
from __future__ import annotations
import pytest
from app.services import ollama_service
@pytest.fixture(autouse=True)
def _clean_probe_cache():
"""The cache is a module global and outlives a test."""
ollama_service.reset_reachability_cache()
yield
ollama_service.reset_reachability_cache()
@pytest.fixture
def probe_calls(monkeypatch):
"""Count the HTTP probes, and make every one of them fail.
Failure is the case that matters: a reachable Ollama answers in
milliseconds, an unreachable one costs the full timeout, and it is the
second that used to be paid per row.
"""
calls: list = []
def boom(url, **kwargs):
calls.append(url)
raise OSError("connection refused")
monkeypatch.setattr(ollama_service, "USE_OLLAMA", True)
monkeypatch.setattr(ollama_service.requests, "get", boom)
return calls
def test_an_unreachable_ollama_is_probed_once_not_once_per_call(probe_calls):
"""The whole point. Ten rows must not be ten timeouts."""
for _ in range(10):
assert ollama_service._ensure_client() is False
assert len(probe_calls) == 1, (
f"{len(probe_calls)} probes for 10 calls - the cache is not holding, and "
f"an ingest will pay the 5s timeout per row"
)
def test_the_cache_expires_so_a_late_start_is_noticed(probe_calls, monkeypatch):
"""A permanent memo would mean an Ollama started after the API is never
seen, and /api/health reports it down until someone redeploys."""
clock = [1000.0]
monkeypatch.setattr(ollama_service.time, "monotonic", lambda: clock[0])
ollama_service._ensure_client()
assert len(probe_calls) == 1
clock[0] += ollama_service._PROBE_TTL_SECONDS + 1
ollama_service._ensure_client()
assert len(probe_calls) == 2, "the probe never expired"
def test_a_reachable_ollama_is_also_cached(monkeypatch):
"""Both outcomes are cached. Caching only the failure would leave the happy
path paying an HTTP round trip per row - cheap, but per row and pointless."""
calls: list = []
class Ok:
status_code = 200
def ok(url, **kwargs):
calls.append(url)
return Ok()
monkeypatch.setattr(ollama_service, "USE_OLLAMA", True)
monkeypatch.setattr(ollama_service.requests, "get", ok)
assert [ollama_service._ensure_client() for _ in range(5)] == [True] * 5
assert len(calls) == 1
def test_disabled_stays_none_and_never_touches_the_network(monkeypatch):
"""Three return values, not two: `system.py` tells "switched off" from
"configured but down", and /api/health's `ollama` field means different
things in each case. Collapsing this to a bool would break that.
"""
def never(*_args, **_kwargs):
raise AssertionError("USE_OLLAMA is false - nothing may be requested")
monkeypatch.setattr(ollama_service, "USE_OLLAMA", False)
monkeypatch.setattr(ollama_service.requests, "get", never)
assert ollama_service._ensure_client() is None
def test_a_row_keeps_its_description_when_ollama_is_absent(probe_calls):
"""The pipeline's side of the contract. `use_llm` being on must be a no-op
against an unreachable server, not a failure - stage 2 is best-effort and
every later stage has to keep working."""
from app.core.store_catalog_pipeline import stage_2_row_intake
row = stage_2_row_intake(
{"product_name": "Amul Butter 100g", "brand": "Amul", "description": ""},
use_llm=True,
)
assert row["description"] == ""

View File

@@ -127,31 +127,44 @@ def test_the_run_is_attributed_to_the_sender_label(client):
# ---------------------------------------------------------------------------
# What the anonymous caller still cannot do
# ---------------------------------------------------------------------------
def test_the_sender_cannot_switch_on_the_expensive_stages(client):
def test_the_request_cannot_move_either_stage(client):
"""Image search and the LLM reach the network and are chosen by settings,
never by the request. An anonymous caller who could flip these could commit
a one-vCPU host to outbound work at will."""
a one-vCPU host to outbound work at will.
Both fields are sent OPPOSITE to their configured values, which is the only
way this proves anything: if the endpoint started honouring the request, the
response would have to disagree with the settings, and it must not.
"""
body = client.post(
UPLOAD,
files=_files(("a.csv", _csv())),
data={"use_llm": "true", "fetch_images": "false"},
data={"use_llm": "false", "fetch_images": "false"},
).json()
# The configured values, not the ones asked for.
assert body["use_llm"] is False
assert body["use_llm"] is True
assert body["fetch_images"] is True
def test_both_stages_are_on_for_an_auto_started_run(client):
"""The requirement, pinned against the shipped defaults rather than against
a monkeypatched pair - so flipping either default in settings.py fails here
rather than passing quietly."""
body = client.post(UPLOAD, files=_files(("a.csv", _csv()))).json()
assert body["fetch_images"] is True
assert body["use_llm"] is True
def test_the_configured_defaults_are_what_reach_the_run(client, monkeypatch):
"""Both directions, so a wrong default cannot hide behind a matching one."""
from app.api.routers import uploads
monkeypatch.setattr(uploads, "UPLOAD_AUTORUN_FETCH_IMAGES", False)
monkeypatch.setattr(uploads, "UPLOAD_AUTORUN_USE_LLM", True)
monkeypatch.setattr(uploads, "UPLOAD_AUTORUN_USE_LLM", False)
body = client.post(UPLOAD, files=_files(("a.csv", _csv()))).json()
assert body["fetch_images"] is False
assert body["use_llm"] is True
assert body["use_llm"] is False
# ---------------------------------------------------------------------------