mirror of
https://github.com/Sea-Haven-Industries/afterhours-shift-manager.git
synced 2026-09-30 07:53:11 +00:00
* feat(api): collapse Slack, portal, and jobs onto Fargate (PLAT-216) Move HTTP and scheduled work onto one always-on Flask task so after-hours loses Lambda cold start without changing the Cognito or roster contracts. * fix(portal-api): keep CORS headers on unexpected 500s Portal SPA error handling needs Access-Control-Allow-Origin even when DynamoDB or other internals fail, otherwise the browser hides the 500. * fix(api): retarget holidays per account and ship App Home changelog (PLAT-216) * fix(iam): list ECS tasks and fail closed on non-prod Paychex (PLAT-216) * fix(portal-api): serve portal JSON with an explicit JSON content type
113 lines
3.4 KiB
Python
113 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
|