from datetime import UTC, datetime import pytest from tenant_engine.domain import ( EmptyUpdateError, ImmutableFieldError, InvalidLifecycleTransitionError, Tenant, TenantLifecycle, ) NOW = datetime(2026, 8, 10, 12, 0, tzinfo=UTC) def _tenant() -> Tenant: return Tenant.create( tenant_id="t-1", identifier="tenant:friendly:binky", display_name="Binky", created_at=NOW ) def test_new_tenant_is_active_at_version_one() -> None: tenant = _tenant() assert tenant.lifecycle is TenantLifecycle.ACTIVE assert tenant.version == 1 assert tenant.is_active def test_metadata_update_bumps_version_and_keeps_identity() -> None: tenant = _tenant().with_metadata({"display_name": "Binky Ltd"}, at=NOW) assert tenant.display_name == "Binky Ltd" assert tenant.version == 2 assert tenant.updated_at == NOW assert tenant.tenant_id == "t-1" assert tenant.identifier == "tenant:friendly:binky" assert tenant.grouping == "friendly" def test_update_rejects_identifier_mutation() -> None: with pytest.raises(ImmutableFieldError): _tenant().with_metadata({"identifier": "tenant:large:other"}, at=NOW) def test_update_rejects_unknown_field() -> None: with pytest.raises(ImmutableFieldError): _tenant().with_metadata({"nickname": "binks"}, at=NOW) def test_update_rejects_empty_change_set() -> None: with pytest.raises(EmptyUpdateError): _tenant().with_metadata({}, at=NOW) def test_update_rejects_no_op_change() -> None: with pytest.raises(EmptyUpdateError): _tenant().with_metadata({"display_name": "Binky"}, at=NOW) def test_retire_is_reversible_and_preserves_identity() -> None: retired = _tenant().retire(at=NOW) assert retired.lifecycle is TenantLifecycle.RETIRED assert retired.retired_at == NOW assert retired.version == 2 assert retired.identifier == "tenant:friendly:binky" reactivated = retired.reactivate(at=NOW) assert reactivated.lifecycle is TenantLifecycle.ACTIVE assert reactivated.reactivated_at == NOW assert reactivated.retired_at == NOW # retirement history is not erased assert reactivated.version == 3 def test_double_retirement_is_an_invalid_transition() -> None: retired = _tenant().retire(at=NOW) with pytest.raises(InvalidLifecycleTransitionError): retired.retire(at=NOW) def test_reactivating_an_active_tenant_is_an_invalid_transition() -> None: with pytest.raises(InvalidLifecycleTransitionError): _tenant().reactivate(at=NOW) def test_metadata_update_denied_while_retired() -> None: retired = _tenant().retire(at=NOW) with pytest.raises(InvalidLifecycleTransitionError): retired.with_metadata({"display_name": "Binky Ltd"}, at=NOW)