kink-discovery / tests /test_api_blockbuster.py
Perplexed7675's picture
Sync from kink_cli (Docker Space)
6ff91d6 verified
Raw
History Blame Contribute Delete
3.31 kB
"""Blockbuster checks for blocking sqlite/sync work on the asyncio loop.
FastAPI runs ``def`` route handlers in a thread pool, so typical sync routes do not
block the event loop. ``async def`` handlers must move sync SQLModel / sqlite work
off the loop with ``asyncio.to_thread``.
Seed DB: ``deploy/hf/seed/hf_bundled_store.db`` (small HF demo catalog).
"""
from __future__ import annotations
import asyncio
import shutil
import sys
from pathlib import Path
import httpx
import pytest
from blockbuster import blockbuster_ctx
from fastapi.testclient import TestClient
_SEED = Path(__file__).resolve().parent.parent / "deploy/hf/seed/hf_bundled_store.db"
@pytest.fixture()
def blockbuster_app(tmp_path, monkeypatch):
if not _SEED.is_file():
pytest.skip(f"Missing seed DB: {_SEED}")
dest = tmp_path / "blockbuster_store.db"
shutil.copyfile(_SEED, dest)
monkeypatch.setenv("KINK_STORE_PATH", str(dest))
monkeypatch.setenv("KINK_SKIP_HEAVY_WARM", "1")
monkeypatch.setenv("KINK_HF_REQUIRE_FULL_CATALOG", "0")
sys.modules.pop("api", None)
import api as api_mod
api_mod._backend_impl = None
api_mod._get_backend()
return api_mod
def test_async_post_users_does_not_block_loop_under_blockbuster(blockbuster_app):
"""``POST /users`` parses async body, then creates the user off the event loop."""
async def inner():
transport = httpx.ASGITransport(app=blockbuster_app.app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
with blockbuster_ctx(scanned_modules=["api"]):
r = await client.post("/users", json={})
assert r.status_code == 200
assert r.json()["id"]
asyncio.run(inner())
def test_sync_get_health_ok_under_blockbuster(blockbuster_app):
"""Sync ``GET /health`` runs in Starlette's thread pool — no loop blocking."""
async def inner():
transport = httpx.ASGITransport(app=blockbuster_app.app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
with blockbuster_ctx(scanned_modules=["api"]):
r = await client.get("/health")
assert r.status_code == 200
assert r.json().get("ok") is True
asyncio.run(inner())
def test_health_catalog_sample_returns_kink_id(blockbuster_app) -> None:
"""``GET /health/catalog-sample`` reads one row (used by deploy smoke, not full ``list_kinks``)."""
client = TestClient(blockbuster_app.app)
r = client.get("/health/catalog-sample")
assert r.status_code == 200
body = r.json()
assert body.get("ok") is True
assert body.get("sample_kink_id")
kid = body["sample_kink_id"]
d = client.get(f"/kinks/{kid}")
assert d.status_code == 200
assert d.json().get("id") == kid
def test_kinks_batch_matches_single_get(blockbuster_app) -> None:
"""Discover prefetches full cards via ``POST /kinks/batch`` — same payload as ``GET /kinks/:id``."""
client = TestClient(blockbuster_app.app)
kid = client.get("/health/catalog-sample").json()["sample_kink_id"]
one = client.get(f"/kinks/{kid}").json()
bat = client.post("/kinks/batch", json={"ids": [kid, "__missing__"]}).json()["items"]
assert len(bat) == 1
assert bat[0] == one