afterhours-shift-manager/tests/portal_api/test_handler.py
Adam Moussa 969ebf90de
Some checks are pending
Deploy / Deploy to prod (push) Waiting to run
feat(portal-api): add Cognito shift API for the employee portal (DEV-287) (#255)
* feat(portal-api): add Cognito shift API for the employee portal (DEV-287)

Employees and admins can pick, drop, swap, and manage coverage through
GET/POST/DELETE /api/shifts. Roster PUT accepts optional email for portal
identity. Slack slash commands and App Home admin modals stay in place.

Co-authored-by: adam <adam@seahavenind.com>

* fix(portal-api): preserve shift and deployment invariants (DEV-287)

Co-authored-by: adam <adam@seahavenind.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-09-21 17:50:57 +00:00

89 lines
2.6 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(
portalapi_app,
"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"