test_migration.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. from __future__ import annotations
  2. import importlib.util
  3. from pathlib import Path
  4. from alembic.config import Config
  5. from alembic.script import ScriptDirectory
  6. BACKEND_ROOT = Path(__file__).resolve().parents[2]
  7. MIGRATIONS_ROOT = BACKEND_ROOT / "migrations"
  8. REVISION_PATH = MIGRATIONS_ROOT / "versions" / "0001_initial_schema.py"
  9. RESTORE_REVISION_PATH = (
  10. MIGRATIONS_ROOT / "versions" / "0002_add_restore_audit_action.py"
  11. )
  12. def _load_revision_module():
  13. spec = importlib.util.spec_from_file_location("dms_initial_migration", REVISION_PATH)
  14. assert spec is not None and spec.loader is not None
  15. module = importlib.util.module_from_spec(spec)
  16. spec.loader.exec_module(module)
  17. return module
  18. def test_alembic_config_and_head_are_valid():
  19. config = Config(str(MIGRATIONS_ROOT / "alembic.ini"))
  20. script = ScriptDirectory.from_config(config)
  21. assert script.get_current_head() == "0002_add_restore_audit_action"
  22. def test_initial_migration_exports_upgrade_and_downgrade():
  23. module = _load_revision_module()
  24. assert callable(module.upgrade)
  25. assert callable(module.downgrade)
  26. def test_initial_migration_contains_seven_tables_and_generated_columns():
  27. source = REVISION_PATH.read_text(encoding="utf-8")
  28. for table_name in (
  29. "sys_organization",
  30. "sys_user",
  31. "doc_category",
  32. "doc_document",
  33. "doc_attachment_binding",
  34. "doc_permission",
  35. "sys_audit_log",
  36. ):
  37. assert f'"{table_name}"' in source
  38. assert source.count('sa.Computed("IF(is_deleted = 0, 1, NULL)"') == 2
  39. assert "uq_doc_attachment_binding_active" in source
  40. assert "uq_doc_permission_active" in source
  41. def test_initial_migration_has_no_cascade():
  42. source = REVISION_PATH.read_text(encoding="utf-8")
  43. assert 'ondelete="CASCADE"' not in source
  44. assert source.count('ondelete="RESTRICT"') >= 8
  45. def test_downgrade_uses_dependency_safe_order():
  46. source = REVISION_PATH.read_text(encoding="utf-8")
  47. positions = [
  48. source.index(f'op.drop_table("{name}")')
  49. for name in (
  50. "sys_audit_log",
  51. "doc_permission",
  52. "doc_attachment_binding",
  53. "doc_document",
  54. "doc_category",
  55. "sys_user",
  56. "sys_organization",
  57. )
  58. ]
  59. assert positions == sorted(positions)
  60. def test_restore_migration_is_scoped_and_reversible():
  61. source = RESTORE_REVISION_PATH.read_text(encoding="utf-8")
  62. assert 'down_revision: str | None = "0001_initial_schema"' in source
  63. assert "RESTORE_DOCUMENT" in source
  64. assert "ix_doc_document_recycle_deleted" in source
  65. assert '["is_deleted", "deleted_at", "id"]' in source
  66. assert "drop_index" in source
  67. assert source.count("create_check_constraint") == 2
  68. assert "create_table" not in source
  69. assert "add_column" not in source