""" Auth-flow integration tests for genenetwork3 protected endpoints. Tests that GN3's `@require_token` decorator correctly rejects requests that carry no bearer token or a malformed/invalid token. These tests do not require database state — the rejection happens before any DB access. Endpoints under test are in `gn3/api/case_attributes.py`: POST /api/case-attribute//edit POST /api/case-attribute//approve/ POST /api/case-attribute//reject/ Via the nginx proxy the external URL prefix is `/api3/`, so the full CD URL for the first endpoint is: https://cd.genenetwork.org/api3/case-attribute/1/edit Run with: pytest -m "gn3 and auth_flow" """ import pytest pytestmark = [pytest.mark.gn3, pytest.mark.auth_flow] # Arbitrary but valid-looking id. The auth rejection happens before any # DB lookup so the exact value does not matter. _INBREDSET_ID = 1 _CHANGE_ID = 1 _INVALID_TOKEN = "Bearer this-is-not-a-valid-jwt" # --------------------------------------------------------------------------- # POST /case-attribute//edit — token enforcement # --------------------------------------------------------------------------- def test_edit_no_token_returns_400(gn3_url, http): resp = http.post( f"{gn3_url}/case-attribute/{_INBREDSET_ID}/edit", json={}, timeout=30, ) assert resp.status_code == 400, ( f"Expected 400 when no token supplied, got {resp.status_code}: {resp.text}" ) def test_edit_no_token_error_is_token_validation_error(gn3_url, http): resp = http.post( f"{gn3_url}/case-attribute/{_INBREDSET_ID}/edit", json={}, timeout=30, ) assert resp.json().get("error") == "TokenValidationError", ( f"Expected error='TokenValidationError', got: {resp.json()}" ) def test_edit_invalid_token_returns_400(gn3_url, http): resp = http.post( f"{gn3_url}/case-attribute/{_INBREDSET_ID}/edit", json={}, headers={"Authorization": _INVALID_TOKEN}, timeout=30, ) assert resp.status_code == 400, ( f"Expected 400 for invalid token, got {resp.status_code}: {resp.text}" ) def test_edit_invalid_token_error_is_token_validation_error(gn3_url, http): resp = http.post( f"{gn3_url}/case-attribute/{_INBREDSET_ID}/edit", json={}, headers={"Authorization": _INVALID_TOKEN}, timeout=30, ) assert resp.json().get("error") == "TokenValidationError", ( f"Expected error='TokenValidationError', got: {resp.json()}" ) # --------------------------------------------------------------------------- # POST /case-attribute//approve/ — token enforcement # --------------------------------------------------------------------------- def test_approve_no_token_returns_400(gn3_url, http): resp = http.post( f"{gn3_url}/case-attribute/{_INBREDSET_ID}/approve/{_CHANGE_ID}", json={}, timeout=30, ) assert resp.status_code == 400, ( f"Expected 400 when no token supplied, got {resp.status_code}: {resp.text}" ) def test_approve_no_token_error_is_token_validation_error(gn3_url, http): resp = http.post( f"{gn3_url}/case-attribute/{_INBREDSET_ID}/approve/{_CHANGE_ID}", json={}, timeout=30, ) assert resp.json().get("error") == "TokenValidationError", ( f"Expected error='TokenValidationError', got: {resp.json()}" ) def test_approve_invalid_token_returns_400(gn3_url, http): resp = http.post( f"{gn3_url}/case-attribute/{_INBREDSET_ID}/approve/{_CHANGE_ID}", json={}, headers={"Authorization": _INVALID_TOKEN}, timeout=30, ) assert resp.status_code == 400, ( f"Expected 400 for invalid token, got {resp.status_code}: {resp.text}" ) def test_approve_invalid_token_error_is_token_validation_error(gn3_url, http): resp = http.post( f"{gn3_url}/case-attribute/{_INBREDSET_ID}/approve/{_CHANGE_ID}", json={}, headers={"Authorization": _INVALID_TOKEN}, timeout=30, ) assert resp.json().get("error") == "TokenValidationError", ( f"Expected error='TokenValidationError', got: {resp.json()}" ) # --------------------------------------------------------------------------- # POST /case-attribute//reject/ — token enforcement # --------------------------------------------------------------------------- def test_reject_no_token_returns_400(gn3_url, http): resp = http.post( f"{gn3_url}/case-attribute/{_INBREDSET_ID}/reject/{_CHANGE_ID}", json={}, timeout=30, ) assert resp.status_code == 400, ( f"Expected 400 when no token supplied, got {resp.status_code}: {resp.text}" ) def test_reject_no_token_error_is_token_validation_error(gn3_url, http): resp = http.post( f"{gn3_url}/case-attribute/{_INBREDSET_ID}/reject/{_CHANGE_ID}", json={}, timeout=30, ) assert resp.json().get("error") == "TokenValidationError", ( f"Expected error='TokenValidationError', got: {resp.json()}" ) def test_reject_invalid_token_returns_400(gn3_url, http): resp = http.post( f"{gn3_url}/case-attribute/{_INBREDSET_ID}/reject/{_CHANGE_ID}", json={}, headers={"Authorization": _INVALID_TOKEN}, timeout=30, ) assert resp.status_code == 400, ( f"Expected 400 for invalid token, got {resp.status_code}: {resp.text}" ) def test_reject_invalid_token_error_is_token_validation_error(gn3_url, http): resp = http.post( f"{gn3_url}/case-attribute/{_INBREDSET_ID}/reject/{_CHANGE_ID}", json={}, headers={"Authorization": _INVALID_TOKEN}, timeout=30, ) assert resp.json().get("error") == "TokenValidationError", ( f"Expected error='TokenValidationError', got: {resp.json()}" ) def test_edit_valid_token_is_not_rejected_by_auth(gn3_url, http, access_token): """Valid token reaches the endpoint handler; response is not TokenValidationError. Requires live auth credentials (set by the CI test-session lifecycle). """ resp = http.post( f"{gn3_url}/case-attribute/{_INBREDSET_ID}/edit", json={"edit-data": []}, headers={"Authorization": f"Bearer {access_token}"}, timeout=30, ) assert resp.json().get("error") != "TokenValidationError", ( f"Valid token should not be rejected by @require_token, got: {resp.json()}" )