chore: consolidate platform split checks
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Module Matrix / module-matrix (push) Has been cancelled

This commit is contained in:
2026-07-10 12:51:19 +02:00
parent 150b720f12
commit 635d25c74c
216 changed files with 23336 additions and 4077 deletions

View File

@@ -12,10 +12,13 @@ from sqlalchemy import create_engine, inspect, text
from govoplan_core.db.base import Base
from govoplan_core.db.migrations import (
REVISION_AUTH_RBAC,
REVISION_CORE_CHANGE_SEQUENCE,
REVISION_FILE_FOLDERS,
REVISION_HIERARCHICAL_SETTINGS,
alembic_config,
migrate_database,
reconcile_change_sequence_retention_floor_drift,
reconcile_namespace_table_drift,
)
@@ -28,6 +31,67 @@ def database_migration_heads(connection) -> set[str]:
class DatabaseMigrationTests(unittest.TestCase):
def test_repairs_missing_change_sequence_retention_floor(self) -> None:
with tempfile.TemporaryDirectory(prefix="msm-change-sequence-drift-test-") as directory:
database = Path(directory) / "change-sequence-drift.db"
url = f"sqlite:///{database}"
command.upgrade(alembic_config(database_url=url, enabled_modules=()), REVISION_CORE_CHANGE_SEQUENCE)
engine = create_engine(url)
try:
with engine.begin() as connection:
connection.execute(text("DROP TABLE core_change_sequence_retention_floor"))
with engine.connect() as connection:
tables = set(inspect(connection).get_table_names())
self.assertIn("core_change_sequence", tables)
self.assertNotIn("core_change_sequence_retention_floor", tables)
finally:
engine.dispose()
self.assertTrue(reconcile_change_sequence_retention_floor_drift(url))
engine = create_engine(url)
try:
with engine.connect() as connection:
inspector = inspect(connection)
self.assertIn("core_change_sequence_retention_floor", inspector.get_table_names())
self.assertIn(
"ix_core_change_sequence_retention_scope",
{index["name"] for index in inspector.get_indexes("core_change_sequence_retention_floor")},
)
finally:
engine.dispose()
def test_repairs_namespace_tables_when_database_was_stamped_ahead(self) -> None:
with tempfile.TemporaryDirectory(prefix="msm-namespace-drift-test-") as directory:
database = Path(directory) / "namespace-drift.db"
url = f"sqlite:///{database}"
engine = create_engine(url)
try:
with engine.begin() as connection:
connection.execute(text("CREATE TABLE accounts (id TEXT PRIMARY KEY)"))
connection.execute(text("CREATE TABLE users (id TEXT PRIMARY KEY)"))
connection.execute(text("CREATE TABLE governance_templates (id TEXT PRIMARY KEY)"))
with engine.connect() as connection:
tables = set(inspect(connection).get_table_names())
self.assertIn("accounts", tables)
self.assertNotIn("access_accounts", tables)
finally:
engine.dispose()
self.assertTrue(reconcile_namespace_table_drift(url))
engine = create_engine(url)
try:
with engine.connect() as connection:
tables = set(inspect(connection).get_table_names())
self.assertNotIn("accounts", tables)
self.assertIn("access_accounts", tables)
self.assertNotIn("governance_templates", tables)
self.assertIn("admin_governance_templates", tables)
finally:
engine.dispose()
def test_repairs_create_all_schema_drift_and_upgrades_to_head(self) -> None:
with tempfile.TemporaryDirectory(prefix="msm-migration-test-") as directory:
database = Path(directory) / "legacy.db"
@@ -72,33 +136,33 @@ class DatabaseMigrationTests(unittest.TestCase):
job_indexes = {index["name"] for index in inspector.get_indexes("campaign_jobs")}
attempt_columns = {column["name"] for column in inspector.get_columns("send_attempts")}
tables = set(inspector.get_table_names())
tenant_columns = {column["name"] for column in inspector.get_columns("tenants")}
user_columns = {column["name"] for column in inspector.get_columns("users")}
session_columns = {column["name"] for column in inspector.get_columns("auth_sessions")}
group_columns = {column["name"] for column in inspector.get_columns("groups")}
role_columns = {column["name"] for column in inspector.get_columns("roles")}
role_indexes = {index["name"] for index in inspector.get_indexes("roles")}
tenant_columns = {column["name"] for column in inspector.get_columns("tenancy_tenants")}
user_columns = {column["name"] for column in inspector.get_columns("access_users")}
session_columns = {column["name"] for column in inspector.get_columns("access_auth_sessions")}
group_columns = {column["name"] for column in inspector.get_columns("access_groups")}
role_columns = {column["name"] for column in inspector.get_columns("access_roles")}
role_indexes = {index["name"] for index in inspector.get_indexes("access_roles")}
campaign_columns = {column["name"] for column in inspector.get_columns("campaigns")}
audit_columns = {column["name"] for column in inspector.get_columns("audit_log")}
audit_indexes = {index["name"] for index in inspector.get_indexes("audit_log")}
system_role_flags = {
row["slug"]: bool(row["is_builtin"])
for row in connection.execute(text(
"SELECT slug, is_builtin FROM roles WHERE tenant_id IS NULL AND slug IN ('system_owner', 'system_admin', 'system_auditor')"
"SELECT slug, is_builtin FROM access_roles WHERE tenant_id IS NULL AND slug IN ('system_owner', 'system_admin', 'system_auditor')"
)).mappings().all()
}
system_settings_columns = {column["name"] for column in inspector.get_columns("system_settings")}
governance_assignment_columns = {column["name"] for column in inspector.get_columns("governance_template_assignments")}
system_settings_columns = {column["name"] for column in inspector.get_columns("core_system_settings")}
governance_assignment_columns = {column["name"] for column in inspector.get_columns("admin_governance_template_assignments")}
import_profile_columns = {column["name"] for column in inspector.get_columns("campaign_recipient_import_mapping_profiles")}
import_profile_indexes = {index["name"] for index in inspector.get_indexes("campaign_recipient_import_mapping_profiles")}
account_count = connection.execute(text("SELECT COUNT(*) FROM accounts")).scalar_one()
user_count = connection.execute(text("SELECT COUNT(*) FROM users")).scalar_one()
account_count = connection.execute(text("SELECT COUNT(*) FROM access_accounts")).scalar_one()
user_count = connection.execute(text("SELECT COUNT(*) FROM access_users")).scalar_one()
system_owner_count = connection.execute(text(
"""
SELECT COUNT(*)
FROM system_role_assignments assignment
JOIN roles role ON role.id = assignment.role_id
JOIN accounts account ON account.id = assignment.account_id
FROM access_system_role_assignments assignment
JOIN access_roles role ON role.id = assignment.role_id
JOIN access_accounts account ON account.id = assignment.account_id
WHERE role.slug = 'system_owner' AND account.is_active = 1
"""
)).scalar_one()
@@ -120,11 +184,11 @@ class DatabaseMigrationTests(unittest.TestCase):
self.assertIn("ix_campaign_jobs_eml_sha256", job_indexes)
self.assertIn("status", attempt_columns)
self.assertIn("claim_token", attempt_columns)
self.assertIn("accounts", tables)
self.assertIn("system_role_assignments", tables)
self.assertIn("system_settings", tables)
self.assertIn("governance_templates", tables)
self.assertIn("governance_template_assignments", tables)
self.assertIn("access_accounts", tables)
self.assertIn("access_system_role_assignments", tables)
self.assertIn("core_system_settings", tables)
self.assertIn("admin_governance_templates", tables)
self.assertIn("admin_governance_template_assignments", tables)
self.assertIn("campaign_shares", tables)
self.assertIn("campaign_recipient_import_mapping_profiles", tables)
self.assertIn("owner_user_id", campaign_columns)
@@ -259,13 +323,13 @@ class DatabaseMigrationTests(unittest.TestCase):
timestamps = {"created_at": "2026-06-14 12:00:00", "updated_at": "2026-06-14 12:00:00"}
connection.execute(text(
"""
INSERT INTO tenants (id, slug, name, is_active, created_at, updated_at)
INSERT INTO tenancy_tenants (id, slug, name, is_active, created_at, updated_at)
VALUES ('tenant-1', 'legacy', 'Legacy', 1, :created_at, :updated_at)
"""
), timestamps)
connection.execute(text(
"""
INSERT INTO users
INSERT INTO access_users
(id, tenant_id, email, display_name, is_active, is_tenant_admin,
auth_provider, password_hash, last_login_at, created_at, updated_at)
VALUES
@@ -275,7 +339,7 @@ class DatabaseMigrationTests(unittest.TestCase):
), timestamps)
connection.execute(text(
"""
INSERT INTO auth_sessions
INSERT INTO access_auth_sessions
(id, tenant_id, user_id, token_hash, expires_at, last_seen_at, revoked_at,
user_agent, ip_address, created_at, updated_at)
VALUES
@@ -292,37 +356,37 @@ class DatabaseMigrationTests(unittest.TestCase):
try:
with engine.connect() as connection:
account = connection.execute(text(
"SELECT id, normalized_email, password_hash FROM accounts"
"SELECT id, normalized_email, password_hash FROM access_accounts"
)).mappings().one()
membership = connection.execute(text(
"SELECT account_id FROM users WHERE id = 'user-1'"
"SELECT account_id FROM access_users WHERE id = 'user-1'"
)).mappings().one()
migrated_session = connection.execute(text(
"SELECT account_id FROM auth_sessions WHERE id = 'session-1'"
"SELECT account_id FROM access_auth_sessions WHERE id = 'session-1'"
)).mappings().one()
owner_assignment = connection.execute(text(
"""
SELECT role.slug
FROM user_role_assignments assignment
JOIN roles role ON role.id = assignment.role_id
FROM access_user_role_assignments assignment
JOIN access_roles role ON role.id = assignment.role_id
WHERE assignment.user_id = 'user-1'
"""
)).scalar_one()
owner_permissions = connection.execute(text(
"SELECT permissions FROM roles WHERE tenant_id = 'tenant-1' AND slug = 'owner'"
"SELECT permissions FROM access_roles WHERE tenant_id = 'tenant-1' AND slug = 'owner'"
)).scalar_one()
system_assignment = connection.execute(text(
"""
SELECT role.slug
FROM system_role_assignments assignment
JOIN roles role ON role.id = assignment.role_id
FROM access_system_role_assignments assignment
JOIN access_roles role ON role.id = assignment.role_id
WHERE assignment.account_id = :account_id
"""
), {"account_id": account["id"]}).scalar_one()
system_role_flags = {
row["slug"]: bool(row["is_builtin"])
for row in connection.execute(text(
"SELECT slug, is_builtin FROM roles WHERE tenant_id IS NULL AND slug IN ('system_owner', 'system_admin', 'system_auditor')"
"SELECT slug, is_builtin FROM access_roles WHERE tenant_id IS NULL AND slug IN ('system_owner', 'system_admin', 'system_auditor')"
)).mappings().all()
}
self.assertEqual(account["normalized_email"], "owner@example.local")
@@ -356,7 +420,7 @@ class DatabaseMigrationTests(unittest.TestCase):
INSERT INTO audit_log
(id, tenant_id, user_id, api_key_id, action, object_type, object_id, details, created_at, updated_at)
VALUES
('audit-system', NULL, NULL, NULL, 'system_settings.updated', 'system_settings', 'system', '{}', :created_at, :updated_at),
('audit-system', NULL, NULL, NULL, 'system_settings.updated', 'core_system_settings', 'system', '{}', :created_at, :updated_at),
('audit-tenant', NULL, NULL, NULL, 'user.updated', 'user', 'user-1', '{}', :created_at, :updated_at)
"""
), timestamps)