mirror of
https://github.com/Sea-Haven-Industries/afterhours-shift-manager.git
synced 2026-09-30 23:03:11 +00:00
114 lines
3.4 KiB
Python
114 lines
3.4 KiB
Python
|
|
"""Flask routes: health, CORS, portal auth fail-closed, roster auth."""
|
||
|
|
|
||
|
|
from unittest.mock import patch
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from server.app import create_app
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def client():
|
||
|
|
app = create_app()
|
||
|
|
app.testing = True
|
||
|
|
return app.test_client()
|
||
|
|
|
||
|
|
|
||
|
|
def test_health_reports_stage_and_sha(client, monkeypatch):
|
||
|
|
monkeypatch.setenv("STAGE", "dev")
|
||
|
|
monkeypatch.setenv("GIT_SHA", "abc123")
|
||
|
|
response = client.get("/api/health")
|
||
|
|
assert response.status_code == 200
|
||
|
|
assert response.get_json() == {"stage": "dev", "sha": "abc123"}
|
||
|
|
|
||
|
|
|
||
|
|
def test_portal_options_is_204_with_cors(client):
|
||
|
|
response = client.options(
|
||
|
|
"/api/shifts",
|
||
|
|
headers={"Origin": "https://internal.seahaven.com"},
|
||
|
|
)
|
||
|
|
assert response.status_code == 204
|
||
|
|
assert (
|
||
|
|
response.headers["Access-Control-Allow-Origin"]
|
||
|
|
== "https://internal.seahaven.com"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_portal_unknown_origin_has_no_acao(client):
|
||
|
|
response = client.options(
|
||
|
|
"/api/shifts",
|
||
|
|
headers={"Origin": "https://evil.example"},
|
||
|
|
)
|
||
|
|
assert response.status_code == 204
|
||
|
|
assert "Access-Control-Allow-Origin" not in response.headers
|
||
|
|
|
||
|
|
|
||
|
|
def test_portal_missing_bearer_is_401(client):
|
||
|
|
response = client.get("/api/shifts")
|
||
|
|
assert response.status_code == 401
|
||
|
|
body = response.get_json()
|
||
|
|
assert body["error"]["code"] == "UNAUTHORIZED"
|
||
|
|
|
||
|
|
|
||
|
|
def test_portal_unexpected_failure_keeps_cors(client):
|
||
|
|
with patch(
|
||
|
|
"shared.portal_http.verify_cognito_id_token",
|
||
|
|
return_value={"name": "Alice", "email": "alice@seahavenind.com"},
|
||
|
|
):
|
||
|
|
with patch(
|
||
|
|
"shared.portal_http.ShiftSchedule",
|
||
|
|
side_effect=RuntimeError("ddb down"),
|
||
|
|
):
|
||
|
|
response = client.get(
|
||
|
|
"/api/shifts",
|
||
|
|
headers={
|
||
|
|
"Authorization": "Bearer token",
|
||
|
|
"Origin": "https://internal.seahaven.com",
|
||
|
|
},
|
||
|
|
)
|
||
|
|
assert response.status_code == 500
|
||
|
|
assert (
|
||
|
|
response.headers["Access-Control-Allow-Origin"]
|
||
|
|
== "https://internal.seahaven.com"
|
||
|
|
)
|
||
|
|
assert response.get_json()["error"]["code"] == "INTERNAL"
|
||
|
|
|
||
|
|
|
||
|
|
def test_portal_invalid_token_is_401(client):
|
||
|
|
with patch(
|
||
|
|
"shared.portal_http.verify_cognito_id_token",
|
||
|
|
return_value=None,
|
||
|
|
):
|
||
|
|
response = client.get(
|
||
|
|
"/api/shifts",
|
||
|
|
headers={"Authorization": "Bearer nope"},
|
||
|
|
)
|
||
|
|
assert response.status_code == 401
|
||
|
|
|
||
|
|
|
||
|
|
def test_roster_missing_bearer_is_401(client, monkeypatch):
|
||
|
|
monkeypatch.setenv(
|
||
|
|
"ROSTER_API_TOKEN_SECRET", "afterhours-shift-manager/roster-api-token"
|
||
|
|
)
|
||
|
|
with patch("shared.roster_http.get_secret", return_value="expected"):
|
||
|
|
response = client.put(
|
||
|
|
"/roster", json={"name": "Pat", "extension": "110", "slack_user_id": "U1"}
|
||
|
|
)
|
||
|
|
assert response.status_code == 401
|
||
|
|
|
||
|
|
|
||
|
|
def test_roster_wrong_token_is_401(client, monkeypatch):
|
||
|
|
monkeypatch.setenv(
|
||
|
|
"ROSTER_API_TOKEN_SECRET", "afterhours-shift-manager/roster-api-token"
|
||
|
|
)
|
||
|
|
import shared.roster_http as roster_http
|
||
|
|
|
||
|
|
roster_http._cached_token = None
|
||
|
|
with patch("shared.roster_http.get_secret", return_value="expected"):
|
||
|
|
response = client.put(
|
||
|
|
"/roster",
|
||
|
|
headers={"Authorization": "Bearer nope"},
|
||
|
|
json={"name": "Pat", "extension": "110", "slack_user_id": "UABC"},
|
||
|
|
)
|
||
|
|
assert response.status_code == 401
|