diff options
Diffstat (limited to 'tests/test_gn3_smoke.py')
| -rw-r--r-- | tests/test_gn3_smoke.py | 215 |
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}" + ) |
