test_migration.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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. Q3_REVISION_PATH = (
  13. MIGRATIONS_ROOT
  14. / "versions"
  15. / "0003_q3_business_alignment_and_content_search.py"
  16. )
  17. def _load_revision_module():
  18. spec = importlib.util.spec_from_file_location("dms_initial_migration", REVISION_PATH)
  19. assert spec is not None and spec.loader is not None
  20. module = importlib.util.module_from_spec(spec)
  21. spec.loader.exec_module(module)
  22. return module
  23. def test_alembic_config_and_head_are_valid():
  24. config = Config(str(MIGRATIONS_ROOT / "alembic.ini"))
  25. script = ScriptDirectory.from_config(config)
  26. assert script.get_current_head() == (
  27. "0003_q3_business_alignment_and_content_search"
  28. )
  29. def test_initial_migration_exports_upgrade_and_downgrade():
  30. module = _load_revision_module()
  31. assert callable(module.upgrade)
  32. assert callable(module.downgrade)
  33. def test_initial_migration_contains_seven_tables_and_generated_columns():
  34. source = REVISION_PATH.read_text(encoding="utf-8")
  35. for table_name in (
  36. "sys_organization",
  37. "sys_user",
  38. "doc_category",
  39. "doc_document",
  40. "doc_attachment_binding",
  41. "doc_permission",
  42. "sys_audit_log",
  43. ):
  44. assert f'"{table_name}"' in source
  45. assert source.count('sa.Computed("IF(is_deleted = 0, 1, NULL)"') == 2
  46. assert "uq_doc_attachment_binding_active" in source
  47. assert "uq_doc_permission_active" in source
  48. def test_initial_migration_has_no_cascade():
  49. source = REVISION_PATH.read_text(encoding="utf-8")
  50. assert 'ondelete="CASCADE"' not in source
  51. assert source.count('ondelete="RESTRICT"') >= 8
  52. def test_downgrade_uses_dependency_safe_order():
  53. source = REVISION_PATH.read_text(encoding="utf-8")
  54. positions = [
  55. source.index(f'op.drop_table("{name}")')
  56. for name in (
  57. "sys_audit_log",
  58. "doc_permission",
  59. "doc_attachment_binding",
  60. "doc_document",
  61. "doc_category",
  62. "sys_user",
  63. "sys_organization",
  64. )
  65. ]
  66. assert positions == sorted(positions)
  67. def test_restore_migration_is_scoped_and_reversible():
  68. source = RESTORE_REVISION_PATH.read_text(encoding="utf-8")
  69. assert 'down_revision: str | None = "0001_initial_schema"' in source
  70. assert "RESTORE_DOCUMENT" in source
  71. assert "ix_doc_document_recycle_deleted" in source
  72. assert '["is_deleted", "deleted_at", "id"]' in source
  73. assert "drop_index" in source
  74. assert source.count("create_check_constraint") == 2
  75. assert "create_table" not in source
  76. assert "add_column" not in source
  77. def test_q3_migration_is_single_scoped_revision_with_safe_downgrade():
  78. source = Q3_REVISION_PATH.read_text(encoding="utf-8")
  79. assert (
  80. 'down_revision: str | None = "0002_add_restore_audit_action"'
  81. in source
  82. )
  83. assert "content_text" in source
  84. assert "content_extract_status" in source
  85. assert "content_extracted_at" in source
  86. assert "LONGTEXT" in source
  87. assert "auth_version = auth_version + 1" in source
  88. assert "role_code = 'USER'" in source
  89. assert "role_code IN ('USER', 'ADMIN')" in source
  90. assert "RuntimeError" in source
  91. assert "create_table" not in source