about summary refs log tree commit diff
path: root/tests/test_gn3_smoke.py
blob: b3e4e9f501d45a1197b9d45a26737e9bc51cda21 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
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}"
    )