2026-05-23 15:52:47 +02:00
|
|
|
"""Migration-seed parity test (MA3 + sprint 2 delta).
|
|
|
|
|
|
|
|
|
|
The runtime F11 matrix in `mimic.rbac.matrix` must equal the union of:
|
|
|
|
|
|
|
|
|
|
- the inline frozen snapshot in the initial migration `202605210001`, plus
|
|
|
|
|
- every per-migration `_DELTA_PERMISSIONS` / `_DELTA_GROUP_PERMISSIONS` added
|
|
|
|
|
by later migrations.
|
|
|
|
|
|
|
|
|
|
When the runtime drifts, *do not* edit an existing migration: write a new
|
|
|
|
|
one with its own delta block and extend the `_MIGRATIONS` tuple below.
|
2026-05-22 05:24:37 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import importlib
|
2026-05-23 15:52:47 +02:00
|
|
|
from types import ModuleType
|
2026-05-22 05:24:37 +02:00
|
|
|
|
|
|
|
|
from mimic.rbac.matrix import GROUP_PERMISSIONS, GroupName, Permission
|
|
|
|
|
|
2026-05-23 15:52:47 +02:00
|
|
|
_INITIAL = "mimic.db.migrations.versions.202605210001_initial_schema"
|
|
|
|
|
_DELTAS: tuple[str, ...] = ("mimic.db.migrations.versions.202605230001_add_user_manage_permission",)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _load(name: str) -> ModuleType:
|
|
|
|
|
return importlib.import_module(name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _cumulative_permissions() -> set[str]:
|
|
|
|
|
initial = _load(_INITIAL)
|
|
|
|
|
codes = set(initial._PERMISSIONS_FROZEN)
|
|
|
|
|
for name in _DELTAS:
|
|
|
|
|
delta = _load(name)
|
|
|
|
|
codes.update(delta._DELTA_PERMISSIONS)
|
|
|
|
|
return codes
|
|
|
|
|
|
2026-05-22 05:24:37 +02:00
|
|
|
|
2026-05-23 15:52:47 +02:00
|
|
|
def _cumulative_group_permissions() -> dict[str, set[str]]:
|
|
|
|
|
initial = _load(_INITIAL)
|
|
|
|
|
cumulative = {gn: set(perms) for gn, perms in initial._GROUP_PERMISSIONS_FROZEN.items()}
|
|
|
|
|
for name in _DELTAS:
|
|
|
|
|
delta = _load(name)
|
|
|
|
|
# `rt_lead` carries ALL_PERMISSIONS at runtime — every delta perm
|
|
|
|
|
# implicitly extends rt_lead too, regardless of whether the migration
|
|
|
|
|
# listed it explicitly.
|
|
|
|
|
rt_lead_implicit = {p for perms in delta._DELTA_GROUP_PERMISSIONS.values() for p in perms}
|
|
|
|
|
cumulative.setdefault("rt_lead", set()).update(rt_lead_implicit)
|
|
|
|
|
for group_name, perms in delta._DELTA_GROUP_PERMISSIONS.items():
|
|
|
|
|
cumulative.setdefault(group_name, set()).update(perms)
|
|
|
|
|
return cumulative
|
2026-05-22 05:24:37 +02:00
|
|
|
|
|
|
|
|
|
2026-05-23 15:52:47 +02:00
|
|
|
def test_runtime_permissions_match_cumulative_migrations() -> None:
|
2026-05-22 05:24:37 +02:00
|
|
|
runtime_codes = {p.value for p in Permission}
|
2026-05-23 15:52:47 +02:00
|
|
|
cumulative = _cumulative_permissions()
|
|
|
|
|
assert runtime_codes == cumulative, (
|
|
|
|
|
"Permission enum drifted from the cumulative migration freeze; "
|
|
|
|
|
"write a new migration delta, do not edit existing ones."
|
2026-05-22 05:24:37 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-05-23 15:52:47 +02:00
|
|
|
def test_runtime_group_membership_matches_cumulative_migrations() -> None:
|
2026-05-22 05:24:37 +02:00
|
|
|
runtime = {gn.value: {p.value for p in perms} for gn, perms in GROUP_PERMISSIONS.items()}
|
2026-05-23 15:52:47 +02:00
|
|
|
cumulative = _cumulative_group_permissions()
|
|
|
|
|
assert runtime == cumulative, (
|
|
|
|
|
"GROUP_PERMISSIONS drifted from the cumulative migration freeze; "
|
|
|
|
|
"write a new migration delta, do not edit existing ones."
|
2026-05-22 05:24:37 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-05-23 15:52:47 +02:00
|
|
|
def test_initial_frozen_group_names_match_enum() -> None:
|
|
|
|
|
initial = _load(_INITIAL)
|
|
|
|
|
assert set(initial._GROUP_PERMISSIONS_FROZEN.keys()) == {g.value for g in GroupName}
|