Files
ITWorx_MCP_Hub-Public/tests/test_wp230_session_store.py
T

126 lines
4.8 KiB
Python

"""WP-230E: the session-store production decision is enforced, not just documented.
See docs/02-security/SESSION_STORE_DECISION.md. Two things are tested:
1. scripts/compose_release_check.py actually fails the release gate if
control-api is ever scaled beyond one replica (the concrete enforcement
mechanism the decision doc points to), not just that the doc says so.
2. The documented single-instance consequence -- a restart invalidates
every session -- is a real, observable property of SessionStore, not an
assumption.
"""
from __future__ import annotations
import tempfile
import unittest
from datetime import UTC, datetime
from pathlib import Path
import yaml # type: ignore[import-untyped]
from scripts.compose_release_check import check_compose
from services.control_api.auth.bff import SessionStore
from services.control_api.auth.errors import SessionExpired
from services.control_api.auth.models import (
AuthenticatedPrincipal,
ManagementRole,
PrincipalKind,
TokenSet,
)
ROOT = Path(__file__).resolve().parents[1]
REAL_COMPOSE = ROOT / "infrastructure" / "compose" / "docker-compose.yml"
def _minimal_compose(extra_control_api: dict[str, object] | None = None) -> dict[str, object]:
control_api: dict[str, object] = {"image": "local/itworx-mcp-hub-control:dev"}
if extra_control_api:
control_api.update(extra_control_api)
return {
"name": "itworx-mcp-hub",
"services": {
"control-api": control_api,
"docker-read-broker": {
"image": "local/itworx-mcp-hub-broker:dev",
"volumes": ["/var/run/docker.sock:/var/run/docker.sock:ro"],
},
},
}
class ComposeSingleInstanceEnforcementTests(unittest.TestCase):
def _check(self, document: dict[str, object]) -> dict[str, object]:
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "docker-compose.yml"
path.write_text(yaml.safe_dump(document), encoding="utf-8")
return check_compose(path)
def test_control_api_scaled_to_two_replicas_fails_the_gate(self) -> None:
result = self._check(_minimal_compose({"deploy": {"replicas": 2}}))
self.assertIn("control-api", result["singleInstanceViolations"])
self.assertFalse(result["pass"])
def test_control_api_with_no_deploy_block_passes(self) -> None:
result = self._check(_minimal_compose())
self.assertEqual(result["singleInstanceViolations"], [])
self.assertTrue(result["pass"])
def test_control_api_explicitly_pinned_to_one_replica_passes(self) -> None:
result = self._check(_minimal_compose({"deploy": {"replicas": 1}}))
self.assertEqual(result["singleInstanceViolations"], [])
self.assertTrue(result["pass"])
def test_real_compose_file_does_not_scale_control_api(self) -> None:
result = check_compose(REAL_COMPOSE)
self.assertEqual(result["singleInstanceViolations"], [])
class SessionStoreRestartBehaviorTests(unittest.TestCase):
"""Proves the documented consequence of the in-memory decision is real:
the store has no persistence, so a process restart (modeled here as
constructing a fresh SessionStore -- the same thing a real restart does,
a brand-new empty dict) invalidates every session that existed before
it, rather than silently reusing stale state."""
def _principal(self) -> AuthenticatedPrincipal:
return AuthenticatedPrincipal(
kind=PrincipalKind.MANAGEMENT_USER,
subject="user-1",
issuer="https://fake.oidc.test",
audience=("itworx-mcp-hub",),
client_id=None,
roles=frozenset({ManagementRole.VIEWER}),
issued_at=datetime(2026, 1, 1, tzinfo=UTC),
expires_at=datetime(2030, 1, 1, tzinfo=UTC),
)
def _store(self) -> SessionStore:
return SessionStore(
ttl_seconds=3600, cookie_name="hub_session", same_site="lax", secure=True
)
def test_session_does_not_survive_a_simulated_restart(self) -> None:
first_process = self._store()
cookie = first_process.create(
self._principal(),
TokenSet(access_token="access", id_token=None, refresh_token=None), # noqa: S106
)
second_process = self._store()
with self.assertRaises(SessionExpired):
second_process.get(cookie.value)
def test_the_same_process_still_recognizes_its_own_session(self) -> None:
store = self._store()
cookie = store.create(
self._principal(),
TokenSet(access_token="access", id_token=None, refresh_token=None), # noqa: S106
)
record = store.get(cookie.value)
self.assertEqual(record.csrf_token, cookie.csrf_token)
if __name__ == "__main__":
unittest.main()