from datetime import UTC, datetime import pytest from tenant_engine.domain import ( CapabilityRole, InvalidGrantError, InvalidTenantIdentifierError, Tenant, create_role_grant, parse_tenant_identifier, ) def test_parse_tenant_identifier_reserved_platform() -> None: grouping, name = parse_tenant_identifier("tenant:platform") assert grouping is None assert name == "platform" def test_parse_tenant_identifier_reserved_coulomb() -> None: grouping, name = parse_tenant_identifier("tenant:coulomb") assert grouping is None assert name == "coulomb" def test_parse_tenant_identifier_grouped() -> None: grouping, name = parse_tenant_identifier("tenant:friendly:binky") assert grouping == "friendly" assert name == "binky" @pytest.mark.parametrize( "identifier", [ "tenant:unknown-grouping:binky", "tenant:friendly", "tenant:friendly:", "not-a-tenant:friendly:binky", "friendly:binky", ], ) def test_parse_tenant_identifier_rejects_invalid_shapes(identifier: str) -> None: with pytest.raises(InvalidTenantIdentifierError): parse_tenant_identifier(identifier) def test_tenant_create_from_identifier() -> None: tenant = Tenant.create(tenant_id="t-1", identifier="tenant:friendly:binky") assert tenant.grouping == "friendly" assert tenant.is_reserved is False def test_tenant_create_reserved_has_no_grouping() -> None: tenant = Tenant.create(tenant_id="t-platform", identifier="tenant:platform") assert tenant.grouping is None assert tenant.is_reserved is True def _tenant(*, grouping: str | None, identifier: str | None = None) -> Tenant: if identifier is None: identifier = f"tenant:{grouping}:acme" if grouping else "tenant:platform" return Tenant.create(tenant_id="t-1", identifier=identifier) def test_plan_assignment_grant_requires_plan_id() -> None: tenant = _tenant(grouping="friendly") with pytest.raises(InvalidGrantError): create_role_grant( tenant=tenant, grant_id="g-1", role=CapabilityRole.IAM, grant_reason="plan_assignment", plan_id=None, granted_by="ops", correlation_id="corr-1", granted_at=datetime.now(UTC), ) def test_plan_assignment_grant_with_plan_id_succeeds() -> None: tenant = _tenant(grouping="friendly") grant = create_role_grant( tenant=tenant, grant_id="g-1", role=CapabilityRole.IAM, grant_reason="plan_assignment", plan_id="plan-iam-dedicated", granted_by="ops", correlation_id="corr-1", granted_at=datetime.now(UTC), ) assert grant.active is True assert grant.plan_id == "plan-iam-dedicated" def test_platform_default_allowed_for_trial_tenant_without_plan() -> None: tenant = _tenant(grouping="trial") grant = create_role_grant( tenant=tenant, grant_id="g-1", role=CapabilityRole.VEN, grant_reason="platform_default", plan_id=None, granted_by="platform", correlation_id="corr-1", granted_at=datetime.now(UTC), ) assert grant.grant_reason == "platform_default" assert grant.plan_id is None def test_platform_default_allowed_for_reserved_tenant() -> None: tenant = _tenant(grouping=None, identifier="tenant:platform") grant = create_role_grant( tenant=tenant, grant_id="g-1", role=CapabilityRole.PLTF, grant_reason="platform_default", plan_id=None, granted_by="platform", correlation_id="corr-1", granted_at=datetime.now(UTC), ) assert grant.role is CapabilityRole.PLTF def test_platform_default_rejected_for_non_trial_grouped_tenant() -> None: tenant = _tenant(grouping="enterprise") with pytest.raises(InvalidGrantError): create_role_grant( tenant=tenant, grant_id="g-1", role=CapabilityRole.VEN, grant_reason="platform_default", plan_id=None, granted_by="platform", correlation_id="corr-1", granted_at=datetime.now(UTC), ) def test_platform_default_rejected_when_plan_id_present() -> None: tenant = _tenant(grouping="trial") with pytest.raises(InvalidGrantError): create_role_grant( tenant=tenant, grant_id="g-1", role=CapabilityRole.VEN, grant_reason="platform_default", plan_id="plan-x", granted_by="platform", correlation_id="corr-1", granted_at=datetime.now(UTC), ) def test_manual_grant_allows_missing_plan_id_for_any_grouping() -> None: tenant = _tenant(grouping="enterprise") 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), ) assert grant.grant_reason == "manual_grant" def test_grant_revoke_is_append_only() -> None: tenant = _tenant(grouping="friendly") 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), ) revoked = grant.revoke(at=datetime.now(UTC)) assert grant.revoked_at is None, "original record must be untouched" assert revoked.revoked_at is not None assert revoked.grant_id == grant.grant_id def test_revoking_twice_raises() -> None: tenant = _tenant(grouping="friendly") 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), ).revoke(at=datetime.now(UTC)) with pytest.raises(InvalidGrantError): grant.revoke(at=datetime.now(UTC))