Spaces:
Running
Running
| """Endpoint tests for the unified saved-work hub: | |
| - Primer Analysis persistence (/api/primers/{save,analyses,analyses/<id>}) | |
| - 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, | |
| } | |
| 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 | |