test_data_rule_frontend_contract.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. from pathlib import Path
  2. from app.core.common.identifiers import new_governance_uid
  3. from app.core.data_rules.contracts import validate_dataflow_spec
  4. API = Path("frontend/src/api/dataRules.js")
  5. AUTHORING = Path("frontend/src/components/DataRules/RuleAuthoringPanel.vue")
  6. CATALOG = Path("frontend/src/components/DataRules/RuleCatalogPicker.vue")
  7. EVIDENCE = Path("frontend/src/components/DataRules/CompilationEvidence.vue")
  8. ASSEMBLER = Path(
  9. "frontend/src/components/DataRules/ProductionLineAssembler.vue"
  10. )
  11. STANDARD = Path(
  12. "frontend/src/views/dataGovernance/dataStandard/components/edit.vue"
  13. )
  14. DATAFLOW = Path(
  15. "frontend/src/views/dataGovernance/dataProcess/components/edit.vue"
  16. )
  17. FACTORY = Path(
  18. "frontend/src/views/dataFactory/workflow/ProductionLineDeployment.vue"
  19. )
  20. FACTORY_INDEX = Path("frontend/src/views/dataFactory/workflow/index.vue")
  21. def test_rule_api_client_exposes_versions_publish_and_server_side_release():
  22. source = API.read_text(encoding="utf-8")
  23. assert "http.get('/rules/capabilities')" in source
  24. assert "http.post('/rules/interpret'" in source
  25. assert "http.post('/rules/validate'" in source
  26. assert "http.post('/rules/production-lines/resolve'" in source
  27. assert "http.post('/rules/rule-versions'" in source
  28. assert "http.post(`/rules/rule-versions/${versionId}/publish`)" in source
  29. assert "http.post(`/rules/rule-versions/${versionId}/validate`" in source
  30. assert "http.post(`/rules/rule-versions/${versionId}/test`" in source
  31. assert "http.get('/rules/catalog'" in source
  32. assert "/rules/catalog/assets/${assetType}/${versionId}/evidence" in source
  33. assert "http.post('/rules/standard-versions'" in source
  34. assert "http.post(`/rules/standard-versions/${versionId}/publish`)" in source
  35. assert "http.post(`/rules/production-lines/${dataflowUid}/release`" in source
  36. assert "listDataFlowDeployments" in source
  37. assert "/deploy-disabled" in source
  38. assert "/canary" in source
  39. assert "/activate" in source
  40. assert "/rollback" in source
  41. def test_reusable_authoring_panel_preserves_human_review_gate():
  42. source = AUTHORING.read_text(encoding="utf-8")
  43. assert "authoringSurface" in source
  44. assert "interpretRule" in source
  45. assert "clarification_required" in source
  46. assert "$emit('candidate'" in source
  47. assert "createRuleVersion" in source
  48. assert "publishRuleVersion" in source
  49. assert "先生成并发布 RuleVersion" in source
  50. assert "rules:publish" in source
  51. assert "$emit('version'" in source
  52. assert "自动执行" not in source
  53. def test_authoring_panel_exposes_governed_ai_lifecycle_without_code_editor():
  54. source = AUTHORING.read_text(encoding="utf-8")
  55. for token in [
  56. "schemaContextReady",
  57. "generation_receipt",
  58. "assumptions",
  59. "ambiguities",
  60. "modelProvenance",
  61. "validateRuleVersion",
  62. "testRuleVersion",
  63. "CompilationEvidence",
  64. ]:
  65. assert token in source
  66. assert "candidatePreview" not in source
  67. assert "<pre>{{ candidate" not in source
  68. def test_catalog_picker_selects_fixed_published_asset_versions():
  69. source = CATALOG.read_text(encoding="utf-8")
  70. for token in [
  71. "searchPublishedCatalog",
  72. "assetType",
  73. "published",
  74. "schema_compatibility",
  75. "latest_evidence",
  76. "v-skeleton-loader",
  77. "aria-label",
  78. "keydown",
  79. ]:
  80. assert token in source
  81. assert "$emit('input', item.id)" in source
  82. assert "item.name" in source
  83. assert "rule_uid" in source
  84. assert "standard_uid" in source
  85. def test_compilation_evidence_shows_trusted_chain_without_sensitive_payloads():
  86. source = EVIDENCE.read_text(encoding="utf-8")
  87. for token in [
  88. "generation",
  89. "logical_compile",
  90. "dry_run",
  91. "publication",
  92. "physical",
  93. "getRuleVersionEvidence",
  94. ]:
  95. assert token in source
  96. for forbidden in ["raw_sample", "secret", "password", "token"]:
  97. assert forbidden not in source.lower()
  98. assert "'ready'" in source
  99. def test_standard_links_published_rules_and_keeps_legacy_code_read_only():
  100. standard = STANDARD.read_text(encoding="utf-8")
  101. assert 'authoring-surface="data_standard"' in standard
  102. assert '@version="handleRuleVersion"' in standard
  103. assert "RuleCatalogPicker" in standard
  104. assert "rule_version_id" in standard
  105. assert "legacyMigration" in standard
  106. assert "readonly" in standard
  107. assert "迁移" in standard
  108. assert "dataStandardCodeGenerate" not in standard
  109. assert "handleCodeGenerate" not in standard
  110. assert "rule_version_status" not in standard
  111. def test_dataflow_assembler_uses_published_catalog_ids_without_inline_rules():
  112. source = ASSEMBLER.read_text(encoding="utf-8")
  113. dataflow = DATAFLOW.read_text(encoding="utf-8")
  114. assert "RuleCatalogPicker" in source
  115. assert "standard_version_id" in source
  116. assert "rule_version_id" in source
  117. assert "schema_compatibility" in source
  118. assert "releaseReadiness" in source
  119. assert "component_kind" in source
  120. assert "rule_spec" not in source
  121. assert "operation_code" not in source
  122. assert "ProductionLineAssembler" in dataflow
  123. assert "dataflow_spec" in dataflow
  124. assert "migration_metadata" in dataflow
  125. assert "rule_spec" not in dataflow
  126. assert "v-model=\"changeObj.rule\"" not in dataflow
  127. assert "handleRuleCandidate" not in dataflow
  128. def test_assembler_emits_a_real_closed_dataflow_spec_for_every_station_kind():
  129. standard_version_id = new_governance_uid()
  130. apply_rule_version_id = new_governance_uid()
  131. quality_rule_version_id = new_governance_uid()
  132. normalized = validate_dataflow_spec(
  133. {
  134. "schema_version": "1.0",
  135. "dataflow_uid": new_governance_uid(),
  136. "name": "客户数据生产线",
  137. "description": "固定受治理资产版本",
  138. "input_schema_refs": ["bd:customer_raw:v1"],
  139. "output_schema_ref": "bd:customer_clean:v1",
  140. "components": [
  141. {
  142. "id": "station_1",
  143. "type": "standard.enforce",
  144. "standard_version_id": standard_version_id,
  145. "stage": "quality_gate",
  146. "order": 1,
  147. },
  148. {
  149. "id": "station_2",
  150. "type": "rule.apply",
  151. "rule_version_id": apply_rule_version_id,
  152. "stage": "transform",
  153. "order": 2,
  154. "idempotency": {
  155. "strategy": "partition_replace",
  156. "key": "run_partition",
  157. },
  158. },
  159. {
  160. "id": "station_3",
  161. "type": "quality.check",
  162. "rule_version_id": quality_rule_version_id,
  163. "stage": "quality_gate",
  164. "order": 3,
  165. },
  166. ],
  167. "parameters": {},
  168. }
  169. )
  170. assert "idempotency" not in normalized["components"][0]
  171. assert "idempotency" in normalized["components"][1]
  172. assert "idempotency" not in normalized["components"][2]
  173. source = ASSEMBLER.read_text(encoding="utf-8")
  174. assert "station.component_kind === 'rule.apply'" in source
  175. assert "component.idempotency" in source
  176. assert "idempotency: {" not in source
  177. def test_catalog_never_guesses_schema_compatibility_from_arbitrary_objects():
  178. source = CATALOG.read_text(encoding="utf-8")
  179. assembler = ASSEMBLER.read_text(encoding="utf-8")
  180. assert "normalizeCompatibility" in source
  181. assert "Object.keys(item.schema_compatibility).length" not in source
  182. assert "unknown" in source
  183. assert "schema_compatibility !== 'compatible'" in assembler
  184. assert "服务端发布预检" in assembler
  185. assert "requestSequence" in source
  186. assert "requestId !== this.requestSequence" in source
  187. assert "getPublishedCatalogAsset" in source
  188. assert "inputSchemaRefs" in source
  189. assert "outputSchemaRef" in source
  190. def test_production_line_editor_renews_expiring_receipts_and_retries_only_safe_errors():
  191. source = DATAFLOW.read_text(encoding="utf-8")
  192. request = Path("frontend/src/utils/request.js").read_text(encoding="utf-8")
  193. assert "governedDraftExpiresAt" in source
  194. assert "data.expires_at" in source
  195. assert "expiresAt - Date.now() <= 60000" in source
  196. assert "ensureFreshDraftReservation" in source
  197. assert "draft_reservation_refresh_required" in source
  198. assert "governedDraftReplayRequired" in source
  199. assert "if (this.governedDraftReplayRequired) return" in source
  200. assert "await this.ensureDataflowUid(true)" in source
  201. assert source.count("await api.addDataFlow(payload)") == 2
  202. assert "error.response?.data" in request
  203. def test_new_dataflow_gets_server_governed_uid_before_assembly():
  204. api = API.read_text(encoding="utf-8")
  205. dataflow = DATAFLOW.read_text(encoding="utf-8")
  206. assert "createProductionLineDraftIdentity" in api
  207. assert "/rules/production-lines/draft-identity" in api
  208. assert "ensureDataflowUid" in dataflow
  209. assert "if (!this.dataflowUid) await this.ensureDataflowUid()" in dataflow
  210. assert "persistedUid" in dataflow
  211. assert "dataflow_uid: null" not in dataflow
  212. def test_standard_authoring_does_not_forge_validated_state_client_side():
  213. source = AUTHORING.read_text(encoding="utf-8")
  214. assert "candidate.candidate_type === 'standard'" not in source or (
  215. "status: 'validated'" not in source
  216. )
  217. assert "先发布规则,再在标准条款中绑定" in source
  218. def test_data_factory_exposes_governed_trial_activation_and_rollback():
  219. page = FACTORY.read_text(encoding="utf-8")
  220. index = FACTORY_INDEX.read_text(encoding="utf-8")
  221. assert "数据生产线投产" in page
  222. assert "getRuleCapabilities" in page
  223. assert "data_factory_readiness" in page
  224. assert "listDataFlowDeployments" in page
  225. assert "listDataFlowDeploymentCandidates" in page
  226. assert "createDataFlowDeployment" in page
  227. assert "deployDataFlowDisabled" in page
  228. assert "runDataFlowCanary" in page
  229. assert "activateDataFlowDeployment" in page
  230. assert "rollbackDataFlowDeployment" in page
  231. assert "reconcileDataFlowDeploy" in page
  232. assert "reconcileDataFlowActivate" in page
  233. assert "reconcileDataFlowRollback" in page
  234. assert "dataflows:deploy" in page
  235. assert "dataflows:canary" in page
  236. assert "rules:execute" in page
  237. assert "dataflows:activate" in page
  238. assert "dataflows:rollback" in page
  239. assert "生产线包 Hash" in page
  240. assert "试生产证据" in page
  241. assert "状态履历" in page
  242. assert "生产线入厂" in page
  243. assert "window.localStorage.getItem(storageKey)" in page
  244. assert "window.localStorage.setItem(storageKey, value)" in page
  245. assert "error?.error?.code === 'terminal_conflict'" in page
  246. assert "Number(error?.code) === 409" not in page
  247. assert "unknown_operations" in page
  248. assert "v-dialog" in page
  249. assert "ProductionLineDeployment" in index