""" 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/ → /api/ 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/ # --------------------------------------------------------------------------- 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/ # --------------------------------------------------------------------------- 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// # --------------------------------------------------------------------------- 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/ # --------------------------------------------------------------------------- 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}" )