"""Endpoint tests for the unified saved-work hub: - Primer Analysis persistence (/api/primers/{save,analyses,analyses/}) - Directed-Evolution library list + signed download (/api/library …) Flask test client; the auth + DB layer is stubbed (no network, no DB).""" import types import pytest from dee import server GOOD_RESULT = { "primers": [{"name": "F1", "fitness": 0.8}, {"name": "R1", "fitness": 0.7}], "has_template": True, } @pytest.fixture def client(): app = server.create_app() app.config.update(TESTING=True) return app.test_client() def _signed_in(monkeypatch): monkeypatch.setattr(server._auth, "get_auth", lambda: types.SimpleNamespace(anonymous=False, user_id="u1", email="x@y.z", plan="free")) # Lazy sweep is fire-and-forget; stub it so tests never touch the network. monkeypatch.setattr(server._auth, "cleanup_expired_primer_analyses_async", lambda *a, **k: None) def _anon(monkeypatch): monkeypatch.setattr(server._auth, "get_auth", lambda: types.SimpleNamespace(anonymous=True, user_id=None, email=None, plan="anon")) # ─────────────────────────── Primer save ─────────────────────────── def test_primer_save_requires_signin(client, monkeypatch): _anon(monkeypatch) r = client.post("/api/primers/save", json={"result": GOOD_RESULT}) assert r.status_code == 403 assert r.get_json()["kind"] == "signin_required" def test_primer_save_rejects_empty(client, monkeypatch): _signed_in(monkeypatch) r = client.post("/api/primers/save", json={"result": {"primers": []}}) assert r.status_code == 400 def test_primer_save_passes_through(client, monkeypatch): _signed_in(monkeypatch) captured = {} def fake_save(user_id, **kw): captured["user_id"] = user_id captured.update(kw) return {"ok": True, "id": "abc"} monkeypatch.setattr(server._auth, "save_primer_analysis", fake_save) r = client.post("/api/primers/save", json={ "label": "my set", "organism": "human", "template_len": 700, "n_primers": 2, "result": GOOD_RESULT}) assert r.status_code == 200 assert r.get_json() == {"ok": True, "id": "abc"} assert captured["user_id"] == "u1" # server-supplied, not client assert captured["n_primers"] == 2 assert captured["result"]["primers"] # result forwarded intact # ─────────────────────── Primer list / get / delete ─────────────────────── def test_primer_list_requires_signin(client, monkeypatch): _anon(monkeypatch) assert client.get("/api/primers/analyses").status_code == 403 def test_primer_list(client, monkeypatch): _signed_in(monkeypatch) monkeypatch.setattr(server._auth, "list_primer_analyses", lambda uid: [{"id": "1", "label": "set A", "n_primers": 3}]) r = client.get("/api/primers/analyses") assert r.status_code == 200 assert r.get_json()["analyses"][0]["label"] == "set A" def test_primer_get_and_404(client, monkeypatch): _signed_in(monkeypatch) monkeypatch.setattr(server._auth, "get_primer_analysis", lambda uid, aid: ({"id": aid, "result": GOOD_RESULT} if aid == "ok" else None)) assert client.get("/api/primers/analyses/ok").status_code == 200 assert client.get("/api/primers/analyses/missing").status_code == 404 def test_primer_delete(client, monkeypatch): _signed_in(monkeypatch) monkeypatch.setattr(server._auth, "delete_primer_analysis", lambda uid, aid: {"ok": True}) r = client.delete("/api/primers/analyses/xyz") assert r.status_code == 200 and r.get_json()["ok"] is True # ───────────────── DE library list + signed download ───────────────── def test_library_list_requires_signin(client, monkeypatch): _anon(monkeypatch) assert client.get("/api/library").status_code == 403 def test_library_list(client, monkeypatch): _signed_in(monkeypatch) monkeypatch.setattr(server._auth, "list_libraries", lambda uid: [{"id": "L1", "name": "lib", "n_variants": 96}]) r = client.get("/api/library") assert r.status_code == 200 assert r.get_json()["libraries"][0]["n_variants"] == 96 def test_library_download_signed_url(client, monkeypatch): _signed_in(monkeypatch) monkeypatch.setattr(server._auth, "library_csv_url", lambda uid, lid: ("https://example.test/signed.csv" if lid == "L1" else None)) ok = client.get("/api/library/L1/download") assert ok.status_code == 200 and ok.get_json()["url"].endswith("signed.csv") assert client.get("/api/library/nope/download").status_code == 404 # ───────────────── Graceful degradation (no Supabase configured) ───────────────── def test_save_primer_analysis_unconfigured_is_graceful(monkeypatch): monkeypatch.setattr(server._auth, "SUPABASE_URL", "", raising=False) monkeypatch.setattr(server._auth, "SUPABASE_SERVICE_KEY", "", raising=False) out = server._auth.save_primer_analysis( "u1", label="x", organism=None, template_len=10, n_primers=1, result={"primers": [{"name": "F"}]}) assert out["ok"] is False and "error" in out