mirror of
https://github.com/Sea-Haven-Industries/afterhours-shift-manager.git
synced 2026-09-30 06:43:12 +00:00
Portal SPA error handling needs Access-Control-Allow-Origin even when DynamoDB or other internals fail, otherwise the browser hides the 500.
101 lines
3.2 KiB
Python
101 lines
3.2 KiB
Python
"""Tests for the portal shift API handler."""
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
|
|
def _event(method="GET", path="/api/shifts", body=None, token="id-token", origin=None):
|
|
headers = {"authorization": f"Bearer {token}"}
|
|
if origin:
|
|
headers["origin"] = origin
|
|
payload = None
|
|
if body is not None:
|
|
payload = json.dumps(body)
|
|
return {
|
|
"version": "2.0",
|
|
"routeKey": f"{method} {path}",
|
|
"rawPath": path,
|
|
"headers": headers,
|
|
"queryStringParameters": {},
|
|
"requestContext": {"http": {"method": method, "path": path}},
|
|
"body": payload,
|
|
"isBase64Encoded": False,
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def identity(portalapi_app, monkeypatch):
|
|
monkeypatch.setattr(
|
|
"shared.portal_http.verify_cognito_id_token",
|
|
lambda _token: {"name": "Alice", "email": "alice@seahavenind.com"},
|
|
)
|
|
|
|
|
|
def test_unlinked_email_returns_linked_false(portalapi_app, schedule, identity):
|
|
result = portalapi_app.handler(_event(), None)
|
|
assert result["statusCode"] == 200
|
|
body = json.loads(result["body"])
|
|
assert body["linked"] is False
|
|
assert body["email"] == "alice@seahavenind.com"
|
|
|
|
|
|
def test_401_without_bearer(portalapi_app, schedule):
|
|
event = _event()
|
|
event["headers"] = {}
|
|
result = portalapi_app.handler(event, None)
|
|
assert result["statusCode"] == 401
|
|
|
|
|
|
def test_linked_snapshot_and_admin_flag(portalapi_app, schedule, seed, identity):
|
|
seed.roster(
|
|
"114",
|
|
"Alice",
|
|
slack_user_id="U_ADMIN",
|
|
email="alice@seahavenind.com",
|
|
)
|
|
seed.config(admin_users=["U_ADMIN"])
|
|
seed.weekly("Monday", "114", "Alice")
|
|
result = portalapi_app.handler(_event(), None)
|
|
assert result["statusCode"] == 200
|
|
body = json.loads(result["body"])
|
|
assert body["linked"] is True
|
|
assert body["isAdmin"] is True
|
|
assert body["me"]["extension"] == "114"
|
|
assert len(body["days"]) == 7
|
|
|
|
|
|
def test_non_admin_cannot_override(portalapi_app, schedule, seed, identity):
|
|
seed.roster("114", "Alice", slack_user_id="U_ALICE", email="alice@seahavenind.com")
|
|
seed.config(admin_users=["U_OTHER"])
|
|
result = portalapi_app.handler(
|
|
_event(
|
|
method="POST",
|
|
path="/api/shifts/admin/override",
|
|
body={"date": "2026-06-10", "extension": "114", "shiftType": "night"},
|
|
),
|
|
None,
|
|
)
|
|
assert result["statusCode"] == 403
|
|
|
|
|
|
def test_cors_header_for_portal_origin(portalapi_app, schedule, identity):
|
|
result = portalapi_app.handler(_event(origin="https://internal.seahaven.com"), None)
|
|
assert (
|
|
result["headers"]["Access-Control-Allow-Origin"]
|
|
== "https://internal.seahaven.com"
|
|
)
|
|
|
|
|
|
def test_unexpected_failure_keeps_cors(portalapi_app, identity, monkeypatch):
|
|
monkeypatch.setattr(
|
|
"shared.portal_http.ShiftSchedule",
|
|
lambda: (_ for _ in ()).throw(RuntimeError("ddb down")),
|
|
)
|
|
result = portalapi_app.handler(_event(origin="https://internal.seahaven.com"), None)
|
|
assert result["statusCode"] == 500
|
|
assert (
|
|
result["headers"]["Access-Control-Allow-Origin"]
|
|
== "https://internal.seahaven.com"
|
|
)
|
|
assert json.loads(result["body"])["error"]["code"] == "INTERNAL"
|