test_data_rule_frontend_contract.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  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 "/activate" not in source
  37. def test_reusable_authoring_panel_preserves_human_review_gate():
  38. source = AUTHORING.read_text(encoding="utf-8")
  39. assert "authoringSurface" in source
  40. assert "interpretRule" in source
  41. assert "clarification_required" in source
  42. assert "$emit('candidate'" in source
  43. assert "createRuleVersion" in source
  44. assert "publishRuleVersion" in source
  45. assert "先生成并发布 RuleVersion" in source
  46. assert "rules:publish" in source
  47. assert "$emit('version'" in source
  48. assert "自动执行" not in source
  49. def test_authoring_panel_exposes_governed_ai_lifecycle_without_code_editor():
  50. source = AUTHORING.read_text(encoding="utf-8")
  51. for token in [
  52. "schemaContextReady",
  53. "generation_receipt",
  54. "assumptions",
  55. "ambiguities",
  56. "modelProvenance",
  57. "validateRuleVersion",
  58. "testRuleVersion",
  59. "CompilationEvidence",
  60. ]:
  61. assert token in source
  62. assert "candidatePreview" not in source
  63. assert "<pre>{{ candidate" not in source
  64. def test_catalog_picker_selects_fixed_published_asset_versions():
  65. source = CATALOG.read_text(encoding="utf-8")
  66. for token in [
  67. "searchPublishedCatalog",
  68. "assetType",
  69. "published",
  70. "schema_compatibility",
  71. "latest_evidence",
  72. "v-skeleton-loader",
  73. "aria-label",
  74. "keydown",
  75. ]:
  76. assert token in source
  77. assert "$emit('input', item.id)" in source
  78. assert "item.name" in source
  79. assert "rule_uid" in source
  80. assert "standard_uid" in source
  81. def test_compilation_evidence_shows_trusted_chain_without_sensitive_payloads():
  82. source = EVIDENCE.read_text(encoding="utf-8")
  83. for token in [
  84. "generation",
  85. "logical_compile",
  86. "dry_run",
  87. "publication",
  88. "physical",
  89. "getRuleVersionEvidence",
  90. ]:
  91. assert token in source
  92. for forbidden in ["raw_sample", "secret", "password", "token"]:
  93. assert forbidden not in source.lower()
  94. assert "'ready'" in source
  95. def test_standard_links_published_rules_and_keeps_legacy_code_read_only():
  96. standard = STANDARD.read_text(encoding="utf-8")
  97. assert 'authoring-surface="data_standard"' in standard
  98. assert '@version="handleRuleVersion"' in standard
  99. assert "RuleCatalogPicker" in standard
  100. assert "rule_version_id" in standard
  101. assert "legacyMigration" in standard
  102. assert "readonly" in standard
  103. assert "迁移" in standard
  104. assert "dataStandardCodeGenerate" not in standard
  105. assert "handleCodeGenerate" not in standard
  106. assert "rule_version_status" not in standard
  107. def test_dataflow_assembler_uses_published_catalog_ids_without_inline_rules():
  108. source = ASSEMBLER.read_text(encoding="utf-8")
  109. dataflow = DATAFLOW.read_text(encoding="utf-8")
  110. assert "RuleCatalogPicker" in source
  111. assert "standard_version_id" in source
  112. assert "rule_version_id" in source
  113. assert "schema_compatibility" in source
  114. assert "releaseReadiness" in source
  115. assert "component_kind" in source
  116. assert "rule_spec" not in source
  117. assert "operation_code" not in source
  118. assert "ProductionLineAssembler" in dataflow
  119. assert "dataflow_spec" in dataflow
  120. assert "migration_metadata" in dataflow
  121. assert "rule_spec" not in dataflow
  122. assert "v-model=\"changeObj.rule\"" not in dataflow
  123. assert "handleRuleCandidate" not in dataflow
  124. def test_assembler_emits_a_real_closed_dataflow_spec_for_every_station_kind():
  125. standard_version_id = new_governance_uid()
  126. apply_rule_version_id = new_governance_uid()
  127. quality_rule_version_id = new_governance_uid()
  128. normalized = validate_dataflow_spec(
  129. {
  130. "schema_version": "1.0",
  131. "dataflow_uid": new_governance_uid(),
  132. "name": "客户数据生产线",
  133. "description": "固定受治理资产版本",
  134. "input_schema_refs": ["bd:customer_raw:v1"],
  135. "output_schema_ref": "bd:customer_clean:v1",
  136. "components": [
  137. {
  138. "id": "station_1",
  139. "type": "standard.enforce",
  140. "standard_version_id": standard_version_id,
  141. "stage": "quality_gate",
  142. "order": 1,
  143. },
  144. {
  145. "id": "station_2",
  146. "type": "rule.apply",
  147. "rule_version_id": apply_rule_version_id,
  148. "stage": "transform",
  149. "order": 2,
  150. "idempotency": {
  151. "strategy": "partition_replace",
  152. "key": "run_partition",
  153. },
  154. },
  155. {
  156. "id": "station_3",
  157. "type": "quality.check",
  158. "rule_version_id": quality_rule_version_id,
  159. "stage": "quality_gate",
  160. "order": 3,
  161. },
  162. ],
  163. "parameters": {},
  164. }
  165. )
  166. assert "idempotency" not in normalized["components"][0]
  167. assert "idempotency" in normalized["components"][1]
  168. assert "idempotency" not in normalized["components"][2]
  169. source = ASSEMBLER.read_text(encoding="utf-8")
  170. assert "station.component_kind === 'rule.apply'" in source
  171. assert "component.idempotency" in source
  172. assert "idempotency: {" not in source
  173. def test_catalog_never_guesses_schema_compatibility_from_arbitrary_objects():
  174. source = CATALOG.read_text(encoding="utf-8")
  175. assembler = ASSEMBLER.read_text(encoding="utf-8")
  176. assert "normalizeCompatibility" in source
  177. assert "Object.keys(item.schema_compatibility).length" not in source
  178. assert "unknown" in source
  179. assert "schema_compatibility !== 'compatible'" in assembler
  180. assert "服务端发布预检" in assembler
  181. assert "requestSequence" in source
  182. assert "requestId !== this.requestSequence" in source
  183. assert "getPublishedCatalogAsset" in source
  184. assert "inputSchemaRefs" in source
  185. assert "outputSchemaRef" in source
  186. def test_production_line_editor_renews_expiring_receipts_and_retries_only_safe_errors():
  187. source = DATAFLOW.read_text(encoding="utf-8")
  188. request = Path("frontend/src/utils/request.js").read_text(encoding="utf-8")
  189. assert "governedDraftExpiresAt" in source
  190. assert "data.expires_at" in source
  191. assert "expiresAt - Date.now() <= 60000" in source
  192. assert "ensureFreshDraftReservation" in source
  193. assert "draft_reservation_refresh_required" in source
  194. assert "governedDraftReplayRequired" in source
  195. assert "if (this.governedDraftReplayRequired) return" in source
  196. assert "await this.ensureDataflowUid(true)" in source
  197. assert source.count("await api.addDataFlow(payload)") == 2
  198. assert "error.response?.data" in request
  199. def test_new_dataflow_gets_server_governed_uid_before_assembly():
  200. api = API.read_text(encoding="utf-8")
  201. dataflow = DATAFLOW.read_text(encoding="utf-8")
  202. assert "createProductionLineDraftIdentity" in api
  203. assert "/rules/production-lines/draft-identity" in api
  204. assert "ensureDataflowUid" in dataflow
  205. assert "if (!this.dataflowUid) await this.ensureDataflowUid()" in dataflow
  206. assert "persistedUid" in dataflow
  207. assert "dataflow_uid: null" not in dataflow
  208. def test_standard_authoring_does_not_forge_validated_state_client_side():
  209. source = AUTHORING.read_text(encoding="utf-8")
  210. assert "candidate.candidate_type === 'standard'" not in source or (
  211. "status: 'validated'" not in source
  212. )
  213. assert "先发布规则,再在标准条款中绑定" in source
  214. def test_data_factory_exposes_release_ready_but_no_fake_activation():
  215. page = FACTORY.read_text(encoding="utf-8")
  216. index = FACTORY_INDEX.read_text(encoding="utf-8")
  217. assert "数据生产线投产" in page
  218. assert "getRuleCapabilities" in page
  219. assert "production_line_release" in page
  220. assert "data_factory_activation" in page
  221. assert "发布版本" in page
  222. assert "执行计划" in page
  223. assert "发布与执行计划已经可用" in page
  224. assert "投产激活仍保持关闭" in page
  225. assert "ProductionLineDeployment" in index