test_models.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. from __future__ import annotations
  2. from sqlalchemy import Computed, UniqueConstraint
  3. import dms.models # noqa: F401
  4. from dms.common.enums import (
  5. AllowedModule,
  6. AuditAction,
  7. AuditResult,
  8. AuditTarget,
  9. SecurityLevel,
  10. SECURITY_LEVEL_VALUES,
  11. )
  12. from dms.extensions import db
  13. EXPECTED_TABLES = {
  14. "sys_organization",
  15. "sys_user",
  16. "doc_category",
  17. "doc_document",
  18. "doc_attachment_binding",
  19. "doc_permission",
  20. "sys_audit_log",
  21. }
  22. BUSINESS_TABLES = EXPECTED_TABLES - {"sys_audit_log"}
  23. COMMON_COLUMNS = {
  24. "id",
  25. "created_by",
  26. "updated_by",
  27. "created_at",
  28. "updated_at",
  29. "deleted_at",
  30. "is_deleted",
  31. "row_version",
  32. }
  33. def test_exactly_seven_models_are_registered():
  34. assert set(db.metadata.tables) == EXPECTED_TABLES
  35. assert len(db.metadata.tables) == 7
  36. def test_business_tables_have_common_columns():
  37. for table_name in BUSINESS_TABLES:
  38. columns = {column.name for column in db.metadata.tables[table_name].columns}
  39. assert COMMON_COLUMNS <= columns
  40. def test_audit_log_has_append_only_exception_fields():
  41. columns = {
  42. column.name for column in db.metadata.tables["sys_audit_log"].columns
  43. }
  44. assert "created_at" in columns
  45. assert {"updated_at", "deleted_at", "is_deleted", "row_version"}.isdisjoint(
  46. columns
  47. )
  48. def test_auth_version_field_exists_with_default_zero():
  49. column = db.metadata.tables["sys_user"].c.auth_version
  50. assert column.nullable is False
  51. assert str(column.server_default.arg) == "0"
  52. def test_attachment_type_field_exists():
  53. column = db.metadata.tables["doc_document"].c.attachment_type
  54. assert column.nullable is True
  55. def test_active_marker_columns_are_virtual_generated_columns():
  56. for table_name in ("doc_attachment_binding", "doc_permission"):
  57. column = db.metadata.tables[table_name].c.active_marker
  58. assert isinstance(column.computed, Computed)
  59. assert "IF(is_deleted = 0, 1, NULL)" in str(column.computed.sqltext)
  60. assert column.computed.persisted is False
  61. def _unique_column_sets(table_name: str) -> set[tuple[str, ...]]:
  62. table = db.metadata.tables[table_name]
  63. return {
  64. tuple(column.name for column in constraint.columns)
  65. for constraint in table.constraints
  66. if isinstance(constraint, UniqueConstraint)
  67. }
  68. def test_active_binding_unique_constraint():
  69. assert (
  70. "main_document_id",
  71. "attachment_document_id",
  72. "active_marker",
  73. ) in _unique_column_sets("doc_attachment_binding")
  74. def test_active_permission_unique_constraint():
  75. assert (
  76. "document_id",
  77. "subject_type",
  78. "subject_id",
  79. "active_marker",
  80. ) in _unique_column_sets("doc_permission")
  81. def test_no_foreign_key_uses_cascade_and_audit_has_no_foreign_keys():
  82. for table in db.metadata.tables.values():
  83. for foreign_key in table.foreign_keys:
  84. assert foreign_key.ondelete in {"RESTRICT", "NO ACTION"}
  85. assert foreign_key.ondelete != "CASCADE"
  86. assert not db.metadata.tables["sys_audit_log"].foreign_keys
  87. def test_created_and_updated_by_have_no_foreign_keys():
  88. for table_name in BUSINESS_TABLES:
  89. table = db.metadata.tables[table_name]
  90. assert not table.c.created_by.foreign_keys
  91. assert not table.c.updated_by.foreign_keys
  92. def test_required_permanent_unique_codes_exist():
  93. assert ("org_code",) in _unique_column_sets("sys_organization")
  94. assert ("username",) in _unique_column_sets("sys_user")
  95. assert ("category_code",) in _unique_column_sets("doc_category")
  96. def test_critical_indexes_exist():
  97. expected = {
  98. "doc_document": {
  99. "ix_doc_document_type_active_updated",
  100. "ix_doc_document_file_hash",
  101. "ix_doc_document_attachment_type_active_updated",
  102. },
  103. "sys_audit_log": {
  104. "ix_sys_audit_log_created_at",
  105. "ix_sys_audit_log_user_created",
  106. "ix_sys_audit_log_organization_created",
  107. "ix_sys_audit_log_action_created",
  108. "ix_sys_audit_log_target_created",
  109. "ix_sys_audit_log_result_created",
  110. "ix_sys_audit_log_request_id",
  111. },
  112. }
  113. for table_name, names in expected.items():
  114. actual = {index.name for index in db.metadata.tables[table_name].indexes}
  115. assert names <= actual
  116. def test_permission_model_has_no_inherited_source_column():
  117. assert "permission_source" not in db.metadata.tables["doc_permission"].c
  118. def test_contract_enums_are_exact():
  119. assert {item.value for item in AllowedModule} == {
  120. "DOCUMENT_BROWSER",
  121. "BACKEND_MANAGEMENT",
  122. "AUDIT_LOG",
  123. }
  124. assert {item.value for item in AuditResult} == {"SUCCESS", "FAILURE"}
  125. assert {item.value for item in AuditAction} == {
  126. "LOGIN",
  127. "LOGOUT",
  128. "VIEW_DOCUMENT",
  129. "DOWNLOAD_DOCUMENT",
  130. "UPLOAD_DOCUMENT",
  131. "BATCH_IMPORT",
  132. "EDIT_DOCUMENT",
  133. "DELETE_DOCUMENT",
  134. "CREATE_CATEGORY",
  135. "EDIT_CATEGORY",
  136. "DELETE_CATEGORY",
  137. "CHANGE_PERMISSION",
  138. "BIND_ATTACHMENT",
  139. "UNBIND_ATTACHMENT",
  140. "RESTORE_DOCUMENT",
  141. }
  142. assert {item.value for item in AuditTarget} == {
  143. "AUTH",
  144. "CATEGORY",
  145. "DOCUMENT",
  146. "ATTACHMENT",
  147. "PERMISSION",
  148. "ATTACHMENT_BINDING",
  149. }
  150. assert SECURITY_LEVEL_VALUES == {
  151. SecurityLevel.PUBLIC: 10,
  152. SecurityLevel.INTERNAL: 20,
  153. SecurityLevel.SECRET: 30,
  154. SecurityLevel.CONFIDENTIAL: 40,
  155. SecurityLevel.TOP_SECRET: 50,
  156. }