about summary refs log tree commit diff
path: root/tests/test_gn3_smoke.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_gn3_smoke.py')
-rw-r--r--tests/test_gn3_smoke.py215
1 files changed, 215 insertions, 0 deletions
diff --git a/tests/test_gn3_smoke.py b/tests/test_gn3_smoke.py
new file mode 100644
index 0000000..b3e4e9f
--- /dev/null
+++ b/tests/test_gn3_smoke.py
@@ -0,0 +1,215 @@
+"""
+Smoke tests for the genenetwork3 REST API.
+
+All tests hit public, unauthenticated endpoints.  The gn3 API is proxied
+at /api3/ on the gn2 domain; nginx rewrites /api3/<path> → /api/<path>
+before forwarding to gn3, so the URL seen by the caller is:
+
+    https://cd.genenetwork.org/api3/metadata/species
+    (→ gn3 internal: /api/metadata/species)
+
+Response format note: gn3 metadata endpoints return JSON-LD, shaped as:
+
+    {"@context": {...}, "data": [...]}
+
+The actual payload is always in the "data" key.  On the CD environment the
+RDF/SPARQL store may be empty, so "data" can be []; production will have
+real entries.  Content assertions (e.g. "contains mouse") are therefore
+skipped when the data list is empty.
+
+Run with:
+
+    pytest -m "gn3 and smoke"
+
+Known CD/production bugs (as of 2026-05-27):
+  - POST /api3/metadata/datasets/edit returns 500 on both CD and production
+    due to TypeError in privileges_fulfill_specs() — tracked separately.
+"""
+
+import pytest
+
+
+pytestmark = [pytest.mark.gn3, pytest.mark.smoke]
+
+# ---------------------------------------------------------------------------
+# Known-good test data (sourced from existing gn2/gn3 test fixtures)
+# ---------------------------------------------------------------------------
+_KNOWN_DATASET = "HC_M2_0606_P"   # Hippocampus Consortium M430v2 Jun06 PDNN
+_KNOWN_SPECIES = "mouse"
+_KNOWN_GROUP = "BXD"
+_KNOWN_PROBESET = "1435395_s_at"
+_KNOWN_INBREDSET_ID = 1           # BXD inbredset_id in the database
+
+
+def _jsonld_data(resp):
+    """Return the 'data' list from a JSON-LD gn3 response."""
+    body = resp.json()
+    assert isinstance(body, dict), f"Expected JSON object, got: {type(body)}"
+    assert "data" in body, f"Missing 'data' key in JSON-LD response: {list(body.keys())}"
+    assert isinstance(body["data"], list), (
+        f"'data' should be a list, got {type(body['data'])}"
+    )
+    return body["data"]
+
+
+# ---------------------------------------------------------------------------
+# GET /api3/metadata/species
+# ---------------------------------------------------------------------------
+
+class TestMetadataSpecies:
+    def test_returns_200(self, gn3_url, http):
+        resp = http.get(f"{gn3_url}/metadata/species", timeout=30)
+        assert resp.status_code == 200
+
+    def test_response_is_jsonld_with_data_key(self, gn3_url, http):
+        resp = http.get(f"{gn3_url}/metadata/species", timeout=30)
+        data = _jsonld_data(resp)  # asserts structure internally
+        assert isinstance(data, list)
+
+    def test_contains_mouse_when_data_populated(self, gn3_url, http):
+        resp = http.get(f"{gn3_url}/metadata/species", timeout=30)
+        items = _jsonld_data(resp)
+        if not items:
+            pytest.skip("Species data is empty in this environment (RDF store not populated)")
+        all_names = [
+            s.get("fullName", s.get("name", s.get("shortName", "")))
+            for s in items
+        ]
+        assert any("mouse" in n.lower() or "mus" in n.lower() for n in all_names), (
+            f"Mouse not found in species list: {all_names}"
+        )
+
+    def test_species_entry_schema_when_data_populated(self, gn3_url, http):
+        resp = http.get(f"{gn3_url}/metadata/species", timeout=30)
+        items = _jsonld_data(resp)
+        if not items:
+            pytest.skip("Species data is empty in this environment")
+        entry = items[0]
+        assert any(k in entry for k in ("fullName", "name", "shortName")), (
+            f"Species entry has no recognisable name field: {entry}"
+        )
+
+
+# ---------------------------------------------------------------------------
+# GET /api3/metadata/species/<name>
+# ---------------------------------------------------------------------------
+
+class TestMetadataSpeciesByName:
+    def test_known_species_returns_200(self, gn3_url, http):
+        resp = http.get(f"{gn3_url}/metadata/species/{_KNOWN_SPECIES}", timeout=30)
+        assert resp.status_code == 200, (
+            f"Expected 200 for species '{_KNOWN_SPECIES}', "
+            f"got {resp.status_code}: {resp.text}"
+        )
+
+    def test_unknown_species_does_not_500(self, gn3_url, http):
+        resp = http.get(f"{gn3_url}/metadata/species/nonexistent_xyz", timeout=30)
+        assert resp.status_code != 500, (
+            f"Unknown species caused 500: {resp.text}"
+        )
+
+
+# ---------------------------------------------------------------------------
+# GET /api3/metadata/groups
+# ---------------------------------------------------------------------------
+
+class TestMetadataGroups:
+    def test_returns_200(self, gn3_url, http):
+        resp = http.get(f"{gn3_url}/metadata/groups", timeout=30)
+        assert resp.status_code == 200
+
+    def test_response_is_jsonld_with_data_key(self, gn3_url, http):
+        resp = http.get(f"{gn3_url}/metadata/groups", timeout=30)
+        data = _jsonld_data(resp)
+        assert isinstance(data, list)
+
+    def test_contains_bxd_when_data_populated(self, gn3_url, http):
+        resp = http.get(f"{gn3_url}/metadata/groups", timeout=30)
+        items = _jsonld_data(resp)
+        if not items:
+            pytest.skip("Groups data is empty in this environment")
+        names = [g.get("name", g.get("shortName", "")) for g in items]
+        assert "BXD" in names, f"BXD not found in groups: {names[:20]}"
+
+
+# ---------------------------------------------------------------------------
+# GET /api3/metadata/datasets/<name>
+# ---------------------------------------------------------------------------
+
+class TestMetadataDatasets:
+    def test_known_dataset_returns_200(self, gn3_url, http):
+        resp = http.get(f"{gn3_url}/metadata/datasets/{_KNOWN_DATASET}", timeout=30)
+        assert resp.status_code == 200, (
+            f"Expected 200 for dataset '{_KNOWN_DATASET}', "
+            f"got {resp.status_code}: {resp.text}"
+        )
+
+    def test_known_dataset_response_is_jsonld(self, gn3_url, http):
+        resp = http.get(f"{gn3_url}/metadata/datasets/{_KNOWN_DATASET}", timeout=30)
+        body = resp.json()
+        assert isinstance(body, dict), f"Expected JSON object, got {type(body)}"
+        assert "@context" in body or "data" in body, (
+            f"Response does not look like JSON-LD: {list(body.keys())}"
+        )
+
+    def test_unknown_dataset_does_not_500(self, gn3_url, http):
+        resp = http.get(f"{gn3_url}/metadata/datasets/NONEXISTENT_XYZ", timeout=30)
+        assert resp.status_code != 500, (
+            f"Unknown dataset caused 500: {resp.text}"
+        )
+
+
+# ---------------------------------------------------------------------------
+# GET /api3/metadata/probesets/<dataset>/<name>
+# ---------------------------------------------------------------------------
+
+class TestMetadataProbesets:
+    def test_known_probeset_returns_200(self, gn3_url, http):
+        resp = http.get(
+            f"{gn3_url}/metadata/probesets/{_KNOWN_DATASET}/{_KNOWN_PROBESET}",
+            timeout=30,
+        )
+        assert resp.status_code == 200, (
+            f"Expected 200 for probeset '{_KNOWN_DATASET}/{_KNOWN_PROBESET}', "
+            f"got {resp.status_code}: {resp.text}"
+        )
+
+
+# ---------------------------------------------------------------------------
+# GET /api3/case-attribute/<inbredset_id>
+# ---------------------------------------------------------------------------
+
+class TestCaseAttributes:
+    def test_known_inbredset_does_not_500(self, gn3_url, http):
+        resp = http.get(
+            f"{gn3_url}/case-attribute/{_KNOWN_INBREDSET_ID}",
+            timeout=30,
+        )
+        assert resp.status_code != 500, (
+            f"case-attribute inbredset_id={_KNOWN_INBREDSET_ID} caused 500: {resp.text}"
+        )
+
+
+# ---------------------------------------------------------------------------
+# Protected endpoints — known bug
+# ---------------------------------------------------------------------------
+
+@pytest.mark.xfail(
+    reason=(
+        "BUG: POST /api3/metadata/datasets/edit returns 500 on both CD and "
+        "production due to TypeError: privileges_fulfill_specs() missing 1 "
+        "required positional argument: 'system_privileges' "
+        "(gn3/api/metadata.py:279). Should return 401 without a token."
+    ),
+    strict=True,
+)
+def test_metadata_edit_without_token_returns_401(gn3_url, http):
+    resp = http.post(
+        f"{gn3_url}/metadata/datasets/edit",
+        json={},
+        timeout=30,
+    )
+    assert resp.status_code == 401, (
+        f"Expected 401 on /metadata/datasets/edit without token, "
+        f"got {resp.status_code}: {resp.text}"
+    )