from datetime import UTC, datetime import pytest from tenant_engine.domain import CapabilityRole, PlanAssignment, Tenant, create_role_grant from tenant_engine.store import ( GrantNotFoundError, InMemoryTenantStore, TenantAlreadyExistsError, TenantNotFoundError, ) def _store_with_tenant( *, grouping: str = "friendly", name: str = "binky" ) -> tuple[InMemoryTenantStore, Tenant]: store = InMemoryTenantStore() tenant = Tenant.create(tenant_id=f"t-{name}", identifier=f"tenant:{grouping}:{name}") store.create_tenant(tenant) return store, tenant def test_create_and_get_tenant() -> None: store, tenant = _store_with_tenant() assert store.get_tenant(tenant.tenant_id) == tenant def test_create_duplicate_tenant_raises() -> None: store, tenant = _store_with_tenant() with pytest.raises(TenantAlreadyExistsError): store.create_tenant(tenant) def test_get_unknown_tenant_raises() -> None: store = InMemoryTenantStore() with pytest.raises(TenantNotFoundError): store.get_tenant("does-not-exist") def test_grant_and_active_roles() -> None: store, tenant = _store_with_tenant() grant = create_role_grant( tenant=tenant, grant_id="g-1", role=CapabilityRole.CUS, grant_reason="manual_grant", plan_id=None, granted_by="ops", correlation_id="corr-1", granted_at=datetime.now(UTC), ) store.grant_role(grant) assert store.active_roles(tenant.tenant_id) == frozenset({CapabilityRole.CUS}) def test_revoke_removes_role_from_active_set_but_keeps_record() -> None: store, tenant = _store_with_tenant() grant = create_role_grant( tenant=tenant, grant_id="g-1", role=CapabilityRole.VEN, grant_reason="manual_grant", plan_id=None, granted_by="ops", correlation_id="corr-1", granted_at=datetime.now(UTC), ) store.grant_role(grant) revoked = store.revoke_role(tenant_id=tenant.tenant_id, grant_id="g-1", at=datetime.now(UTC)) assert revoked.revoked_at is not None assert store.active_roles(tenant.tenant_id) == frozenset() def test_revoke_unknown_grant_raises() -> None: store, tenant = _store_with_tenant() with pytest.raises(GrantNotFoundError): store.revoke_role(tenant_id=tenant.tenant_id, grant_id="missing", at=datetime.now(UTC)) def test_non_exclusive_roles_coexist() -> None: store, tenant = _store_with_tenant() for role in (CapabilityRole.CUS, CapabilityRole.VEN): store.grant_role( create_role_grant( tenant=tenant, grant_id=f"g-{role.value}", role=role, grant_reason="manual_grant", plan_id=None, granted_by="ops", correlation_id="corr-1", granted_at=datetime.now(UTC), ) ) assert store.active_roles(tenant.tenant_id) == frozenset( {CapabilityRole.CUS, CapabilityRole.VEN} ) def test_assign_plan() -> None: store, tenant = _store_with_tenant() store.assign_plan( PlanAssignment(tenant_id=tenant.tenant_id, plan_id="plan-x", assigned_at=datetime.now(UTC)) ) events = store.events_for(tenant.tenant_id) assert any( event.event_type == "plan_assigned" and event.payload["plan_id"] == "plan-x" for event in events ) def test_get_tenant_resolves_by_identifier_not_only_internal_id() -> None: """Found via a real cross-service check (KEY-WP-0005-T02 against a live tenant-engine): external callers like key-cape and flex-auth only ever have the tenant's profile identifier (the IAM Profile `tenant` claim value), never its internal tenant_id, which is caller-chosen at creation and otherwise opaque. """ store, tenant = _store_with_tenant() by_internal_id = store.get_tenant(tenant.tenant_id) by_identifier = store.get_tenant(tenant.identifier) assert by_internal_id == by_identifier == tenant def test_active_roles_resolves_by_identifier() -> None: store, tenant = _store_with_tenant() store.grant_role( create_role_grant( tenant=tenant, grant_id="g-1", role=CapabilityRole.CUS, grant_reason="manual_grant", plan_id=None, granted_by="ops", correlation_id="corr-1", granted_at=datetime.now(UTC), ) ) assert store.active_roles(tenant.identifier) == frozenset({CapabilityRole.CUS}) def test_revoke_role_resolves_by_identifier() -> None: store, tenant = _store_with_tenant() store.grant_role( create_role_grant( tenant=tenant, grant_id="g-1", role=CapabilityRole.CUS, grant_reason="manual_grant", plan_id=None, granted_by="ops", correlation_id="corr-1", granted_at=datetime.now(UTC), ) ) revoked = store.revoke_role(tenant_id=tenant.identifier, grant_id="g-1", at=datetime.now(UTC)) assert revoked.revoked_at is not None assert store.active_roles(tenant.tenant_id) == frozenset() def test_create_tenant_rejects_duplicate_identifier_with_different_internal_id() -> None: store, tenant = _store_with_tenant() other = Tenant.create(tenant_id="a-different-internal-id", identifier=tenant.identifier) with pytest.raises(TenantAlreadyExistsError): store.create_tenant(other) def test_every_mutation_emits_an_event() -> None: store, tenant = _store_with_tenant() grant = create_role_grant( tenant=tenant, grant_id="g-1", role=CapabilityRole.CUS, grant_reason="manual_grant", plan_id=None, granted_by="ops", correlation_id="corr-1", granted_at=datetime.now(UTC), ) store.grant_role(grant) store.revoke_role(tenant_id=tenant.tenant_id, grant_id="g-1", at=datetime.now(UTC)) store.assign_plan( PlanAssignment(tenant_id=tenant.tenant_id, plan_id="plan-x", assigned_at=datetime.now(UTC)) ) event_types = [event.event_type for event in store.events_for(tenant.tenant_id)] assert event_types == ["tenant_created", "role_granted", "role_revoked", "plan_assigned"]