chore: consolidate platform split checks
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user