@shundoo-ai/dsh-cosmic 1.0.21 → 1.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/cosmic-build/LOCAL-PATCHES.md +43 -14
- package/skills/cosmic-build/SKILL.md +24 -9
- package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +26 -17
- package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +96 -2
- package/skills/cosmic-build/knowledge/cosmic/arch/security/security_standard.md +4 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/allimport.md +2 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/core/dynamic-object-collection.md +6 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/core/model.md +9 -9
- package/skills/cosmic-build/knowledge/cosmic/platform/core/view.md +5 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +8 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +5 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/print-plugin-guide.md +1 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/report-plugin.md +269 -57
- package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +130 -23
- package/skills/cosmic-build/knowledge/cosmic/platform/reporttreedata-plugin.md +48 -28
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/_index.md +12 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/attachment-name-process.md +17 -13
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/common/dynamic-update-field.md +4 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/filter-grid/filter-grid-set-value.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-basedatafield.md +2 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-container-init.md +0 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-popup.md +0 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/filter-value-parse.md +5 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/list-show-form.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/control/list/query-field-dynamic.md +0 -7
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/business-data/delete-service.md +24 -29
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-batch.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/ksql/db-direct-access.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/entity-object-access.md +8 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/data-access/qfilter/qfilter-compose.md +6 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/after-bind-data-set-visible.md +5 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/click-return-data.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/closed-call-back-set-value.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/register-listener.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/event/set-filter-tab-org.md +3 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dispatch-service/microservice-dev.md +18 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/dist-lock/dist-lock-basic.md +3 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/mq/mq-subscribe.md +5 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/async-operation.md +10 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/big-task-async.md +4 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/infra/thread-pool/schedule-task.md +9 -7
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-interactive.md +33 -34
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/operation-plugin-events.md +30 -22
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-async-query.md +133 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-batch-query.md +26 -22
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-chart.md +112 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-dataset-assembly.md +200 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-head-filter.md +177 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-hyperlink-drill.md +216 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-list-data-query.md +12 -12
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-merge-column.md +44 -6
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-print-data.md +163 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-row-data-toolbar.md +188 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-subtotal-total.md +139 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-data-query.md +108 -12
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/report/report-tree-list.md +181 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/system-service/report-plugin-interface.md +3 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-basedata-cascade-confirm.md +14 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-before-item-validate.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-build-filter.md +1 -1
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-multi-level-filter.md +11 -11
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/tree/tree-list-refresh-node.md +22 -15
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +2 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-entity-model.md +3 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-exception.md +9 -5
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-orm-access.md +4 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-request-context.md +7 -3
- package/skills/cosmic-build/knowledge/cosmic/platform/standards.md +28 -3
- package/skills/cosmic-build/references/compile-guide.md +36 -1
- package/skills/cosmic-build/references/dcs-code-rules.md +180 -0
- package/skills/cosmic-build/references/delivery.md +16 -2
- package/skills/cosmic-build/references/execution-contract.md +15 -3
- package/skills/cosmic-build/references/java-plugin-delivery.md +21 -11
- package/skills/cosmic-build/references/plugin-handoff-input.md +82 -24
- package/skills/cosmic-build/references/plugin-runtime-routing.md +23 -6
- package/skills/cosmic-build/references/report-filter-contract.md +53 -15
- package/skills/cosmic-build/references/sdk-inspection.md +4 -1
- package/skills/cosmic-build/references/type-registry.json +26 -17
- package/skills/cosmic-build/schemas/data-model-authoring.schema.json +1 -1
- package/skills/cosmic-build/scripts/_http_client.py +3 -0
- package/skills/cosmic-build/scripts/compile_plugin_source.py +251 -42
- package/skills/cosmic-build/scripts/configure_list.py +112 -0
- package/skills/cosmic-build/scripts/dcs-rule-catalog.json +799 -0
- package/skills/cosmic-build/scripts/dcs_code_gate.py +1976 -0
- package/skills/cosmic-build/scripts/execution_binding.py +25 -0
- package/skills/cosmic-build/scripts/platform_api_contract.py +352 -7
- package/skills/cosmic-build/scripts/plugin_source_gate.py +9 -1
- package/skills/cosmic-build/scripts/plugin_target_sdk.py +12 -8
- package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +488 -88
- package/skills/cosmic-build/scripts/query_plugin_handoff.py +207 -20
- package/skills/cosmic-build/scripts/register_plugins.py +27 -10
- package/skills/cosmic-build/scripts/render_output_cards.py +57 -20
- package/skills/cosmic-build/scripts/resolve_output_path.py +60 -26
- package/skills/cosmic-build/scripts/run_java_tests.py +162 -0
- package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +28 -0
- package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +1 -22
- package/skills/cosmic-build/scripts/test/fixtures/list_column_property_declarations.json +887 -0
- package/skills/cosmic-build/scripts/test/fixtures/operation-entry-field-prepare.java +78 -0
- package/skills/cosmic-build/scripts/test/test_add_field_required_contract.py +12 -0
- package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +23 -2
- package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +287 -4
- package/skills/cosmic-build/scripts/test/test_configure_list_delivery.py +132 -0
- package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +58 -1
- package/skills/cosmic-build/scripts/test/test_data_model_contract.py +19 -0
- package/skills/cosmic-build/scripts/test/test_dcs_code_gate.py +901 -0
- package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +1 -1
- package/skills/cosmic-build/scripts/test/test_direct_cli_architecture.py +2 -0
- package/skills/cosmic-build/scripts/test/test_http_client.py +6 -0
- package/skills/cosmic-build/scripts/test/test_java_toolchain_feedback.py +206 -0
- package/skills/cosmic-build/scripts/test/test_kd_api.py +21 -0
- package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +184 -0
- package/skills/cosmic-build/scripts/test/test_mcp_cli.py +139 -2
- package/skills/cosmic-build/scripts/test/test_menu_creation_closure.py +16 -0
- package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +55 -1
- package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +1 -1
- package/skills/cosmic-build/scripts/test/test_operation_field_preparation.py +84 -0
- package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +322 -1
- package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +951 -4
- package/skills/cosmic-build/scripts/test/test_plugin_source_gate.py +30 -2
- package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +21 -0
- package/skills/cosmic-build/scripts/test/test_property_reroute.py +165 -0
- package/skills/cosmic-build/scripts/test/test_property_schema_first.py +35 -0
- package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +221 -0
- package/skills/cosmic-build/scripts/test/test_register_plugins.py +76 -4
- package/skills/cosmic-build/scripts/test/test_render_output_cards.py +75 -3
- package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -5
- package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +103 -11
- package/skills/cosmic-build/scripts/test/test_report_plugin_source_gate.py +46 -16
- package/skills/cosmic-build/scripts/test/test_report_properties.py +74 -15
- package/skills/cosmic-build/scripts/test/test_report_query_data_source.py +3 -1
- package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +1 -2
- package/skills/cosmic-build/scripts/test/test_test_environment.py +76 -1
- package/skills/cosmic-build/scripts/test_environment.py +67 -1
- package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +52 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +16 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +59 -26
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +75 -18
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_auto.py +39 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/pdm_client.py +97 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +10 -12
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +3 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +38 -29
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +400 -233
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +87 -45
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +42 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +135 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +17 -11
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_reroute.py +15 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +1429 -238
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/validators.py +56 -28
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +380 -134
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_export.py +12 -11
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/meta_verifier.py +42 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_add_field_placement_closure.py +8 -8
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_configuration_readback.py +94 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +47 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_metadata_unchanged_fail_closed.py +87 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_dym_evidence.py +23 -23
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_entity_integrity.py +279 -4
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_field_guard.py +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +509 -32
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +178 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_recreate_plan.py +125 -10
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_serve_contract.py +208 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +259 -50
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_inventory_perf.py +29 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +20 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +18 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +35 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_identity_guard.py +25 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_time_range_rule.py +98 -0
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/domain-model/report-model.md +20 -9
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/report-modeling-guide.md +55 -25
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/property-guides/report-column-properties.md +15 -39
- package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +19 -17
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +82 -38
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-batch.md +64 -19
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-plugin.md +32 -20
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/examples/golden-example-report-tree.md +78 -31
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/report-artifact-schema.json +2 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-companion-fields.md +3 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-list-configuration.md +2 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +3 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +27 -4
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +99 -44
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_examples.py +31 -4
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/test_report_validator.py +129 -29
- package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +6 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/validation.md +2 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/SKILL.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +104 -25
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +9 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +39 -12
- package/skills/cosmic-build/skills/mcp-server/SKILL.md +39 -24
- package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +20 -6
- package/skills/cosmic-dev-best-practices/SKILL.md +1 -1
- package/skills/cosmic-dev-best-practices/references/README.md +2 -2
- package/skills/cosmic-dev-best-practices/references/basedata-dev-convention.md +2 -2
- package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +4 -4
- package/skills/cosmic-dev-best-practices/references/design-document-convention.md +2 -2
- package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +4 -4
- package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +13 -13
- package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/README.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +109 -13
- package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +1 -1
- package/skills/erp-analysis/references/delivery-contract.md +2 -1
- package/skills/erp-analysis/templates/AGENTS.template.md +3 -1
- package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +0 -169
- package/skills/cosmic-build/assets/platform-report-filters/platform.patch +0 -98
- package/skills/cosmic-build/scripts/apply_report_filter_extension.py +0 -39
package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py
CHANGED
|
@@ -42,16 +42,68 @@ def _column(field_key, field_type="decimal", presentation=None, date_index=None)
|
|
|
42
42
|
return column
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def
|
|
46
|
-
|
|
45
|
+
def _compile_requirements(columns=(), filters=(), plugins=(("kd.kdtest.report.P", "Java"),)):
|
|
46
|
+
"""仅测试原始/历史需求编译;此路径不证明输入可写,也不调用 writer。"""
|
|
47
47
|
normalized_filters = report_ops.normalize_report_filters_input(list(filters)) if filters else []
|
|
48
48
|
return report_ops.compile_report_plugin_contract({
|
|
49
|
-
"reportColumns":
|
|
49
|
+
"reportColumns": list(columns),
|
|
50
50
|
"reportFilters": normalized_filters,
|
|
51
51
|
"reportPlugins": [{"className": name, "type": kind} for name, kind in plugins],
|
|
52
52
|
}, "kdtest_rpt")
|
|
53
53
|
|
|
54
54
|
|
|
55
|
+
class DefaultSortContractTest(unittest.TestCase):
|
|
56
|
+
def _artifact(self):
|
|
57
|
+
return {
|
|
58
|
+
"reportColumns": [{"kind": "group", "fieldKey": "group", "caption": "Group",
|
|
59
|
+
"children": [_column("created", "date", date_index="CreateTime"),
|
|
60
|
+
_column("amount")]}],
|
|
61
|
+
"reportSorts": [{"fieldKey": "created", "direction": "desc"},
|
|
62
|
+
{"fieldKey": "amount", "direction": "asc"}],
|
|
63
|
+
"reportPlugins": [{"className": "kd.test.report.P", "type": "Java", "enabled": True}],
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
def test_order_and_dataset_alias_reach_query_contract(self):
|
|
67
|
+
artifact = self._artifact()
|
|
68
|
+
contract = report_ops.compile_report_plugin_contract(artifact, "kdtest_rpt")
|
|
69
|
+
self.assertEqual(contract["defaultSorts"], [
|
|
70
|
+
{"fieldKey": "created", "dateIndex": "CreateTime", "direction": "desc"},
|
|
71
|
+
{"fieldKey": "amount", "dateIndex": "amount", "direction": "asc"},
|
|
72
|
+
])
|
|
73
|
+
self.assertEqual(contract["defaultSortPolicy"]["priority"], "interactiveThenDefault")
|
|
74
|
+
self.assertFalse(contract["defaultSortPolicy"]["metadataWritten"])
|
|
75
|
+
self.assertFalse(contract["defaultSortPolicy"]["runtimeVerified"])
|
|
76
|
+
self.assertNotIn("dateIndex", artifact["reportSorts"][0])
|
|
77
|
+
|
|
78
|
+
def test_missing_or_group_sort_reference_fails(self):
|
|
79
|
+
for key in ("missing", "group"):
|
|
80
|
+
with self.subTest(key=key):
|
|
81
|
+
artifact = self._artifact()
|
|
82
|
+
artifact["reportSorts"][0]["fieldKey"] = key
|
|
83
|
+
with self.assertRaisesRegex(ValueError, "sort columns not found"):
|
|
84
|
+
report_ops.compile_report_default_sorts(artifact)
|
|
85
|
+
|
|
86
|
+
def test_missing_dataset_binding_is_an_input_error(self):
|
|
87
|
+
artifact = self._artifact()
|
|
88
|
+
del artifact["reportColumns"][0]["children"][0]["dateIndex"]
|
|
89
|
+
with self.assertRaisesRegex(ValueError, "dateIndex"):
|
|
90
|
+
report_ops.compile_report_default_sorts(artifact)
|
|
91
|
+
|
|
92
|
+
def test_sorting_requires_one_enabled_java_fetch_plugin(self):
|
|
93
|
+
for plugins in ([], [{"className": "P", "type": "JS"}],
|
|
94
|
+
[{"className": "P", "type": "KingScript"}],
|
|
95
|
+
[{"className": "P", "enabled": False}],
|
|
96
|
+
[{"className": "P"}, {"className": "Q"}]):
|
|
97
|
+
with self.subTest(plugins=plugins):
|
|
98
|
+
artifact = self._artifact()
|
|
99
|
+
artifact["reportPlugins"] = plugins
|
|
100
|
+
artifact["queryDataSource"] = {"dataSourceNumber": "ds"}
|
|
101
|
+
with self.assertRaisesRegex(ValueError, "REPORT_DEFAULT_SORT_PLUGIN_REQUIRED"):
|
|
102
|
+
report_ops.compile_report_default_sorts(artifact)
|
|
103
|
+
artifact.pop("reportSorts")
|
|
104
|
+
self.assertEqual(report_ops.compile_report_default_sorts(artifact), [])
|
|
105
|
+
|
|
106
|
+
|
|
55
107
|
class PresentationInputContractTest(unittest.TestCase):
|
|
56
108
|
"""Presentation is validated against the column it decorates."""
|
|
57
109
|
|
|
@@ -97,11 +149,107 @@ class PresentationInputContractTest(unittest.TestCase):
|
|
|
97
149
|
self.assertIn("scale must be an integer", self._reject(
|
|
98
150
|
_column("amt", presentation={"kind": "decimal", "scale": 42})))
|
|
99
151
|
|
|
100
|
-
def
|
|
152
|
+
def test_an_explicit_decimal_presentation_survives_normalization(self):
|
|
101
153
|
columns = report_ops.normalize_report_columns_input([
|
|
102
|
-
_column("
|
|
103
|
-
self.assertEqual(columns[0]["presentation"],
|
|
104
|
-
|
|
154
|
+
_column("value", presentation={"kind": "decimal", "scale": 2})])
|
|
155
|
+
self.assertEqual(columns[0]["presentation"], {"kind": "decimal", "scale": 2})
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class DynamicPrecisionWriteGuardTest(unittest.TestCase):
|
|
159
|
+
"""测试真实门禁和入口;HTTP/auth 仅作不应触达的边界探针。"""
|
|
160
|
+
|
|
161
|
+
def _columns(self, kind="amount", field_type="bigint", hide=True, nested=False, scale=None):
|
|
162
|
+
key = "measureUnitField" if kind == "qty" else "currencyField"
|
|
163
|
+
spec = {"kind": kind, key: "reference"}
|
|
164
|
+
if scale is not None:
|
|
165
|
+
spec["scale"] = scale
|
|
166
|
+
columns = [_column("value", presentation=spec),
|
|
167
|
+
{**_column("reference", field_type), "hide": hide}]
|
|
168
|
+
return [{"kind": "group", "fieldKey": "group", "caption": "Group", "children": columns}] if nested else columns
|
|
169
|
+
|
|
170
|
+
def test_scalar_targets_never_prove_a_precision_reference(self):
|
|
171
|
+
import copy
|
|
172
|
+
for kind in ("amount", "price", "qty"):
|
|
173
|
+
for field_type in report_ops._REPORT_COLUMN_FIELD_TYPES:
|
|
174
|
+
for hide in (True, False):
|
|
175
|
+
for nested in (True, False):
|
|
176
|
+
for scale in (None, 2):
|
|
177
|
+
with self.subTest(kind=kind, fieldType=field_type, hide=hide, nested=nested, scale=scale):
|
|
178
|
+
columns = self._columns(kind, field_type, hide, nested, scale)
|
|
179
|
+
before = copy.deepcopy(columns)
|
|
180
|
+
with self.assertRaisesRegex(ValueError, "REPORT_PRECISION_REFERENCE_UNVERIFIED"):
|
|
181
|
+
report_ops.normalize_report_columns_input(columns)
|
|
182
|
+
self.assertEqual(columns, before)
|
|
183
|
+
|
|
184
|
+
def test_direct_writers_and_dry_run_refuse_before_http(self):
|
|
185
|
+
from unittest import mock
|
|
186
|
+
with mock.patch.object(report_ops, "_api_call") as http:
|
|
187
|
+
for writer in (report_ops.set_report_columns, report_ops.add_report_columns):
|
|
188
|
+
for dry_run in (True, False):
|
|
189
|
+
with self.subTest(writer=writer.__name__, dry_run=dry_run):
|
|
190
|
+
with self.assertRaisesRegex(ValueError, "REPORT_PRECISION_REFERENCE_UNVERIFIED"):
|
|
191
|
+
writer({}, "fixture", "FID", self._columns(nested=True), dry_run=dry_run)
|
|
192
|
+
http.assert_not_called()
|
|
193
|
+
|
|
194
|
+
def test_serve_and_pipeline_refuse_before_writers(self):
|
|
195
|
+
import cosmic_api
|
|
196
|
+
from unittest import mock
|
|
197
|
+
for kind in ("amount", "price", "qty"):
|
|
198
|
+
with mock.patch.object(cosmic_api, "do_set_report_columns") as write, \
|
|
199
|
+
mock.patch.object(cosmic_api, "do_add_report_columns") as append, \
|
|
200
|
+
mock.patch.object(cosmic_api, "do_create_page") as create:
|
|
201
|
+
for cmd in ("setReportColumns", "addReportColumns"):
|
|
202
|
+
with self.assertRaisesRegex(ValueError, "REPORT_PRECISION_REFERENCE_UNVERIFIED"):
|
|
203
|
+
cosmic_api._dispatch_serve({}, "fixture", cmd, {"columns": self._columns(kind)})
|
|
204
|
+
with self.assertRaisesRegex(ValueError, "REPORT_PRECISION_REFERENCE_UNVERIFIED"):
|
|
205
|
+
cosmic_api._execute_report_pipeline({}, "fixture", {"reportColumns": self._columns(kind)}, "APP", "UNIT")
|
|
206
|
+
write.assert_not_called()
|
|
207
|
+
append.assert_not_called()
|
|
208
|
+
create.assert_not_called()
|
|
209
|
+
|
|
210
|
+
def test_raw_contract_keeps_requirement_without_claiming_runtime_success(self):
|
|
211
|
+
import copy
|
|
212
|
+
for kind in ("amount", "price", "qty"):
|
|
213
|
+
columns = self._columns(kind)
|
|
214
|
+
artifact = {"reportColumns": columns, "reportSorts": [{"fieldKey": "value", "direction": "asc"}],
|
|
215
|
+
"reportPlugins": [{"className": "kd.test.report.P", "type": "Java", "enabled": True}]}
|
|
216
|
+
before = copy.deepcopy(artifact)
|
|
217
|
+
contract = report_ops.compile_report_plugin_contract(artifact)
|
|
218
|
+
self.assertEqual(artifact, before)
|
|
219
|
+
self.assertEqual(contract["defaultSorts"], [{"fieldKey": "value", "dateIndex": "value", "direction": "asc"}])
|
|
220
|
+
entry = contract["columnPresentation"][0]
|
|
221
|
+
source = entry["precisionSource"]
|
|
222
|
+
self.assertEqual(source["requiredRuntimeValueType"], "DynamicObject")
|
|
223
|
+
self.assertEqual(source["fieldKey"], "reference")
|
|
224
|
+
self.assertFalse(source["rawIdIsReference"])
|
|
225
|
+
self.assertFalse(source["runtimeVerified"])
|
|
226
|
+
self.assertTrue(source["metadataWriteBlocked"])
|
|
227
|
+
self.assertEqual(len(source["requiredEvidence"]), 3)
|
|
228
|
+
self.assertEqual(columns[0]["presentation"]["kind"], kind)
|
|
229
|
+
|
|
230
|
+
def test_dynamic_precision_cannot_refresh_a_successful_authoring_baseline(self):
|
|
231
|
+
from unittest import mock
|
|
232
|
+
with tempfile.TemporaryDirectory() as root:
|
|
233
|
+
report_ops.write_report_authoring_state(root, "sample_rpt", "FID",
|
|
234
|
+
{"reportColumns": [_column("value")]}, {"reportColumns": [_column("value")]})
|
|
235
|
+
path = report_ops.report_authoring_path(root, "sample_rpt")
|
|
236
|
+
before = path.read_bytes()
|
|
237
|
+
for kind in ("amount", "price", "qty"):
|
|
238
|
+
with mock.patch.object(report_ops, "_api_call") as http:
|
|
239
|
+
with self.assertRaisesRegex(ValueError, "REPORT_PRECISION_REFERENCE_UNVERIFIED"):
|
|
240
|
+
report_ops.write_report_authoring_state(root, "sample_rpt", "FID",
|
|
241
|
+
{"reportColumns": self._columns(kind)}, {})
|
|
242
|
+
http.assert_not_called()
|
|
243
|
+
self.assertEqual(path.read_bytes(), before)
|
|
244
|
+
|
|
245
|
+
def test_supported_unbound_types_are_not_precision_fallbacks(self):
|
|
246
|
+
columns = [_column("id", "bigint"), _column("date", "date"),
|
|
247
|
+
_column("measure", presentation={"kind": "decimal", "scale": 3}),
|
|
248
|
+
_column("ref", "bigint", {"kind": "basedata", "entityId": "bd_item", "displayProp": "name"})]
|
|
249
|
+
normalized = report_ops.normalize_report_columns_input(columns)
|
|
250
|
+
self.assertEqual(normalized, columns)
|
|
251
|
+
for entry in _compile_requirements(columns=normalized)["columnPresentation"]:
|
|
252
|
+
self.assertNotIn("precisionSource", entry)
|
|
105
253
|
|
|
106
254
|
|
|
107
255
|
class PresentationStaysOutOfPlatformPayloadTest(unittest.TestCase):
|
|
@@ -109,14 +257,14 @@ class PresentationStaysOutOfPlatformPayloadTest(unittest.TestCase):
|
|
|
109
257
|
|
|
110
258
|
def test_column_payload_carries_only_contract_properties(self):
|
|
111
259
|
columns = report_ops.normalize_report_columns_input([
|
|
112
|
-
_column("
|
|
260
|
+
_column("value", presentation={"kind": "decimal", "scale": 2})])
|
|
113
261
|
payload = report_ops._build_columns_payload(columns)
|
|
114
262
|
self.assertEqual(sorted(payload[0]), ["caption", "dateIndex", "fieldKey", "fieldType", "kind"])
|
|
115
263
|
|
|
116
264
|
def test_nested_group_children_are_also_clean(self):
|
|
117
265
|
columns = report_ops.normalize_report_columns_input([{
|
|
118
266
|
"kind": "group", "fieldKey": "grp", "caption": "G", "children": [
|
|
119
|
-
_column("
|
|
267
|
+
_column("value", presentation={"kind": "decimal", "scale": 3})]}])
|
|
120
268
|
child = report_ops._build_columns_payload(columns)[0]["children"][0]
|
|
121
269
|
self.assertNotIn("presentation", child)
|
|
122
270
|
self.assertNotIn("measureUnitField", child)
|
|
@@ -132,13 +280,13 @@ class CompileReportPluginContractTest(unittest.TestCase):
|
|
|
132
280
|
report handed Java delivery nothing at all, and a date column returned as text then
|
|
133
281
|
failed at render time with "setvalue fail, name: <column>, Unparseable date".
|
|
134
282
|
"""
|
|
135
|
-
contract =
|
|
283
|
+
contract = _compile_requirements(columns=[_column("amt")])
|
|
136
284
|
self.assertIsNotNone(contract)
|
|
137
285
|
self.assertEqual(contract["columnPresentation"], [])
|
|
138
286
|
self.assertEqual([row["dateIndex"] for row in contract["dataSetColumnTypes"]], ["amt"])
|
|
139
287
|
|
|
140
288
|
def test_nothing_to_say_still_means_no_contract(self):
|
|
141
|
-
self.assertIsNone(
|
|
289
|
+
self.assertIsNone(_compile_requirements(columns=[]))
|
|
142
290
|
|
|
143
291
|
def test_each_kind_maps_to_a_documented_factory_call(self):
|
|
144
292
|
cases = {
|
|
@@ -156,11 +304,11 @@ class CompileReportPluginContractTest(unittest.TestCase):
|
|
|
156
304
|
}
|
|
157
305
|
for kind, (spec, field_type, expected) in cases.items():
|
|
158
306
|
with self.subTest(kind=kind):
|
|
159
|
-
contract =
|
|
307
|
+
contract = _compile_requirements(columns=[_column("amt", field_type, presentation=spec)])
|
|
160
308
|
self.assertEqual(contract["columnPresentation"][0]["java"], expected)
|
|
161
309
|
|
|
162
310
|
def test_no_guessed_type_constant_appears_anywhere(self):
|
|
163
|
-
contract =
|
|
311
|
+
contract = _compile_requirements(columns=[
|
|
164
312
|
_column("amt", presentation={"kind": "amount", "currencyField": "cur"}),
|
|
165
313
|
_column("qty", presentation={"kind": "qty", "measureUnitField": "unit"}),
|
|
166
314
|
])
|
|
@@ -169,7 +317,7 @@ class CompileReportPluginContractTest(unittest.TestCase):
|
|
|
169
317
|
self.assertNotIn(guessed, rendered)
|
|
170
318
|
|
|
171
319
|
def test_scale_on_a_money_column_is_executable_code_not_a_comment(self):
|
|
172
|
-
entry =
|
|
320
|
+
entry = _compile_requirements(columns=[
|
|
173
321
|
_column("amt", presentation={"kind": "amount", "currencyField": "cur", "scale": 2}),
|
|
174
322
|
])["columnPresentation"][0]
|
|
175
323
|
self.assertEqual(entry["javaFollowUp"], ["setScale(2)"])
|
|
@@ -177,7 +325,7 @@ class CompileReportPluginContractTest(unittest.TestCase):
|
|
|
177
325
|
self.assertNotIn("/*", entry["java"])
|
|
178
326
|
|
|
179
327
|
def test_a_plain_decimal_column_takes_scale_from_the_factory(self):
|
|
180
|
-
entry =
|
|
328
|
+
entry = _compile_requirements(columns=[
|
|
181
329
|
_column("qty", presentation={"kind": "decimal", "scale": 4}),
|
|
182
330
|
])["columnPresentation"][0]
|
|
183
331
|
self.assertIn(", 4)", entry["java"])
|
|
@@ -191,20 +339,20 @@ class CompileReportPluginContractTest(unittest.TestCase):
|
|
|
191
339
|
R-presentation-binding now rejects an undeclared target — would only ever be reachable
|
|
192
340
|
from an artifact the validator refuses.
|
|
193
341
|
"""
|
|
194
|
-
contract =
|
|
342
|
+
contract = _compile_requirements(columns=[
|
|
195
343
|
_column("amt", presentation={"kind": "amount", "currencyField": "currency_id"}),
|
|
196
344
|
_column("qty", presentation={"kind": "qty", "measureUnitField": "unit_id"}),
|
|
197
345
|
])
|
|
198
346
|
self.assertEqual(contract["dataSetColumns"], ["amt", "qty"])
|
|
199
347
|
|
|
200
348
|
def test_group_children_are_compiled_too(self):
|
|
201
|
-
contract =
|
|
349
|
+
contract = _compile_requirements(columns=[{
|
|
202
350
|
"kind": "group", "fieldKey": "grp", "caption": "G", "children": [
|
|
203
351
|
_column("amt", presentation={"kind": "amount", "currencyField": "cur"})]}])
|
|
204
352
|
self.assertEqual(contract["columnPresentation"][0]["fieldKey"], "amt")
|
|
205
353
|
|
|
206
354
|
def test_unpersisted_filter_properties_become_requirements(self):
|
|
207
|
-
contract =
|
|
355
|
+
contract = _compile_requirements(
|
|
208
356
|
columns=[_column("amt")],
|
|
209
357
|
filters=[{"fieldPath": "flt_date", "displayName": "Date", "compare": "between",
|
|
210
358
|
"mustInput": True, "defaultValue": "thismonth"}],
|
|
@@ -220,7 +368,7 @@ class CompileReportPluginContractTest(unittest.TestCase):
|
|
|
220
368
|
between date filter does not become a date-range control. Before this entry existed the
|
|
221
369
|
comparison reached Java delivery as nothing.
|
|
222
370
|
"""
|
|
223
|
-
contract =
|
|
371
|
+
contract = _compile_requirements(
|
|
224
372
|
columns=[_column("amt")],
|
|
225
373
|
filters=[
|
|
226
374
|
{"fieldPath": "flt_date", "displayName": "Date", "compare": "between"},
|
|
@@ -260,7 +408,7 @@ class CompileReportPluginContractTest(unittest.TestCase):
|
|
|
260
408
|
self.assertEqual(contract["filterValueContracts"][0]["candidateValueKeys"], ["acme_region", "flt_area", "acme_area"])
|
|
261
409
|
|
|
262
410
|
def test_default_valued_filter_properties_are_not_requirements(self):
|
|
263
|
-
contract =
|
|
411
|
+
contract = _compile_requirements(
|
|
264
412
|
columns=[_column("amt")],
|
|
265
413
|
filters=[{"fieldPath": "flt_date", "displayName": "Date", "compare": "between",
|
|
266
414
|
"mustInput": False, "visible": True}],
|
|
@@ -285,17 +433,17 @@ class CompileReportPluginContractTest(unittest.TestCase):
|
|
|
285
433
|
}
|
|
286
434
|
for field_type, (accessor, java_type, algo_type) in expected.items():
|
|
287
435
|
with self.subTest(fieldType=field_type):
|
|
288
|
-
row =
|
|
436
|
+
row = _compile_requirements(columns=[_column("col", field_type=field_type)])[
|
|
289
437
|
"dataSetColumnTypes"][0]
|
|
290
438
|
self.assertEqual(row["rowAccessor"], accessor)
|
|
291
439
|
self.assertEqual(row["javaType"], java_type)
|
|
292
440
|
self.assertEqual(row["algoDataType"], algo_type)
|
|
293
|
-
decimal =
|
|
441
|
+
decimal = _compile_requirements(columns=[_column("amt")])["dataSetColumnTypes"][0]
|
|
294
442
|
self.assertEqual(decimal["rowAccessor"], "getBigDecimal")
|
|
295
443
|
self.assertTrue(decimal["algoDataType"].startswith("DataType.createBigDecimalType("))
|
|
296
444
|
|
|
297
445
|
def test_a_formula_column_is_not_given_a_guessed_type(self):
|
|
298
|
-
row =
|
|
446
|
+
row = _compile_requirements(columns=[_column("calc", field_type="formula")])["dataSetColumnTypes"][0]
|
|
299
447
|
self.assertEqual(row["rowAccessor"], "get")
|
|
300
448
|
self.assertEqual(row["javaType"], "Object")
|
|
301
449
|
self.assertNotIn("algoDataType", row)
|
|
@@ -317,7 +465,7 @@ class CompileReportPluginContractTest(unittest.TestCase):
|
|
|
317
465
|
platform cannot render: the runtime entity is built from the column list, so a value
|
|
318
466
|
arriving outside any column has no property to attach to.
|
|
319
467
|
"""
|
|
320
|
-
contract =
|
|
468
|
+
contract = _compile_requirements(columns=[
|
|
321
469
|
_column("cur", field_type="bigint"),
|
|
322
470
|
_column("amt", presentation={"kind": "amount", "currencyField": "cur"}),
|
|
323
471
|
])
|
|
@@ -348,13 +496,13 @@ class CompileReportPluginContractTest(unittest.TestCase):
|
|
|
348
496
|
self.assertNotIn("unit", contract["dataSetColumns"])
|
|
349
497
|
|
|
350
498
|
def test_a_kind_that_binds_nothing_reports_no_binding(self):
|
|
351
|
-
contract =
|
|
499
|
+
contract = _compile_requirements(columns=[
|
|
352
500
|
_column("sup", field_type="bigint", presentation={
|
|
353
501
|
"kind": "basedata", "entityId": "bd_supplier", "displayProp": "name"})])
|
|
354
502
|
self.assertNotIn("bindsTo", contract["columnPresentation"][0])
|
|
355
503
|
|
|
356
504
|
def test_one_binding_target_lists_every_column_that_needs_it(self):
|
|
357
|
-
contract =
|
|
505
|
+
contract = _compile_requirements(columns=[
|
|
358
506
|
_column("unit", field_type="bigint"),
|
|
359
507
|
_column("in_qty", presentation={"kind": "qty", "measureUnitField": "unit"}),
|
|
360
508
|
_column("out_qty", presentation={"kind": "qty", "measureUnitField": "unit"}),
|
|
@@ -364,11 +512,11 @@ class CompileReportPluginContractTest(unittest.TestCase):
|
|
|
364
512
|
self.assertEqual(contract["dataSetColumns"].count("unit"), 1)
|
|
365
513
|
|
|
366
514
|
def test_a_column_nothing_binds_to_carries_no_back_reference(self):
|
|
367
|
-
contract =
|
|
515
|
+
contract = _compile_requirements(columns=[_column("amt")])
|
|
368
516
|
self.assertNotIn("requiredBy", contract["dataSetColumnTypes"][0])
|
|
369
517
|
|
|
370
518
|
def test_a_grouped_column_is_typed_like_any_other(self):
|
|
371
|
-
contract =
|
|
519
|
+
contract = _compile_requirements(columns=[{
|
|
372
520
|
"kind": "group", "fieldKey": "grp", "caption": "G",
|
|
373
521
|
"children": [_column("biz_date", field_type="date")]}])
|
|
374
522
|
self.assertEqual(
|
|
@@ -377,7 +525,7 @@ class CompileReportPluginContractTest(unittest.TestCase):
|
|
|
377
525
|
|
|
378
526
|
def test_a_column_whose_key_differs_from_its_dataset_column_keeps_the_binding(self):
|
|
379
527
|
"""No factory signature takes dateIndex, so setDateIndex has to be emitted."""
|
|
380
|
-
contract =
|
|
528
|
+
contract = _compile_requirements(columns=[{
|
|
381
529
|
"kind": "column", "fieldKey": "material_ref", "dateIndex": "material_id",
|
|
382
530
|
"fieldType": "bigint", "caption": "Material",
|
|
383
531
|
"presentation": {"kind": "basedata", "entityId": "bd_material",
|
|
@@ -386,23 +534,60 @@ class CompileReportPluginContractTest(unittest.TestCase):
|
|
|
386
534
|
['setDateIndex("material_id")'])
|
|
387
535
|
|
|
388
536
|
def test_a_column_whose_key_equals_its_dataset_column_gets_no_redundant_setter(self):
|
|
389
|
-
contract =
|
|
537
|
+
contract = _compile_requirements(columns=[
|
|
390
538
|
_column("amt", presentation={"kind": "amount", "currencyField": "cur", "scale": 2})])
|
|
391
539
|
self.assertEqual(contract["columnPresentation"][0]["javaFollowUp"], ["setScale(2)"])
|
|
392
540
|
|
|
393
541
|
def test_contract_records_the_registered_plugin_classes(self):
|
|
394
|
-
contract =
|
|
542
|
+
contract = _compile_requirements(columns=[
|
|
395
543
|
_column("amt", presentation={"kind": "amount", "currencyField": "cur"})])
|
|
396
544
|
self.assertEqual(contract["pluginClasses"], ["kd.kdtest.report.P"])
|
|
545
|
+
self.assertEqual(contract["pluginTypes"], {"kd.kdtest.report.P": "reportlist"})
|
|
397
546
|
self.assertEqual(contract["formNumber"], "kdtest_rpt")
|
|
398
547
|
self.assertEqual(contract["schemaVersion"], report_ops.REPORT_PLUGIN_CONTRACT_VERSION)
|
|
399
548
|
|
|
549
|
+
def test_a_script_plugin_is_not_listed_for_java_delivery(self):
|
|
550
|
+
"""pluginClasses feeds the card gate, which looks for an archived .java file.
|
|
551
|
+
|
|
552
|
+
A KingScript or JS registration has no Java source, so listing it made the gate refuse
|
|
553
|
+
the cards for a source that can never exist.
|
|
554
|
+
"""
|
|
555
|
+
contract = _compile_requirements(columns=[_column("amt")], plugins=(
|
|
556
|
+
("kd.kdtest.report.P", "Java"), ("kd.kdtest.report.ScriptPlugin", "KingScript"),
|
|
557
|
+
("kd.kdtest.report.JsPlugin", "JS")))
|
|
558
|
+
self.assertEqual(contract["pluginClasses"], ["kd.kdtest.report.P"])
|
|
559
|
+
|
|
560
|
+
def test_the_left_tree_plugin_is_listed_with_its_own_type(self):
|
|
561
|
+
"""The left tree's class sits on ReportTreeAp.ReportPlugin, a different slot and base class.
|
|
562
|
+
|
|
563
|
+
Reading only reportPlugins left it outside the card gate, and a gate message saying
|
|
564
|
+
reportlist would have sent its delivery to the wrong base class.
|
|
565
|
+
"""
|
|
566
|
+
contract = report_ops.compile_report_plugin_contract({
|
|
567
|
+
"reportColumns": [_column("amt")],
|
|
568
|
+
"reportPlugins": [{"className": "kd.kdtest.report.CostReportPlugin", "type": "Java"}],
|
|
569
|
+
"reportProperties": {"tree": {"ReportPlugin": "kd.kdtest.report.CostTreePlugin"}},
|
|
570
|
+
}, "kdtest_rpt")
|
|
571
|
+
self.assertEqual(contract["pluginClasses"],
|
|
572
|
+
["kd.kdtest.report.CostReportPlugin", "kd.kdtest.report.CostTreePlugin"])
|
|
573
|
+
self.assertEqual(contract["pluginTypes"], {
|
|
574
|
+
"kd.kdtest.report.CostReportPlugin": "reportlist",
|
|
575
|
+
"kd.kdtest.report.CostTreePlugin": "reporttreelist",
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
def test_an_unparseable_properties_section_registers_no_tree_class(self):
|
|
579
|
+
types = report_ops.registered_report_plugin_types({
|
|
580
|
+
"reportPlugins": [{"className": "kd.kdtest.report.P"}],
|
|
581
|
+
"reportProperties": ["not", "an", "object"],
|
|
582
|
+
})
|
|
583
|
+
self.assertEqual(types, {"kd.kdtest.report.P": "reportlist"})
|
|
584
|
+
|
|
400
585
|
|
|
401
586
|
class WriteReportPluginContractTest(unittest.TestCase):
|
|
402
587
|
"""Persisting is how Java delivery picks the contract up."""
|
|
403
588
|
|
|
404
589
|
def test_contract_is_written_next_to_the_run_result(self):
|
|
405
|
-
contract =
|
|
590
|
+
contract = _compile_requirements(columns=[
|
|
406
591
|
_column("amt", presentation={"kind": "amount", "currencyField": "cur"})])
|
|
407
592
|
with tempfile.TemporaryDirectory() as tmp:
|
|
408
593
|
path = report_ops.write_report_plugin_contract(tmp, "kdtest_rpt", contract)
|
|
@@ -416,5 +601,297 @@ class WriteReportPluginContractTest(unittest.TestCase):
|
|
|
416
601
|
self.assertFalse(os.path.isdir(os.path.join(tmp, "_runtime")))
|
|
417
602
|
|
|
418
603
|
|
|
604
|
+
class IncrementalReportAuthoringTest(unittest.TestCase):
|
|
605
|
+
def baseline(self, root):
|
|
606
|
+
artifact = {'reportColumns': [_column('amt', presentation={'kind': 'decimal', 'scale': 3})],
|
|
607
|
+
'reportFilters': [{'fieldPath': 'flt_period', 'displayName': 'Period', 'compare': '=', 'mustInput': True},
|
|
608
|
+
{'fieldPath': 'flt_person', 'displayName': 'Person', 'compare': '='}],
|
|
609
|
+
'reportPlugins': [{'className': 'kd.sample.report.Query', 'type': 'Java'}]}
|
|
610
|
+
schema = {'readBackAvailable': True, 'readBackSource': 'canonical',
|
|
611
|
+
'reportColumns': artifact['reportColumns'], 'reportFilters': report_ops._build_filters_payload(artifact['reportFilters'])}
|
|
612
|
+
report_ops.write_report_authoring_state(root, 'sample_rpt', 'FORM', artifact, schema)
|
|
613
|
+
return artifact, schema
|
|
614
|
+
|
|
615
|
+
def test_append_keeps_old_presentation_and_adds_basedata_contract(self):
|
|
616
|
+
with tempfile.TemporaryDirectory() as root:
|
|
617
|
+
artifact, before = self.baseline(root)
|
|
618
|
+
person = _column('person_id', 'bigint', {'kind': 'basedata', 'entityId': 'bos_user', 'displayProp': 'name'})
|
|
619
|
+
after = {**before, 'reportColumns': [*artifact['reportColumns'], person]}
|
|
620
|
+
# 已有同键列被平台跳过,请求的 scale=8 不能覆盖原有 scale=3。
|
|
621
|
+
requested = [_column('amt', presentation={'kind': 'decimal', 'scale': 8}), person]
|
|
622
|
+
result = report_ops.sync_report_authoring_state(root, 'sample_rpt', 'FORM', 'columnsAppend',
|
|
623
|
+
{'before': before['reportColumns'], 'requested': requested}, before, after, {'verified': True})
|
|
624
|
+
with open(result['contractPath'], encoding='utf-8') as handle:
|
|
625
|
+
contract = json.load(handle)
|
|
626
|
+
self.assertEqual(contract['dataSetColumns'], ['amt', 'person_id'])
|
|
627
|
+
self.assertEqual(contract['columnPresentation'][0]['scale'], 3)
|
|
628
|
+
self.assertEqual(contract['columnPresentation'][1]['entityId'], 'bos_user')
|
|
629
|
+
again = report_ops.sync_report_authoring_state(root, 'sample_rpt', 'FORM', 'columnsAppend',
|
|
630
|
+
{'before': after['reportColumns'], 'requested': requested}, after, after, {'verified': True})
|
|
631
|
+
with open(again['artifactPath'], encoding='utf-8') as handle:
|
|
632
|
+
self.assertEqual(len(json.load(handle)['reportColumns']), 2)
|
|
633
|
+
|
|
634
|
+
def test_filter_replacement_removes_withdrawn_requirement_and_preserves_columns(self):
|
|
635
|
+
with tempfile.TemporaryDirectory() as root:
|
|
636
|
+
artifact, before = self.baseline(root)
|
|
637
|
+
filters = artifact['reportFilters'][:1]
|
|
638
|
+
after = {**before, 'reportFilters': filters}
|
|
639
|
+
result = report_ops.sync_report_authoring_state(root, 'sample_rpt', 'FORM', 'filters',
|
|
640
|
+
filters, before, after, {'verified': True})
|
|
641
|
+
with open(result['contractPath'], encoding='utf-8') as handle:
|
|
642
|
+
contract = json.load(handle)
|
|
643
|
+
self.assertEqual([row['filterKey'] for row in contract['filterValueContracts']], ['flt_period'])
|
|
644
|
+
self.assertEqual(contract['dataSetColumns'], ['amt'])
|
|
645
|
+
self.assertEqual(contract['columnPresentation'][0]['scale'], 3)
|
|
646
|
+
|
|
647
|
+
def test_missing_baseline_readback_failure_and_designer_drift_fail_closed(self):
|
|
648
|
+
for mode in ('missing', 'unverified', 'drift', 'identity'):
|
|
649
|
+
with self.subTest(mode=mode), tempfile.TemporaryDirectory() as root:
|
|
650
|
+
artifact, before = self.baseline(root) if mode != 'missing' else ({}, {})
|
|
651
|
+
if mode == 'drift':
|
|
652
|
+
before = {**before, 'reportColumns': [_column('unknown')]}
|
|
653
|
+
result = report_ops.sync_report_authoring_state(root, 'sample_rpt', 'OTHER' if mode == 'identity' else 'FORM',
|
|
654
|
+
'filters', [], before, {**before, 'reportFilters': []}, {'verified': mode != 'unverified'})
|
|
655
|
+
self.assertEqual(result['status'], 'incomplete')
|
|
656
|
+
self.assertTrue(result['platformMutationApplied'])
|
|
657
|
+
with open(result['path'], encoding='utf-8') as handle:
|
|
658
|
+
self.assertEqual(json.load(handle)['status'], 'incomplete')
|
|
659
|
+
|
|
660
|
+
def test_write_receipt_survives_failed_readback_without_replaying_mutation(self):
|
|
661
|
+
import cosmic_api
|
|
662
|
+
from unittest import mock
|
|
663
|
+
with tempfile.TemporaryDirectory() as root:
|
|
664
|
+
artifact, before = self.baseline(root)
|
|
665
|
+
with mock.patch.object(cosmic_api, 'do_get_report_schema', side_effect=OSError('gateway unavailable')) as read:
|
|
666
|
+
result = cosmic_api._attach_report_readback({}, 'fixture', 'FORM', 'filters',
|
|
667
|
+
artifact['reportFilters'][:1], {'formNumber': 'sample_rpt', 'formId': 'FORM'},
|
|
668
|
+
app_root=root, schema_before=before)
|
|
669
|
+
read.assert_called_once()
|
|
670
|
+
self.assertTrue(result['status'])
|
|
671
|
+
self.assertTrue(result['data']['platformMutationApplied'])
|
|
672
|
+
self.assertFalse(result['verification']['verified'])
|
|
673
|
+
self.assertEqual(result['data']['reportAuthoringState']['status'], 'incomplete')
|
|
674
|
+
|
|
675
|
+
def test_cli_readback_refreshes_contract_using_real_verifier(self):
|
|
676
|
+
import cosmic_api
|
|
677
|
+
from unittest import mock
|
|
678
|
+
with tempfile.TemporaryDirectory() as root:
|
|
679
|
+
artifact, before = self.baseline(root)
|
|
680
|
+
filters = artifact['reportFilters'][:1]
|
|
681
|
+
after = {**before, 'reportFilters': report_ops._build_filters_payload(filters)}
|
|
682
|
+
with mock.patch.object(cosmic_api, 'do_get_report_schema', return_value=after):
|
|
683
|
+
result = cosmic_api._attach_report_readback({}, 'fixture', 'FORM', 'filters', filters,
|
|
684
|
+
{'formNumber': 'sample_rpt', 'formId': 'FORM'}, app_root=root, schema_before=before)
|
|
685
|
+
self.assertTrue(result['verification']['verified'])
|
|
686
|
+
self.assertEqual(result['data']['reportAuthoringState']['status'], 'current')
|
|
687
|
+
|
|
688
|
+
def test_scoped_serve_mutation_invalidates_old_contract_but_dry_run_does_not(self):
|
|
689
|
+
import cosmic_api
|
|
690
|
+
from unittest import mock
|
|
691
|
+
with tempfile.TemporaryDirectory() as root:
|
|
692
|
+
artifact, before = self.baseline(root)
|
|
693
|
+
path = report_ops.report_authoring_path(root, 'sample_rpt')
|
|
694
|
+
args = {'appRoot': root, 'formNumber': 'sample_rpt', 'plugin': {'className': 'kd.sample.report.New'}}
|
|
695
|
+
with mock.patch.object(cosmic_api, 'do_register_report_plugin', return_value='kd.sample.report.New'):
|
|
696
|
+
cosmic_api._dispatch_serve({}, 'fixture', 'registerReportPlugin', {**args, 'dryRun': True})
|
|
697
|
+
self.assertEqual(json.loads(path.read_text())['status'], 'current')
|
|
698
|
+
cosmic_api._dispatch_serve({}, 'fixture', 'registerReportPlugin', args)
|
|
699
|
+
self.assertEqual(json.loads(path.read_text())['status'], 'incomplete')
|
|
700
|
+
|
|
701
|
+
def test_property_writes_invalidate_only_successful_unmerged_baseline(self):
|
|
702
|
+
import cosmic_api
|
|
703
|
+
with tempfile.TemporaryDirectory() as root:
|
|
704
|
+
artifact, before = self.baseline(root)
|
|
705
|
+
path = report_ops.report_authoring_path(root, 'sample_rpt')
|
|
706
|
+
cosmic_api._invalidate_unmerged_report_state({'status': False}, 'setReportFilterFieldConfig', root, 'sample_rpt')
|
|
707
|
+
self.assertEqual(json.loads(path.read_text())['status'], 'current')
|
|
708
|
+
result = cosmic_api._invalidate_unmerged_report_state({'status': True, 'data': {}},
|
|
709
|
+
'setReportFilterFieldConfig', root, 'sample_rpt')
|
|
710
|
+
self.assertEqual(result['data']['reportAuthoringState']['status'], 'incomplete')
|
|
711
|
+
self.assertEqual(json.loads(path.read_text())['status'], 'incomplete')
|
|
712
|
+
|
|
713
|
+
def test_scoped_failed_writes_distinguish_uncertain_from_applied(self):
|
|
714
|
+
import cosmic_api
|
|
715
|
+
from unittest import mock
|
|
716
|
+
for mode in ('rejected', 'transport', 'readback', 'mismatch'):
|
|
717
|
+
with self.subTest(mode=mode), tempfile.TemporaryDirectory() as root:
|
|
718
|
+
self.baseline(root)
|
|
719
|
+
response = {'status': False, 'message': 'save rejected'} if mode == 'rejected' else {'status': True}
|
|
720
|
+
observed = {'ReportFilterFieldConfig': {'reportFilterFields': [{'key': 'other'}]}}
|
|
721
|
+
with mock.patch.object(report_ops, '_api_call', return_value=response,
|
|
722
|
+
side_effect=TimeoutError('gateway') if mode == 'transport' else None) as write, \
|
|
723
|
+
mock.patch.object(report_ops, 'read_report_element_properties', return_value=observed,
|
|
724
|
+
side_effect=OSError('gateway') if mode == 'readback' else None) as read:
|
|
725
|
+
result = cosmic_api._dispatch_serve({}, 'fixture', 'setReportFilterFieldConfig',
|
|
726
|
+
{'appRoot': root, 'formNumber': 'sample_rpt', 'fields': [{'key': 'flt_period'}]})
|
|
727
|
+
write.assert_called_once()
|
|
728
|
+
self.assertFalse(result['status'])
|
|
729
|
+
self.assertFalse(cosmic_api._serve_result_success(result))
|
|
730
|
+
state = json.loads(report_ops.report_authoring_path(root, 'sample_rpt').read_text())
|
|
731
|
+
self.assertEqual(state['status'], 'incomplete')
|
|
732
|
+
evidence = 'platformMutationApplied' if mode in ('readback', 'mismatch') else 'platformMutationPossible'
|
|
733
|
+
absent = 'platformMutationPossible' if evidence == 'platformMutationApplied' else 'platformMutationApplied'
|
|
734
|
+
self.assertTrue(state[evidence])
|
|
735
|
+
self.assertNotIn(absent, state)
|
|
736
|
+
self.assertTrue(result['data'][evidence])
|
|
737
|
+
self.assertFalse(result['data']['runtimeVerified'])
|
|
738
|
+
self.assertEqual(read.call_count, int(mode in ('readback', 'mismatch')))
|
|
739
|
+
|
|
740
|
+
def test_preflight_dry_run_and_unscoped_failure_do_not_invalidate_other_baselines(self):
|
|
741
|
+
import cosmic_api
|
|
742
|
+
from unittest import mock
|
|
743
|
+
with tempfile.TemporaryDirectory() as root:
|
|
744
|
+
self.baseline(root)
|
|
745
|
+
args = {'appRoot': root, 'formNumber': 'sample_rpt', 'fields': [{'key': 'flt_period'}]}
|
|
746
|
+
path = report_ops.report_authoring_path(root, 'sample_rpt')
|
|
747
|
+
with mock.patch.object(report_ops, '_api_call', side_effect=TimeoutError('gateway')) as write:
|
|
748
|
+
with self.assertRaises(ValueError):
|
|
749
|
+
cosmic_api._dispatch_serve({}, 'fixture', 'setReportFilterFieldConfig', {**args, 'fields': []})
|
|
750
|
+
cosmic_api._dispatch_serve({}, 'fixture', 'setReportFilterFieldConfig', {**args, 'dryRun': True})
|
|
751
|
+
write.assert_not_called()
|
|
752
|
+
self.assertEqual(json.loads(path.read_text())['status'], 'current')
|
|
753
|
+
unscoped = {key: value for key, value in args.items() if key != 'appRoot'}
|
|
754
|
+
result = cosmic_api._dispatch_serve({}, 'fixture', 'setReportFilterFieldConfig', unscoped)
|
|
755
|
+
write.assert_called_once()
|
|
756
|
+
self.assertFalse(result['status'])
|
|
757
|
+
self.assertNotIn('reportAuthoringState', result['data'])
|
|
758
|
+
self.assertEqual(json.loads(path.read_text())['status'], 'current')
|
|
759
|
+
|
|
760
|
+
def test_cli_failure_keeps_report_state_evidence_and_restores_stdout(self):
|
|
761
|
+
import builtins
|
|
762
|
+
import cosmic_api
|
|
763
|
+
import io
|
|
764
|
+
from contextlib import redirect_stdout, redirect_stderr
|
|
765
|
+
from types import SimpleNamespace
|
|
766
|
+
from unittest import mock
|
|
767
|
+
for applied in (None, True):
|
|
768
|
+
with self.subTest(applied=applied), tempfile.TemporaryDirectory() as root:
|
|
769
|
+
self.baseline(root)
|
|
770
|
+
original_print = builtins.print
|
|
771
|
+
output, errors = io.StringIO(), io.StringIO()
|
|
772
|
+
def execute():
|
|
773
|
+
cosmic_api._CLI_DELIVERY_ARGS = SimpleNamespace(
|
|
774
|
+
cmd='setReportFilterFieldConfig', app_root=root, formNumber='sample_rpt', dry_run=False)
|
|
775
|
+
raise report_ops.ReportMutationError('REPORT_WRITE_OUTCOME_UNKNOWN: fixture', applied)
|
|
776
|
+
with mock.patch.object(cosmic_api, '_main_impl', side_effect=execute), \
|
|
777
|
+
mock.patch.object(sys, 'argv', ['cosmic_api.py', '--stdout']), \
|
|
778
|
+
redirect_stdout(output), redirect_stderr(errors):
|
|
779
|
+
self.assertEqual(cosmic_api.main(), 1)
|
|
780
|
+
self.assertIs(builtins.print, original_print)
|
|
781
|
+
print('CARD_STDOUT_RESTORED')
|
|
782
|
+
lines = output.getvalue().splitlines()
|
|
783
|
+
result = json.loads('\n'.join(lines[:-1]))
|
|
784
|
+
self.assertEqual(lines[-1], 'CARD_STDOUT_RESTORED')
|
|
785
|
+
self.assertFalse(result['status'])
|
|
786
|
+
self.assertEqual(result['data']['reportAuthoringState']['status'], 'incomplete')
|
|
787
|
+
self.assertTrue(result['data']['platformMutationApplied' if applied else 'platformMutationPossible'])
|
|
788
|
+
|
|
789
|
+
def test_cli_success_restores_print_and_output_context_for_following_cards(self):
|
|
790
|
+
import builtins
|
|
791
|
+
import cosmic_api
|
|
792
|
+
import io
|
|
793
|
+
from _core import formatters
|
|
794
|
+
from contextlib import redirect_stdout, redirect_stderr
|
|
795
|
+
from unittest import mock
|
|
796
|
+
original_print = builtins.print
|
|
797
|
+
previous = (formatters._stdout_mode, formatters._output_file,
|
|
798
|
+
formatters._current_cmd, formatters._current_arg_hash)
|
|
799
|
+
output, errors = io.StringIO(), io.StringIO()
|
|
800
|
+
with mock.patch.object(sys, 'argv', ['cosmic_api.py', '--stdout', 'describeCommand', 'setReportFilters']), \
|
|
801
|
+
redirect_stdout(output), redirect_stderr(errors):
|
|
802
|
+
self.assertEqual(cosmic_api.main(), 0)
|
|
803
|
+
self.assertIs(builtins.print, original_print)
|
|
804
|
+
self.assertEqual((formatters._stdout_mode, formatters._output_file,
|
|
805
|
+
formatters._current_cmd, formatters._current_arg_hash), previous)
|
|
806
|
+
print('CARD_STDOUT_RESTORED')
|
|
807
|
+
lines = output.getvalue().splitlines()
|
|
808
|
+
self.assertEqual(lines[-1], 'CARD_STDOUT_RESTORED')
|
|
809
|
+
self.assertEqual(json.loads('\n'.join(lines[:-1]))['action']['command'], 'setReportFilters')
|
|
810
|
+
|
|
811
|
+
def test_internal_executor_does_not_install_a_process_wide_print_router(self):
|
|
812
|
+
import builtins
|
|
813
|
+
import cosmic_api
|
|
814
|
+
from _core import formatters
|
|
815
|
+
from unittest import mock
|
|
816
|
+
original_print = builtins.print
|
|
817
|
+
with mock.patch.object(sys, 'argv', ['cosmic_api.py', '--stdout', 'auth']), \
|
|
818
|
+
mock.patch.object(builtins, 'print', original_print), \
|
|
819
|
+
mock.patch.object(formatters, '_stdout_mode', False), \
|
|
820
|
+
mock.patch.object(cosmic_api, '_CLI_MACHINE_MODE', False), \
|
|
821
|
+
mock.patch.object(cosmic_api, '_CLI_DELIVERY_ARGS', None), \
|
|
822
|
+
mock.patch.object(cosmic_api, '_resolve_app_root_arg', side_effect=RuntimeError('fixture-stop')):
|
|
823
|
+
with self.assertRaisesRegex(RuntimeError, 'fixture-stop'):
|
|
824
|
+
cosmic_api._main_impl()
|
|
825
|
+
self.assertIs(builtins.print, original_print)
|
|
826
|
+
|
|
827
|
+
def test_unexpected_write_response_and_rejected_save_are_not_preflight_failures(self):
|
|
828
|
+
for response in (None, [], {'status': False}, {'errorCode': 'SAVE_REJECTED'}, {'data': {'success': False}}):
|
|
829
|
+
with self.subTest(response=response):
|
|
830
|
+
with self.assertRaises(report_ops.ReportMutationError) as failure:
|
|
831
|
+
report_ops._check_result(response, 'setReportFilters', form_mutation_possible=True)
|
|
832
|
+
self.assertIsNone(failure.exception.mutation_applied)
|
|
833
|
+
self.assertIn('do not repeat the same write blindly', str(failure.exception))
|
|
834
|
+
with self.assertRaises(RuntimeError) as failure:
|
|
835
|
+
report_ops._check_result({'status': False}, 'getFormSchema')
|
|
836
|
+
self.assertNotIsInstance(failure.exception, report_ops.ReportMutationError)
|
|
837
|
+
|
|
838
|
+
def test_malformed_readback_preserves_successful_write_and_invalidates_state(self):
|
|
839
|
+
import cosmic_api
|
|
840
|
+
from unittest import mock
|
|
841
|
+
for schema in (None, [], {'readBackAvailable': True, 'reportFilters': [None]}):
|
|
842
|
+
with self.subTest(schema=schema), tempfile.TemporaryDirectory() as root:
|
|
843
|
+
artifact, before = self.baseline(root)
|
|
844
|
+
with mock.patch.object(cosmic_api, 'do_get_report_schema', return_value=schema):
|
|
845
|
+
result = cosmic_api._attach_report_readback({}, 'fixture', 'FORM', 'filters',
|
|
846
|
+
artifact['reportFilters'][:1], {'formNumber': 'sample_rpt', 'formId': 'FORM'},
|
|
847
|
+
app_root=root, schema_before=before)
|
|
848
|
+
self.assertTrue(result['data']['platformMutationApplied'])
|
|
849
|
+
self.assertFalse(result['verification']['verified'])
|
|
850
|
+
self.assertEqual(json.loads(report_ops.report_authoring_path(root, 'sample_rpt').read_text())['status'], 'incomplete')
|
|
851
|
+
|
|
852
|
+
def test_design_state_mismatch_preserves_successful_property_write(self):
|
|
853
|
+
import cosmic_api
|
|
854
|
+
from unittest import mock
|
|
855
|
+
result = {'designStateExpectation': {'hostType': 'ReportListAp', 'expected': {'MaxRowHeight': 32}}}
|
|
856
|
+
with mock.patch.object(cosmic_api, '_export_report_dym_evidence', return_value='fixture.dym'), \
|
|
857
|
+
mock.patch.object(cosmic_api, 'attach_report_property_design_state',
|
|
858
|
+
side_effect=RuntimeError('REPORT_PROPERTY_MISMATCH: fixture')):
|
|
859
|
+
with self.assertRaises(report_ops.ReportMutationError) as failure:
|
|
860
|
+
cosmic_api._settle_report_property_design_state({}, 'fixture', result, 'sample_rpt', 'app', 'setReportProperties')
|
|
861
|
+
self.assertTrue(failure.exception.mutation_applied)
|
|
862
|
+
self.assertTrue(cosmic_api._report_mutation_failure(failure.exception)['data']['platformMutationApplied'])
|
|
863
|
+
|
|
864
|
+
def test_atomic_local_save_failure_cleans_only_its_temporary_files(self):
|
|
865
|
+
from pathlib import Path
|
|
866
|
+
from unittest import mock
|
|
867
|
+
with tempfile.TemporaryDirectory() as root:
|
|
868
|
+
artifact, schema = self.baseline(root)
|
|
869
|
+
runtime = Path(root) / '_runtime'
|
|
870
|
+
unrelated = runtime / 'unrelated.tmp'
|
|
871
|
+
unrelated.write_text('keep')
|
|
872
|
+
with mock.patch.object(report_ops.os, 'replace', side_effect=OSError('save failed')):
|
|
873
|
+
with self.assertRaises(OSError):
|
|
874
|
+
report_ops.write_report_authoring_state(root, 'sample_rpt', 'FORM', artifact, schema)
|
|
875
|
+
self.assertEqual(list(runtime.glob('*.tmp')), [unrelated])
|
|
876
|
+
self.assertEqual(json.loads(report_ops.report_authoring_path(root, 'sample_rpt').read_text())['status'], 'incomplete')
|
|
877
|
+
|
|
878
|
+
def test_contract_compile_failure_leaves_state_incomplete(self):
|
|
879
|
+
from unittest import mock
|
|
880
|
+
with tempfile.TemporaryDirectory() as root:
|
|
881
|
+
artifact, before = self.baseline(root)
|
|
882
|
+
with mock.patch.object(report_ops, 'compile_report_plugin_contract', side_effect=ValueError('invalid contract')):
|
|
883
|
+
with self.assertRaises(ValueError):
|
|
884
|
+
report_ops.write_report_authoring_state(root, 'sample_rpt', 'FORM', artifact, before)
|
|
885
|
+
self.assertEqual(json.loads(report_ops.report_authoring_path(root, 'sample_rpt').read_text())['status'], 'incomplete')
|
|
886
|
+
|
|
887
|
+
def test_query_data_source_only_does_not_create_a_java_contract(self):
|
|
888
|
+
with tempfile.TemporaryDirectory() as root:
|
|
889
|
+
artifact = {'queryDataSource': {'number': 'sample_data'}, 'reportColumns': [_column('amt')]}
|
|
890
|
+
result = report_ops.write_report_authoring_state(root, 'sample_rpt', 'FORM', artifact,
|
|
891
|
+
{'reportColumns': artifact['reportColumns']})
|
|
892
|
+
self.assertEqual(result['contractPath'], '')
|
|
893
|
+
self.assertFalse(os.path.isfile(os.path.join(root, '_runtime', 'sample_rpt.report-plugin-contract.json')))
|
|
894
|
+
|
|
895
|
+
|
|
419
896
|
if __name__ == "__main__":
|
|
420
897
|
unittest.main()
|