89 lines
2.7 KiB
Python
89 lines
2.7 KiB
Python
|
|
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)
|