Spaces:
Sleeping
Sleeping
| """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" | |
| 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 | |