from __future__ import annotations import unittest from uuid import uuid4 from services.control_api.persistence.db import Base, create_engine_from_url, session_factory from services.control_api.persistence.demo_client import ( DEMO_CLIENT_ID, DEMO_CLIENT_ISSUER, DEMO_CLIENT_SUBJECT, provision_demo_client, ) from services.control_api.persistence.models import ( ClientBindingRow, ClientRow, EnvironmentRow, ProjectRow, VirtualServerRow, WorkspaceRow, ) class DemoClientProvisioningTests(unittest.TestCase): def setUp(self) -> None: self.engine = create_engine_from_url("sqlite:///:memory:") Base.metadata.create_all(self.engine) self.sessions = session_factory(self.engine) self.workspace_id = uuid4() self.project_id = uuid4() self.environment_id = uuid4() self.server_id = uuid4() with self.sessions() as session, session.begin(): session.add(WorkspaceRow(id=self.workspace_id, slug="itworx", name="ITWorx")) session.flush() session.add( ProjectRow( id=self.project_id, workspace_id=self.workspace_id, slug="mobilityops", name="MobilityOps", ) ) session.flush() session.add( EnvironmentRow( id=self.environment_id, workspace_id=self.workspace_id, project_id=self.project_id, name="prod", classification="production", ) ) session.flush() session.add( VirtualServerRow( id=self.server_id, workspace_id=self.workspace_id, project_id=self.project_id, environment_id=self.environment_id, slug="mobilityops-prod-readonly", desired_hash="a" * 64, enabled=True, # Must satisfy the [0-9a-f]{32} route-identifier shape that # SqlAlchemyAliasSource validates -- provisioning now # requires a real external gateway route. gateway_external_id="a" * 32, ) ) def tearDown(self) -> None: self.engine.dispose() def test_provisions_client_and_binding(self) -> None: with self.sessions() as session, session.begin(): result = provision_demo_client(session) self.assertEqual(result.client_id, DEMO_CLIENT_ID) with self.sessions() as session: client = session.get(ClientRow, DEMO_CLIENT_ID) assert client is not None self.assertEqual(client.issuer, DEMO_CLIENT_ISSUER) self.assertEqual(client.subject, DEMO_CLIENT_SUBJECT) self.assertEqual(client.status, "active") binding = session.get(ClientBindingRow, result.binding_id) assert binding is not None self.assertEqual(binding.client_id, DEMO_CLIENT_ID) self.assertEqual(binding.virtual_server_id, self.server_id) self.assertTrue(binding.enabled) def test_idempotent_on_repeat_run(self) -> None: with self.sessions() as session, session.begin(): first = provision_demo_client(session) with self.sessions() as session, session.begin(): second = provision_demo_client(session) self.assertEqual(first, second) with self.sessions() as session: self.assertEqual( session.query(ClientRow).filter(ClientRow.id == DEMO_CLIENT_ID).count(), 1 ) self.assertEqual( session.query(ClientBindingRow) .filter(ClientBindingRow.client_id == DEMO_CLIENT_ID) .count(), 1, ) def test_raises_when_virtual_server_missing(self) -> None: with self.sessions() as session, session.begin(): with self.assertRaises(ValueError): provision_demo_client(session, project_slug="does-not-exist") def test_raises_when_virtual_server_is_not_uniquely_determined(self) -> None: """Two enabled, externally-routed servers for the same project and environment must fail closed rather than bind the demo identity to an arbitrary one of them.""" with self.sessions() as session, session.begin(): session.add( VirtualServerRow( id=uuid4(), workspace_id=self.workspace_id, project_id=self.project_id, environment_id=self.environment_id, slug="mobilityops-prod-readonly-second", desired_hash="b" * 64, enabled=True, gateway_external_id="b" * 32, ) ) with self.sessions() as session, session.begin(): with self.assertRaises(ValueError): provision_demo_client(session) def test_raises_when_virtual_server_has_no_external_route(self) -> None: """A server that exists but was never routed externally is not a valid demo binding target.""" with self.sessions() as session, session.begin(): stored = session.get(VirtualServerRow, self.server_id) assert stored is not None stored.gateway_external_id = None with self.sessions() as session, session.begin(): with self.assertRaises(ValueError): provision_demo_client(session)