test_models.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  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. ContentExtractStatus,
  10. RoleCode,
  11. SecurityLevel,
  12. SECURITY_LEVEL_VALUES,
  13. )
  14. from dms.extensions import db
  15. EXPECTED_TABLES = {
  16. "sys_organization",
  17. "sys_user",
  18. "doc_category",
  19. "doc_document",
  20. "doc_attachment_binding",
  21. "doc_document_relation",
  22. "doc_sub_plan_binding",
  23. "doc_permission",
  24. "sys_audit_log",
  25. "sys_role",
  26. "sys_role_permission",
  27. "sys_ip_entry",
  28. "sys_org_ip_assignment",
  29. "sys_metric_sample",
  30. "sys_notification",
  31. }
  32. BUSINESS_TABLES = EXPECTED_TABLES - {
  33. "sys_audit_log",
  34. "sys_metric_sample",
  35. "sys_notification",
  36. }
  37. COMMON_COLUMNS = {
  38. "id",
  39. "created_by",
  40. "updated_by",
  41. "created_at",
  42. "updated_at",
  43. "deleted_at",
  44. "is_deleted",
  45. "row_version",
  46. }
  47. def test_expected_models_are_registered():
  48. assert set(db.metadata.tables) == EXPECTED_TABLES
  49. assert len(db.metadata.tables) == 15
  50. def test_business_tables_have_common_columns():
  51. for table_name in BUSINESS_TABLES:
  52. columns = {column.name for column in db.metadata.tables[table_name].columns}
  53. assert COMMON_COLUMNS <= columns
  54. def test_audit_log_has_append_only_exception_fields():
  55. columns = {
  56. column.name for column in db.metadata.tables["sys_audit_log"].columns
  57. }
  58. assert "created_at" in columns
  59. assert {"updated_at", "deleted_at", "is_deleted", "row_version"}.isdisjoint(
  60. columns
  61. )
  62. def test_notification_has_append_only_exception_fields():
  63. """契约1.34:通知表追加型——无软删/乐观锁/更新审计列,状态由 action/status 表达。"""
  64. columns = {
  65. column.name for column in db.metadata.tables["sys_notification"].columns
  66. }
  67. assert "created_at" in columns
  68. assert "handled_at" in columns
  69. assert {
  70. "updated_at",
  71. "deleted_at",
  72. "is_deleted",
  73. "row_version",
  74. }.isdisjoint(columns)
  75. def test_auth_version_field_exists_with_default_zero():
  76. column = db.metadata.tables["sys_user"].c.auth_version
  77. assert column.nullable is False
  78. assert str(column.server_default.arg) == "0"
  79. def test_attachment_type_field_exists():
  80. column = db.metadata.tables["doc_document"].c.attachment_type
  81. assert column.nullable is True
  82. def test_active_marker_columns_are_virtual_generated_columns():
  83. for table_name in (
  84. "doc_attachment_binding",
  85. "doc_permission",
  86. "sys_role",
  87. "sys_role_permission",
  88. "sys_ip_entry",
  89. "sys_org_ip_assignment",
  90. "sys_user",
  91. "doc_sub_plan_binding",
  92. ):
  93. column = db.metadata.tables[table_name].c.active_marker
  94. assert isinstance(column.computed, Computed)
  95. assert "IF(is_deleted = 0, 1, NULL)" in str(column.computed.sqltext)
  96. assert column.computed.persisted is False
  97. def test_role_default_marker_is_virtual_generated_column():
  98. column = db.metadata.tables["sys_role"].c.default_marker
  99. assert isinstance(column.computed, Computed)
  100. assert "IF(is_default = 1 AND is_deleted = 0, 1, NULL)" in str(
  101. column.computed.sqltext
  102. )
  103. assert column.computed.persisted is False
  104. def _unique_column_sets(table_name: str) -> set[tuple[str, ...]]:
  105. table = db.metadata.tables[table_name]
  106. return {
  107. tuple(column.name for column in constraint.columns)
  108. for constraint in table.constraints
  109. if isinstance(constraint, UniqueConstraint)
  110. }
  111. def test_active_binding_unique_constraint():
  112. assert (
  113. "main_document_id",
  114. "attachment_document_id",
  115. "active_marker",
  116. ) in _unique_column_sets("doc_attachment_binding")
  117. def test_active_permission_unique_constraint():
  118. assert (
  119. "document_id",
  120. "subject_id",
  121. "active_marker",
  122. ) in _unique_column_sets("doc_permission")
  123. def test_role_tables_unique_constraints():
  124. assert ("role_name", "active_marker") in _unique_column_sets("sys_role")
  125. assert ("default_marker",) in _unique_column_sets("sys_role")
  126. assert (
  127. "role_id",
  128. "security_level",
  129. "dept_scope",
  130. "active_marker",
  131. ) in _unique_column_sets("sys_role_permission")
  132. def test_ip_tables_unique_constraints():
  133. assert ("ip_value", "active_marker") in _unique_column_sets("sys_ip_entry")
  134. assert (
  135. "organization_id",
  136. "ip_entry_id",
  137. "active_marker",
  138. ) in _unique_column_sets("sys_org_ip_assignment")
  139. def test_sub_plan_binding_unique_constraint_and_indexes():
  140. assert (
  141. "main_document_id",
  142. "sub_document_id",
  143. "active_marker",
  144. ) in _unique_column_sets("doc_sub_plan_binding")
  145. names = {
  146. index.name
  147. for index in db.metadata.tables["doc_sub_plan_binding"].indexes
  148. }
  149. assert {
  150. "ix_doc_sub_plan_binding_main_active_sort",
  151. "ix_doc_sub_plan_binding_sub_active",
  152. } <= names
  153. def test_portal_user_external_id_unique_constraint():
  154. assert ("external_id", "active_marker") in _unique_column_sets("sys_user")
  155. def test_no_foreign_key_uses_cascade_and_audit_has_no_foreign_keys():
  156. for table in db.metadata.tables.values():
  157. for foreign_key in table.foreign_keys:
  158. assert foreign_key.ondelete in {"RESTRICT", "NO ACTION"}
  159. assert foreign_key.ondelete != "CASCADE"
  160. assert not db.metadata.tables["sys_audit_log"].foreign_keys
  161. def test_created_and_updated_by_have_no_foreign_keys():
  162. for table_name in BUSINESS_TABLES:
  163. table = db.metadata.tables[table_name]
  164. assert not table.c.created_by.foreign_keys
  165. assert not table.c.updated_by.foreign_keys
  166. def test_required_permanent_unique_codes_exist():
  167. assert ("org_code", "active_marker") in _unique_column_sets("sys_organization")
  168. assert ("username",) in _unique_column_sets("sys_user")
  169. assert ("category_code", "active_marker") in _unique_column_sets("doc_category")
  170. def test_critical_indexes_exist():
  171. expected = {
  172. "doc_document": {
  173. "ix_doc_document_type_active_updated",
  174. "ix_doc_document_file_hash",
  175. "ix_doc_document_attachment_type_active_updated",
  176. },
  177. "sys_audit_log": {
  178. "ix_sys_audit_log_created_at",
  179. "ix_sys_audit_log_user_created",
  180. "ix_sys_audit_log_organization_created",
  181. "ix_sys_audit_log_action_created",
  182. "ix_sys_audit_log_target_created",
  183. "ix_sys_audit_log_result_created",
  184. "ix_sys_audit_log_request_id",
  185. },
  186. }
  187. for table_name, names in expected.items():
  188. actual = {index.name for index in db.metadata.tables[table_name].indexes}
  189. assert names <= actual
  190. def test_permission_model_has_no_inherited_source_column():
  191. assert "permission_source" not in db.metadata.tables["doc_permission"].c
  192. def test_contract_enums_are_exact():
  193. assert {item.value for item in RoleCode} == {"USER", "ADMIN"}
  194. assert {item.value for item in ContentExtractStatus} == {
  195. "SUCCESS",
  196. "EMPTY",
  197. "UNSUPPORTED",
  198. "FAILED",
  199. }
  200. assert {item.value for item in AllowedModule} == {
  201. "DOCUMENT_BROWSER",
  202. "BACKEND_MANAGEMENT",
  203. "AUDIT_LOG",
  204. "ORGANIZATION_USER_MANAGEMENT",
  205. "SYSTEM_MANAGEMENT",
  206. }
  207. assert {item.value for item in AuditResult} == {"SUCCESS", "FAILURE"}
  208. assert {item.value for item in AuditAction} == {
  209. "LOGIN",
  210. "LOGOUT",
  211. "VIEW_DOCUMENT",
  212. "DOWNLOAD_DOCUMENT",
  213. "UPLOAD_DOCUMENT",
  214. "BATCH_IMPORT",
  215. "EDIT_DOCUMENT",
  216. "DELETE_DOCUMENT",
  217. "CREATE_CATEGORY",
  218. "EDIT_CATEGORY",
  219. "DELETE_CATEGORY",
  220. "CHANGE_PERMISSION",
  221. "UPLOAD_PERMISSION_VOUCHER",
  222. "DELETE_PERMISSION_VOUCHER",
  223. "MOVE_DOCUMENT",
  224. "MOVE_ATTACHMENT",
  225. "BIND_ATTACHMENT",
  226. "UNBIND_ATTACHMENT",
  227. "RESTORE_DOCUMENT",
  228. "CREATE_ORGANIZATION",
  229. "EDIT_ORGANIZATION",
  230. "DELETE_ORGANIZATION",
  231. "CREATE_USER",
  232. "EDIT_USER",
  233. "DELETE_USER",
  234. "RESET_USER_PASSWORD",
  235. "REORDER_DOCUMENT",
  236. "REORDER_ATTACHMENT",
  237. "REORDER_CATEGORY",
  238. "CREATE_ROLE",
  239. "EDIT_ROLE",
  240. "DELETE_ROLE",
  241. "CHANGE_ROLE_PERMISSION",
  242. "CHANGE_ROLE_USER",
  243. "DOCUMENT_PASSWORD_SET",
  244. "DOCUMENT_PASSWORD_REMOVE",
  245. "CREATE_IP_ENTRY",
  246. "EDIT_IP_ENTRY",
  247. "DELETE_IP_ENTRY",
  248. "ASSIGN_ORG_IP",
  249. "BACKUP_CREATE",
  250. "PREVIEW_CACHE_CLEAR",
  251. "FORCE_OFFLINE",
  252. "BACKUP_TEST_TARGET",
  253. "REORDER_ORGANIZATION",
  254. "REORDER_USER",
  255. "REORDER_ROLE",
  256. "BORROW_REQUEST_CREATE",
  257. "BORROW_REQUEST_APPROVE",
  258. "BORROW_REQUEST_REJECT",
  259. }
  260. assert {item.value for item in AuditTarget} == {
  261. "AUTH",
  262. "CATEGORY",
  263. "DOCUMENT",
  264. "ATTACHMENT",
  265. "PERMISSION",
  266. "ATTACHMENT_BINDING",
  267. "ORGANIZATION",
  268. "USER",
  269. "ROLE",
  270. "IP_ENTRY",
  271. "BACKUP",
  272. "SYSTEM",
  273. "BORROW_REQUEST",
  274. }
  275. assert SECURITY_LEVEL_VALUES == {
  276. SecurityLevel.PUBLIC: 10,
  277. SecurityLevel.INTERNAL: 20,
  278. SecurityLevel.SECRET: 30,
  279. SecurityLevel.CONFIDENTIAL: 40,
  280. SecurityLevel.TOP_SECRET: 50,
  281. }