@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_readback_derivation.py
CHANGED
|
@@ -8,12 +8,16 @@ not verified" even though ``data.formLayout`` carried the exact control tree tha
|
|
|
8
8
|
These tests pin the derived projection and the honest accounting of what it cannot prove.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
import copy
|
|
12
|
+
import json
|
|
11
13
|
import os
|
|
12
14
|
import sys
|
|
13
15
|
import unittest
|
|
16
|
+
from unittest import mock
|
|
14
17
|
|
|
15
18
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
16
19
|
|
|
20
|
+
from _core import report_ops # noqa: E402
|
|
17
21
|
from _core.report_ops import ( # noqa: E402
|
|
18
22
|
_columns_from_layout,
|
|
19
23
|
_filters_from_layout,
|
|
@@ -313,7 +317,10 @@ class TestDerivedVerification(unittest.TestCase):
|
|
|
313
317
|
):
|
|
314
318
|
result = report_readback_verification(section, expected, _derived_schema())
|
|
315
319
|
self.assertFalse(result["verified"], section)
|
|
316
|
-
|
|
320
|
+
if section == "sorts":
|
|
321
|
+
self.assertEqual(result["errorCode"], "REPORT_SORTS_API_UNSAFE")
|
|
322
|
+
else:
|
|
323
|
+
self.assertIn("not carried by readBackSource", result["mismatches"][0]["actual"])
|
|
317
324
|
|
|
318
325
|
def test_no_read_back_source_reports_unavailable(self):
|
|
319
326
|
result = report_readback_verification(
|
|
@@ -322,5 +329,175 @@ class TestDerivedVerification(unittest.TestCase):
|
|
|
322
329
|
self.assertEqual("unavailable", result["source"])
|
|
323
330
|
|
|
324
331
|
|
|
332
|
+
def _projection_response():
|
|
333
|
+
"""bos1030 字节码确认的投影形状;fixture 不模拟查询生效。"""
|
|
334
|
+
layout = _layout()
|
|
335
|
+
controls = layout["children"][1]["children"][0]["children"]
|
|
336
|
+
controls[0].update(id="date-control", fieldId="date-field", type="FieldAp")
|
|
337
|
+
controls[1].update(id="supplier-control", fieldId="supplier-field", type="FieldAp")
|
|
338
|
+
return {"data": {"formLayout": layout, "reportFilters": {
|
|
339
|
+
"available": True,
|
|
340
|
+
"filterPanel": {"commFilterPanelFound": True, "filterMustInput": True,
|
|
341
|
+
"showFilterFields": {"format": "json-array",
|
|
342
|
+
"raw": '["sup_number","biz_date"]', "parsed": ["sup_number", "biz_date"]}},
|
|
343
|
+
"filters": [
|
|
344
|
+
{"fieldPath": "sup_number", "registered": True, "controlFound": True,
|
|
345
|
+
"duplicateControlCount": 1, "controlId": "supplier-control", "fieldId": "supplier-field",
|
|
346
|
+
"fieldType": "MulBasedataField", "mustInput": True, "visibleEffective": False,
|
|
347
|
+
"reference": {"kind": "mulbasedata", "baseEntityId": "supplier-uuid",
|
|
348
|
+
"baseEntityNumber": "bd_supplier"}, "rangeKeys": None,
|
|
349
|
+
"compare": {"source": "ReportFilterFieldConfig", "compareTypeId": "1",
|
|
350
|
+
"dataSourceId": "query-source", "fieldName": "supplier"}, "unknown": []},
|
|
351
|
+
{"fieldPath": "biz_date", "registered": True, "controlFound": True,
|
|
352
|
+
"duplicateControlCount": 1, "controlId": "date-control", "fieldId": "date-field",
|
|
353
|
+
"fieldType": "DateRangeField", "mustInput": False, "visibleEffective": True,
|
|
354
|
+
"reference": {"kind": "none"}, "rangeKeys": {"start": "date_from", "end": "date_to"},
|
|
355
|
+
"compare": {"source": "unconfigured", "compareTypeId": None,
|
|
356
|
+
"unknown": ["compareTypeId", "fieldName"]}, "unknown": []}],
|
|
357
|
+
"orphanRegistrations": [], "unmanagedControls": [], "nonFilterChildren": []}}}
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _projection_schema(response):
|
|
361
|
+
with mock.patch.object(report_ops, "_get_schema", return_value=response):
|
|
362
|
+
return report_ops.get_report_schema({}, "token", "report-id")
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _projection_request():
|
|
366
|
+
return [
|
|
367
|
+
{"fieldPath": "sup_number", "displayName": "Supplier Number", "compare": "in",
|
|
368
|
+
"fieldType": "MulBasedataField", "basedataNumber": "bd_supplier", "mustInput": True},
|
|
369
|
+
{"fieldPath": "biz_date", "displayName": "Order Date", "compare": "between",
|
|
370
|
+
"fieldType": "DateRangeField", "startDateFieldKey": "date_from", "endDateFieldKey": "date_to"}]
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class TestStructuredFilterProjection(unittest.TestCase):
|
|
374
|
+
def test_new_projection_verifies_identity_required_and_custom_range_not_query(self):
|
|
375
|
+
response = _projection_response()
|
|
376
|
+
before = copy.deepcopy(response)
|
|
377
|
+
schema = _projection_schema(response)
|
|
378
|
+
result = report_readback_verification("filters", _projection_request(), schema)
|
|
379
|
+
self.assertTrue(result["verified"], result)
|
|
380
|
+
self.assertEqual(result["source"], "reportFilterProjection")
|
|
381
|
+
self.assertNotIn("mustInput", result["unverifiedProperties"])
|
|
382
|
+
self.assertNotIn("basedataNumber", result.get("unverifiableRequestedProperties", []))
|
|
383
|
+
self.assertIn("compare", result["unverifiedProperties"])
|
|
384
|
+
self.assertEqual(schema["reportFilters"][0]["compareConfig"]["fieldName"], "supplier")
|
|
385
|
+
self.assertNotIn("compare", schema["reportFilters"][0])
|
|
386
|
+
self.assertEqual(response, before)
|
|
387
|
+
self.assertEqual(schema["reportColumns"], _columns_from_layout(response["data"]["formLayout"]))
|
|
388
|
+
self.assertIn("reportPlugins", schema["unverifiableSections"])
|
|
389
|
+
|
|
390
|
+
def test_wrong_multiselect_reference_is_a_mismatch(self):
|
|
391
|
+
response = _projection_response()
|
|
392
|
+
response["data"]["reportFilters"]["filters"][0]["reference"]["baseEntityNumber"] = "bd_customer"
|
|
393
|
+
result = report_readback_verification("filters", _projection_request(), _projection_schema(response))
|
|
394
|
+
self.assertFalse(result["verified"])
|
|
395
|
+
self.assertTrue(any(row["path"].endswith("/basedataNumber") for row in result["mismatches"]))
|
|
396
|
+
|
|
397
|
+
def test_custom_range_and_combo_values_are_compared(self):
|
|
398
|
+
response = _projection_response()
|
|
399
|
+
response["data"]["reportFilters"]["filters"][1]["rangeKeys"]["start"] = "wrong"
|
|
400
|
+
result = report_readback_verification("filters", _projection_request(), _projection_schema(response))
|
|
401
|
+
self.assertFalse(result["verified"])
|
|
402
|
+
self.assertTrue(any("/rangeKeys/start" in row["path"] for row in result["mismatches"]))
|
|
403
|
+
response = _projection_response()
|
|
404
|
+
row = response["data"]["reportFilters"]["filters"][0]
|
|
405
|
+
row.update(fieldType="ComboField", comboItems=[{"value": "0", "caption": "Zero", "seq": 0},
|
|
406
|
+
{"value": "001", "caption": "Code", "seq": 1}])
|
|
407
|
+
requested = _projection_request()
|
|
408
|
+
requested[0] = {"fieldPath": "sup_number", "displayName": "Supplier Number", "compare": "in",
|
|
409
|
+
"fieldType": "ComboField", "comboItems": [{"value": "0", "caption": "Zero"},
|
|
410
|
+
{"value": "001", "caption": "Code"}]}
|
|
411
|
+
self.assertTrue(report_readback_verification("filters", requested, _projection_schema(response))["verified"])
|
|
412
|
+
row["comboItems"][1]["value"] = "1"
|
|
413
|
+
self.assertFalse(report_readback_verification("filters", requested, _projection_schema(response))["verified"])
|
|
414
|
+
|
|
415
|
+
def test_unknown_is_not_false_and_known_false_is_a_mismatch(self):
|
|
416
|
+
response = _projection_response()
|
|
417
|
+
row = response["data"]["reportFilters"]["filters"][0]
|
|
418
|
+
row.update(mustInput=False, unknown=["mustInput"])
|
|
419
|
+
result = report_readback_verification("filters", _projection_request(), _projection_schema(response))
|
|
420
|
+
self.assertTrue(result["verified"], result)
|
|
421
|
+
self.assertIn("mustInput", result["unverifiableRequestedProperties"])
|
|
422
|
+
row["unknown"] = []
|
|
423
|
+
result = report_readback_verification("filters", _projection_request(), _projection_schema(response))
|
|
424
|
+
self.assertFalse(result["verified"])
|
|
425
|
+
|
|
426
|
+
def test_visible_and_explicit_default_flags_use_observed_booleans(self):
|
|
427
|
+
response = _projection_response()
|
|
428
|
+
requested = _projection_request()
|
|
429
|
+
requested[0]["visible"] = True
|
|
430
|
+
result = report_readback_verification("filters", requested, _projection_schema(response))
|
|
431
|
+
self.assertFalse(result["verified"])
|
|
432
|
+
response["data"]["reportFilters"]["filters"][0]["visibleEffective"] = None
|
|
433
|
+
result = report_readback_verification("filters", requested, _projection_schema(response))
|
|
434
|
+
self.assertTrue(result["verified"])
|
|
435
|
+
self.assertIn("visible", result["unverifiableRequestedProperties"])
|
|
436
|
+
|
|
437
|
+
def test_orphan_and_duplicate_controls_do_not_pass_structure(self):
|
|
438
|
+
for value in (0, 2):
|
|
439
|
+
response = _projection_response()
|
|
440
|
+
row = response["data"]["reportFilters"]["filters"][0]
|
|
441
|
+
row.update(controlFound=value != 0, duplicateControlCount=value)
|
|
442
|
+
response["data"]["reportFilters"]["orphanRegistrations"] = ["sup_number"] if value == 0 else []
|
|
443
|
+
result = report_readback_verification("filters", _projection_request(), _projection_schema(response))
|
|
444
|
+
self.assertFalse(result["verified"])
|
|
445
|
+
self.assertIn("orphanRegistrations", result["projectionDiagnostics"])
|
|
446
|
+
|
|
447
|
+
def test_no_name_evidence_does_not_reuse_request_or_unrelated_control(self):
|
|
448
|
+
response = _projection_response()
|
|
449
|
+
response["data"]["reportFilters"]["filters"][0]["controlId"] = "unrelated-control"
|
|
450
|
+
schema = _projection_schema(response)
|
|
451
|
+
self.assertNotIn("displayName", schema["reportFilters"][0])
|
|
452
|
+
result = report_readback_verification("filters", _projection_request(), schema)
|
|
453
|
+
self.assertIn("displayName", result["unverifiableRequestedProperties"])
|
|
454
|
+
|
|
455
|
+
def test_unavailable_missing_panel_and_bad_json_cannot_use_legacy_fallback(self):
|
|
456
|
+
for condition in ("unavailable", "panel", "bad-json", "malformed", "legacy-comma"):
|
|
457
|
+
response = _projection_response()
|
|
458
|
+
projection = response["data"]["reportFilters"]
|
|
459
|
+
if condition == "unavailable":
|
|
460
|
+
projection.update(available=False, reason="missing filter")
|
|
461
|
+
elif condition == "panel":
|
|
462
|
+
projection["filterPanel"]["commFilterPanelFound"] = False
|
|
463
|
+
elif condition == "bad-json":
|
|
464
|
+
projection["filterPanel"]["showFilterFields"]["raw"] = "[bad"
|
|
465
|
+
elif condition == "legacy-comma":
|
|
466
|
+
projection["filterPanel"]["showFilterFields"].update(format="legacy-comma", raw="sup_number,biz_date")
|
|
467
|
+
else:
|
|
468
|
+
response["data"]["reportFilters"] = []
|
|
469
|
+
schema = _projection_schema(response)
|
|
470
|
+
result = report_readback_verification("filters", _projection_request(), schema)
|
|
471
|
+
self.assertFalse(result["verified"], condition)
|
|
472
|
+
self.assertIn("reportFilters", schema["unverifiableSections"])
|
|
473
|
+
|
|
474
|
+
def test_empty_and_projection_only_do_not_prove_other_sections(self):
|
|
475
|
+
response = _projection_response()
|
|
476
|
+
response["data"].pop("formLayout")
|
|
477
|
+
projection = response["data"]["reportFilters"]
|
|
478
|
+
projection["filters"] = []
|
|
479
|
+
projection["filterPanel"]["showFilterFields"].update(format="empty", raw="", parsed=[])
|
|
480
|
+
schema = _projection_schema(response)
|
|
481
|
+
self.assertTrue(report_readback_verification("filters", [], schema)["verified"])
|
|
482
|
+
self.assertFalse(report_readback_verification("columns", [], schema)["verified"])
|
|
483
|
+
|
|
484
|
+
def test_canonical_other_sections_and_dym_do_not_overwrite_projection(self):
|
|
485
|
+
response = _projection_response()
|
|
486
|
+
response["data"]["reportSchema"] = {"readBackAvailable": True, "reportColumns": [],
|
|
487
|
+
"reportFilters": [], "reportPlugins": [], "reportSorts": []}
|
|
488
|
+
schema = _projection_schema(response)
|
|
489
|
+
self.assertEqual(schema["readBackSource"], "canonical")
|
|
490
|
+
self.assertTrue(report_readback_verification("filters", _projection_request(), schema)["verified"])
|
|
491
|
+
response["data"].pop("reportSchema")
|
|
492
|
+
schema = _projection_schema(response)
|
|
493
|
+
report_ops.attach_dym_evidence(schema, {"reportFilters": [
|
|
494
|
+
{"fieldPath": "sup_number", "mustInput": False, "visible": True},
|
|
495
|
+
{"fieldPath": "biz_date", "mustInput": True, "visible": False}],
|
|
496
|
+
"reportPlugins": [{"className": "kd.example.P", "enabled": True}]})
|
|
497
|
+
self.assertTrue(schema["reportFilters"][0]["mustInput"])
|
|
498
|
+
self.assertFalse(schema["reportFilters"][0]["visible"])
|
|
499
|
+
self.assertNotIn("reportPlugins", schema["unverifiableSections"])
|
|
500
|
+
|
|
501
|
+
|
|
325
502
|
if __name__ == "__main__":
|
|
326
503
|
unittest.main(verbosity=2)
|
package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_recreate_plan.py
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
2
|
+
"""不可原位完成的报表列变更必须先规划重建。
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
confirmation instead of being re-implemented here.
|
|
4
|
+
ReportColumns 全量写入会追加实体字段,不能用于修改既有列。追加使用 addReportColumns,
|
|
5
|
+
单叶子删除在已发布补丁的后端使用 removeReportColumn;分组删除、重排、改类型等未覆盖的
|
|
6
|
+
变更仍需另行授权重建。重建不可逆,新表单会获得新 formId,因此必须先备份和比较,
|
|
7
|
+
整个报表的删除继续使用 deleteForm 自身的两步确认,而不在规划器中重新实现。
|
|
9
8
|
|
|
10
9
|
These tests pin the planner: it is read-only, it names what recreating loses, it refuses an
|
|
11
10
|
artifact that would build a different form, and it never claims a fuller diff than the
|
|
@@ -87,7 +86,7 @@ class PlanShapeTest(unittest.TestCase):
|
|
|
87
86
|
|
|
88
87
|
def test_steps_put_deletion_on_delete_form_with_its_own_confirmation(self):
|
|
89
88
|
steps = {step["order"]: step for step in report_ops.plan_report_recreate(_schema())["steps"]}
|
|
90
|
-
self.assertEqual(sorted(steps), [1, 2, 3, 4])
|
|
89
|
+
self.assertEqual(sorted(steps), [1, 2, 3, 4, 5])
|
|
91
90
|
self.assertIn("deleteForm", steps[2]["command"])
|
|
92
91
|
self.assertIn("DESTRUCTIVE_CONFIRMATION_REQUIRED", steps[2]["detail"])
|
|
93
92
|
self.assertIn("6+MZ9NOTMY9R", steps[2]["command"])
|
|
@@ -96,7 +95,7 @@ class PlanShapeTest(unittest.TestCase):
|
|
|
96
95
|
def test_menu_repointing_comes_after_the_recreate_that_mints_the_new_form_id(self):
|
|
97
96
|
# A menu cannot be re-pointed before the recreated form exists.
|
|
98
97
|
steps = {step["order"]: step for step in report_ops.plan_report_recreate(_schema())["steps"]}
|
|
99
|
-
self.assertIn("updateMenu", steps[
|
|
98
|
+
self.assertIn("updateMenu", steps[5]["command"])
|
|
100
99
|
|
|
101
100
|
def test_menu_repointing_comes_after_the_recreate(self):
|
|
102
101
|
"""The new formId only exists once the report is recreated, so menus cannot go first."""
|
|
@@ -177,6 +176,84 @@ class MenuBindingTest(unittest.TestCase):
|
|
|
177
176
|
self.assertIn("no menu entry", menu_step["detail"])
|
|
178
177
|
|
|
179
178
|
|
|
179
|
+
def _plugin_rows(*rows):
|
|
180
|
+
return [{"className": name, "location": location, "pluginType": kind, "enabled": True}
|
|
181
|
+
for name, location, kind in rows]
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class ReportFormPluginTest(unittest.TestCase):
|
|
185
|
+
"""deleteForm takes the report form's own plugins with it, and run does not register them.
|
|
186
|
+
|
|
187
|
+
run registers the data plugins the artifact carries. A reportform plugin sits on the form's
|
|
188
|
+
own plugin list, which queryEditablePlugins reads back as location BILL_FORM, so the plan has
|
|
189
|
+
to name it before deletion or the recreated report silently loses its interaction.
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
def _step(self, plan):
|
|
193
|
+
return next(step for step in plan["steps"] if "formPlugins" in step["action"])
|
|
194
|
+
|
|
195
|
+
def test_the_losses_name_the_report_form_plugins(self):
|
|
196
|
+
joined = " ".join(report_ops.plan_report_recreate(_schema())["lostOnRecreate"])
|
|
197
|
+
self.assertIn("reportform", joined)
|
|
198
|
+
self.assertIn("BILL_FORM", joined)
|
|
199
|
+
|
|
200
|
+
def test_form_plugins_are_captured_and_named_in_their_own_step(self):
|
|
201
|
+
plan = report_ops.plan_report_recreate(_schema(), plugin_rows=_plugin_rows(
|
|
202
|
+
("kd.kdtest.report.KpiDrillPlugin", "BILL_FORM", "JAVA"),
|
|
203
|
+
("kd.kdtest.report.P", "REPORT_QUERY_PLUGIN", "JAVA"),
|
|
204
|
+
))
|
|
205
|
+
self.assertEqual([item["className"] for item in plan["current"]["formPlugins"]],
|
|
206
|
+
["kd.kdtest.report.KpiDrillPlugin"])
|
|
207
|
+
step = self._step(plan)
|
|
208
|
+
self.assertIn("register_plugins.py", step["command"])
|
|
209
|
+
self.assertIn("--verify", step["command"])
|
|
210
|
+
self.assertIn("kd.kdtest.report.KpiDrillPlugin", step["detail"])
|
|
211
|
+
self.assertIn("BILL_FORM", step["detail"])
|
|
212
|
+
# The data plugin is the artifact's, and run registers it again by itself.
|
|
213
|
+
self.assertIn("1 Java plugin(s)", step["detail"])
|
|
214
|
+
|
|
215
|
+
def test_the_plugins_come_back_after_the_recreate_and_before_the_menus(self):
|
|
216
|
+
steps = {step["action"]: step["order"] for step in
|
|
217
|
+
report_ops.plan_report_recreate(_schema(), plugin_rows=[])["steps"]}
|
|
218
|
+
recreate = next(order for action, order in steps.items() if "recreate" in action)
|
|
219
|
+
plugins = next(order for action, order in steps.items() if "formPlugins" in action)
|
|
220
|
+
menu = next(order for action, order in steps.items() if "menu" in action)
|
|
221
|
+
self.assertLess(recreate, plugins)
|
|
222
|
+
self.assertLess(plugins, menu)
|
|
223
|
+
|
|
224
|
+
def test_an_unread_plugin_list_is_not_reported_as_empty(self):
|
|
225
|
+
plan = report_ops.plan_report_recreate(_schema())
|
|
226
|
+
self.assertIsNone(plan["current"]["formPlugins"])
|
|
227
|
+
detail = self._step(plan)["detail"]
|
|
228
|
+
self.assertIn("not read", detail)
|
|
229
|
+
self.assertIn("queryEditablePlugins --formNumber kdtest_sup_pur_sum_rpt", detail)
|
|
230
|
+
|
|
231
|
+
def test_a_read_list_without_form_plugins_says_nothing_to_re_register(self):
|
|
232
|
+
plan = report_ops.plan_report_recreate(_schema(), plugin_rows=_plugin_rows(
|
|
233
|
+
("kd.kdtest.report.P", "REPORT_QUERY_PLUGIN", "JAVA"),
|
|
234
|
+
))
|
|
235
|
+
self.assertEqual(plan["current"]["formPlugins"], [])
|
|
236
|
+
self.assertIn("nothing to re-register", self._step(plan)["detail"])
|
|
237
|
+
|
|
238
|
+
def test_a_script_plugin_is_routed_to_register_plugin(self):
|
|
239
|
+
plan = report_ops.plan_report_recreate(_schema(), plugin_rows=_plugin_rows(
|
|
240
|
+
("kdtest_rpt_script", "BILL_FORM", "KINGSCRIPT"),
|
|
241
|
+
))
|
|
242
|
+
detail = self._step(plan)["detail"]
|
|
243
|
+
self.assertIn("kdtest_rpt_script", detail)
|
|
244
|
+
self.assertIn("registerPlugin", detail)
|
|
245
|
+
self.assertIn("Java classes only", detail)
|
|
246
|
+
|
|
247
|
+
def test_a_data_plugin_on_the_form_list_is_not_re_registered_there(self):
|
|
248
|
+
plan = report_ops.plan_report_recreate(_schema(), plugin_rows=_plugin_rows(
|
|
249
|
+
("kd.kdtest.report.P", "BILL_FORM", "JAVA"),
|
|
250
|
+
))
|
|
251
|
+
self.assertEqual(plan["current"]["formPlugins"], [])
|
|
252
|
+
detail = self._step(plan)["detail"]
|
|
253
|
+
self.assertIn("kd.kdtest.report.P is the report's data plugin", detail)
|
|
254
|
+
self.assertIn("do not register it on the form again", detail)
|
|
255
|
+
|
|
256
|
+
|
|
180
257
|
class BackupTest(unittest.TestCase):
|
|
181
258
|
"""A failed recreate must not be a lost report."""
|
|
182
259
|
|
|
@@ -210,12 +287,18 @@ class RecreatePlanCommandTest(unittest.TestCase):
|
|
|
210
287
|
dispatch so the ownership gate, the design-state sync and that read-only promise are pinned.
|
|
211
288
|
"""
|
|
212
289
|
|
|
213
|
-
def _dispatch(self, argv, *, schema=None, menus=None, sync=None):
|
|
290
|
+
def _dispatch(self, argv, *, schema=None, menus=None, sync=None, plugins=None):
|
|
214
291
|
import cosmic_api
|
|
215
292
|
|
|
216
293
|
emitted = {}
|
|
217
294
|
report_schema = schema if schema is not None else _schema()
|
|
295
|
+
# --app-root has to be a bound Cosmic app directory, which archive.json identifies.
|
|
296
|
+
app_root = argv[argv.index("--app-root") + 1]
|
|
297
|
+
with open(os.path.join(app_root, "archive.json"), "w", encoding="utf-8") as handle:
|
|
298
|
+
json.dump({"type": "cosmic", "appCode": "kdtest", "appId": "APP-ID"}, handle)
|
|
218
299
|
menu_result = {"data": {"menus": menus if menus is not None else []}}
|
|
300
|
+
plugin_result = {"status": True, "errorCode": "0",
|
|
301
|
+
"data": plugins if plugins is not None else []}
|
|
219
302
|
patches = {
|
|
220
303
|
"load_config": mock.DEFAULT,
|
|
221
304
|
"do_auth": mock.DEFAULT,
|
|
@@ -223,6 +306,7 @@ class RecreatePlanCommandTest(unittest.TestCase):
|
|
|
223
306
|
"do_get_report_schema": mock.DEFAULT,
|
|
224
307
|
"_export_report_dym_evidence": mock.DEFAULT,
|
|
225
308
|
"do_query_menu_tree": mock.DEFAULT,
|
|
309
|
+
"do_query_editable_plugins": mock.DEFAULT,
|
|
226
310
|
"pjson": mock.DEFAULT,
|
|
227
311
|
}
|
|
228
312
|
with mock.patch.multiple(cosmic_api, **patches) as mocks, \
|
|
@@ -241,6 +325,9 @@ class RecreatePlanCommandTest(unittest.TestCase):
|
|
|
241
325
|
mocks["_export_report_dym_evidence"].side_effect = sync
|
|
242
326
|
mocks["_export_report_dym_evidence"].return_value = ""
|
|
243
327
|
mocks["do_query_menu_tree"].return_value = menu_result
|
|
328
|
+
if isinstance(plugins, Exception):
|
|
329
|
+
mocks["do_query_editable_plugins"].side_effect = plugins
|
|
330
|
+
mocks["do_query_editable_plugins"].return_value = plugin_result
|
|
244
331
|
mocks["pjson"].side_effect = emitted.update
|
|
245
332
|
cosmic_api._main_impl()
|
|
246
333
|
writes = {
|
|
@@ -296,7 +383,35 @@ class RecreatePlanCommandTest(unittest.TestCase):
|
|
|
296
383
|
"--app-root", tmp],
|
|
297
384
|
sync=RuntimeError("sync root not configured"))
|
|
298
385
|
self.assertTrue(emitted["status"])
|
|
299
|
-
self.assertEqual(
|
|
386
|
+
self.assertEqual(5, len(emitted["data"]["steps"]))
|
|
387
|
+
|
|
388
|
+
def test_command_reads_the_report_form_plugins_before_anything_is_deleted(self):
|
|
389
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
390
|
+
emitted, mocks, _writes = self._dispatch(
|
|
391
|
+
["planReportRecreate", "--formNumber", "kdtest_sup_pur_sum_rpt",
|
|
392
|
+
"--app-root", tmp],
|
|
393
|
+
plugins=_plugin_rows(
|
|
394
|
+
("kd.kdtest.report.KpiDrillPlugin", "BILL_FORM", "JAVA"),
|
|
395
|
+
("kd.kdtest.report.P", "REPORT_QUERY_PLUGIN", "JAVA"),
|
|
396
|
+
))
|
|
397
|
+
with open(emitted["data"]["backupPath"], encoding="utf-8") as handle:
|
|
398
|
+
saved = json.load(handle)
|
|
399
|
+
self.assertEqual("kdtest_sup_pur_sum_rpt",
|
|
400
|
+
mocks["do_query_editable_plugins"].call_args.kwargs["form_number"])
|
|
401
|
+
self.assertEqual(["kd.kdtest.report.KpiDrillPlugin"],
|
|
402
|
+
[item["className"] for item in emitted["data"]["current"]["formPlugins"]])
|
|
403
|
+
# The backup is the record that survives deleteForm, so it carries them too.
|
|
404
|
+
self.assertEqual("kd.kdtest.report.KpiDrillPlugin",
|
|
405
|
+
saved["plan"]["current"]["formPlugins"][0]["className"])
|
|
406
|
+
|
|
407
|
+
def test_a_failed_plugin_read_leaves_the_list_unread_instead_of_empty(self):
|
|
408
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
409
|
+
emitted, _mocks, _writes = self._dispatch(
|
|
410
|
+
["planReportRecreate", "--formNumber", "kdtest_sup_pur_sum_rpt",
|
|
411
|
+
"--app-root", tmp],
|
|
412
|
+
plugins=RuntimeError("queryEditablePlugins unavailable"))
|
|
413
|
+
self.assertTrue(emitted["status"])
|
|
414
|
+
self.assertIsNone(emitted["data"]["current"]["formPlugins"])
|
|
300
415
|
|
|
301
416
|
def test_command_reports_the_menu_entries_that_break_on_delete(self):
|
|
302
417
|
with tempfile.TemporaryDirectory() as tmp:
|
package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_serve_contract.py
CHANGED
|
@@ -15,7 +15,9 @@ reached.
|
|
|
15
15
|
|
|
16
16
|
import os
|
|
17
17
|
import sys
|
|
18
|
+
import tempfile
|
|
18
19
|
import unittest
|
|
20
|
+
from pathlib import Path
|
|
19
21
|
from unittest import mock
|
|
20
22
|
|
|
21
23
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
@@ -117,6 +119,212 @@ class ServeReportContractTest(unittest.TestCase):
|
|
|
117
119
|
cmd, {"formNumber": "kdtest_rpt", key: []}, target)
|
|
118
120
|
self.assertIn("non-empty array", message)
|
|
119
121
|
|
|
122
|
+
def test_removal_rejects_non_identifier_and_non_boolean_dry_run(self):
|
|
123
|
+
for field_key in (None, [], 12, "person.name", "../person", "人物"):
|
|
124
|
+
with self.subTest(fieldKey=field_key):
|
|
125
|
+
message = self._dispatch("removeReportColumn", {
|
|
126
|
+
"appRoot": "fixture", "formNumber": "kdtest_rpt", "fieldKey": field_key,
|
|
127
|
+
}, "do_remove_report_column")
|
|
128
|
+
self.assertIn("REPORT_INPUT_INVALID", message)
|
|
129
|
+
self.assertIn("dryRun must be boolean", self._dispatch("removeReportColumn", {
|
|
130
|
+
"appRoot": "fixture", "formNumber": "kdtest_rpt", "fieldKey": "person", "dryRun": "false",
|
|
131
|
+
}, "do_remove_report_column"))
|
|
132
|
+
|
|
133
|
+
def test_removal_requires_owned_scope_and_has_local_only_dry_run(self):
|
|
134
|
+
for missing in ("appRoot", "formNumber"):
|
|
135
|
+
with self.subTest(missing=missing):
|
|
136
|
+
args = {"appRoot": "fixture", "formNumber": "kdtest_rpt", "fieldKey": "person"}
|
|
137
|
+
args.pop(missing)
|
|
138
|
+
self.assertIn("requires appRoot", self._dispatch("removeReportColumn", args, "do_remove_report_column"))
|
|
139
|
+
from _core import report_ops
|
|
140
|
+
with mock.patch.object(report_ops, "_api_call") as api, \
|
|
141
|
+
mock.patch.object(cosmic_api, "_resolve_owned_report_target") as resolve:
|
|
142
|
+
result = cosmic_api._dispatch_serve({}, "fixture", "removeReportColumn", {
|
|
143
|
+
"appRoot": "fixture", "formNumber": "kdtest_rpt", "fieldKey": "person", "dryRun": True,
|
|
144
|
+
})
|
|
145
|
+
api.assert_not_called()
|
|
146
|
+
resolve.assert_not_called()
|
|
147
|
+
self.assertTrue(result["dryRun"])
|
|
148
|
+
self.assertFalse(result["capabilityVerified"])
|
|
149
|
+
self.assertFalse(result["writeAttempted"])
|
|
150
|
+
self.assertEqual(result["payload"]["complexType"], "ReportColumns.remove")
|
|
151
|
+
|
|
152
|
+
def test_removal_corroborates_form_id_and_report_type_before_write(self):
|
|
153
|
+
for row in ({"formId": "OTHER", "modelType": "ReportFormModel"},
|
|
154
|
+
{"formId": "FID", "modelType": "BillFormModel"}):
|
|
155
|
+
with self.subTest(row=row), mock.patch.object(cosmic_api, "_assert_form_number_target_owned", return_value=row), \
|
|
156
|
+
mock.patch.object(cosmic_api, "do_remove_report_column") as write:
|
|
157
|
+
with self.assertRaises(RuntimeError):
|
|
158
|
+
cosmic_api._dispatch_serve({}, "fixture", "removeReportColumn", {
|
|
159
|
+
"appRoot": "fixture", "formNumber": "kdtest_rpt", "formId": "FID", "fieldKey": "person",
|
|
160
|
+
})
|
|
161
|
+
write.assert_not_called()
|
|
162
|
+
|
|
163
|
+
def test_removal_describe_command_and_cli_dry_run_are_available_without_auth(self):
|
|
164
|
+
import io
|
|
165
|
+
import json
|
|
166
|
+
from contextlib import ExitStack, redirect_stdout, redirect_stderr
|
|
167
|
+
from _core import config, report_ops
|
|
168
|
+
# CLI 会切换运行路径;使用真实绑定夹具,并在测试结束后还原进程全局状态。
|
|
169
|
+
with tempfile.TemporaryDirectory() as root, ExitStack() as stack:
|
|
170
|
+
Path(root, "archive.json").write_text(json.dumps({
|
|
171
|
+
"type": "cosmic", "appCode": "fixture", "appId": "APP",
|
|
172
|
+
}), encoding="utf-8")
|
|
173
|
+
stack.enter_context(mock.patch.dict(os.environ))
|
|
174
|
+
names = ("RUNTIME_DIR", "TOKEN_CACHE", "BIZ_CACHE", "CONTEXT_MD", "CONFIG_MD",
|
|
175
|
+
"ARTIFACTS_DIR", "BATCH_MANIFESTS_DIR", "TEMP_ARTIFACT", "WORKSPACE_JSON")
|
|
176
|
+
for module in (cosmic_api, config):
|
|
177
|
+
stack.enter_context(mock.patch.multiple(module, **{name: getattr(module, name) for name in names}))
|
|
178
|
+
stack.enter_context(mock.patch.object(config, "_BOUND_APP_ROOT", getattr(config, "_BOUND_APP_ROOT", None), create=True))
|
|
179
|
+
for arguments in (["describeCommand", "removeReportColumn"],
|
|
180
|
+
["removeReportColumn", "--app-root", root, "--formNumber", "kdtest_rpt", "--fieldKey", "person", "--dryRun"]):
|
|
181
|
+
with self.subTest(arguments=arguments), mock.patch.object(sys, "argv", ["cosmic_api.py", "--stdout", *arguments]), \
|
|
182
|
+
mock.patch.object(cosmic_api, "do_auth") as auth, \
|
|
183
|
+
mock.patch.object(cosmic_api, "_resolve_owned_report_target") as resolve, \
|
|
184
|
+
mock.patch.object(report_ops, "_api_call") as api, \
|
|
185
|
+
redirect_stdout(io.StringIO()) as output, redirect_stderr(io.StringIO()):
|
|
186
|
+
self.assertEqual(cosmic_api.main(), 0)
|
|
187
|
+
result = json.loads(output.getvalue())
|
|
188
|
+
auth.assert_not_called()
|
|
189
|
+
resolve.assert_not_called()
|
|
190
|
+
api.assert_not_called()
|
|
191
|
+
if arguments[0] == "describeCommand":
|
|
192
|
+
self.assertEqual(result["action"]["command"], "removeReportColumn")
|
|
193
|
+
self.assertIn("columnRemoval", str(result))
|
|
194
|
+
else:
|
|
195
|
+
self.assertTrue(result["dryRun"])
|
|
196
|
+
self.assertFalse(result["capabilityVerified"])
|
|
197
|
+
|
|
198
|
+
def test_valid_sorts_are_blocked_without_any_http(self):
|
|
199
|
+
import json
|
|
200
|
+
from _core import report_ops, cmd_properties
|
|
201
|
+
sorts = [{"fieldKey": "amount", "direction": "desc"}]
|
|
202
|
+
with mock.patch.object(report_ops, "_api_call") as api:
|
|
203
|
+
for dry_run in (False, True):
|
|
204
|
+
with self.subTest(dryRun=dry_run), self.assertRaisesRegex(RuntimeError, "REPORT_SORTS_API_UNSAFE"):
|
|
205
|
+
report_ops.set_report_sorts({}, "token", "FID", sorts, dry_run=dry_run)
|
|
206
|
+
with self.assertRaisesRegex(RuntimeError, "REPORT_SORTS_API_UNSAFE"):
|
|
207
|
+
cosmic_api._dispatch_serve({}, "token", "setReportSorts", {
|
|
208
|
+
"formNumber": "kdtest_rpt", "sorts": sorts})
|
|
209
|
+
with self.assertRaisesRegex(RuntimeError, "REPORT_SORTS_API_UNSAFE"):
|
|
210
|
+
report_ops._set_complex({}, "token", "kdtest_rpt", "ReportSorts", {"sorts": sorts})
|
|
211
|
+
api.assert_not_called()
|
|
212
|
+
polluted = [{"purpose": "sort", "direction": "desc", "freezelistkey": "amount"}]
|
|
213
|
+
with mock.patch.object(cmd_properties, "_api_call") as api:
|
|
214
|
+
for complex_type, data in (("ReportSorts", {"sorts": sorts}),
|
|
215
|
+
("initField", polluted),
|
|
216
|
+
("initField", json.dumps(polluted)),
|
|
217
|
+
("ReportListSettings", {"InitField": polluted}),
|
|
218
|
+
("ReportListSettings", {"InitField": json.dumps(polluted)})):
|
|
219
|
+
result = cmd_properties.do_set_complex_property({}, "token", "kdtest_rpt", complex_type, data)
|
|
220
|
+
self.assertEqual(result["errorCode"], "REPORT_SORTS_API_UNSAFE")
|
|
221
|
+
self.assertFalse(result["writeAttempted"])
|
|
222
|
+
result = cmd_properties.do_set_properties({}, "token", "kdtest_rpt", "reportlistap", {"InitField": polluted})
|
|
223
|
+
self.assertFalse(result["writeAttempted"])
|
|
224
|
+
api.assert_not_called()
|
|
225
|
+
cmd_properties.do_set_complex_property({}, "token", "kdtest_rpt", "initField", [
|
|
226
|
+
{"fieldkey": "amount", "isvisible": "false"}], element_key="reportlistap")
|
|
227
|
+
api.assert_called_once()
|
|
228
|
+
|
|
229
|
+
def test_sort_catalog_and_cli_refuse_before_auth_and_preserve_baseline(self):
|
|
230
|
+
import io
|
|
231
|
+
import json
|
|
232
|
+
from contextlib import ExitStack, redirect_stdout, redirect_stderr
|
|
233
|
+
from _core import config, report_ops
|
|
234
|
+
with tempfile.TemporaryDirectory() as root, ExitStack() as stack:
|
|
235
|
+
Path(root, "archive.json").write_text(json.dumps({
|
|
236
|
+
"type": "cosmic", "appCode": "fixture", "appId": "APP",
|
|
237
|
+
}), encoding="utf-8")
|
|
238
|
+
baseline = report_ops.report_authoring_path(root, "kdtest_rpt")
|
|
239
|
+
baseline.parent.mkdir(parents=True, exist_ok=True)
|
|
240
|
+
baseline.write_text('{"status":"current","fixture":"unchanged"}', encoding="utf-8")
|
|
241
|
+
original = baseline.read_bytes()
|
|
242
|
+
stack.enter_context(mock.patch.dict(os.environ))
|
|
243
|
+
names = ("RUNTIME_DIR", "TOKEN_CACHE", "BIZ_CACHE", "CONTEXT_MD", "CONFIG_MD",
|
|
244
|
+
"ARTIFACTS_DIR", "BATCH_MANIFESTS_DIR", "TEMP_ARTIFACT", "WORKSPACE_JSON")
|
|
245
|
+
for module in (cosmic_api, config):
|
|
246
|
+
stack.enter_context(mock.patch.multiple(module, **{name: getattr(module, name) for name in names}))
|
|
247
|
+
stack.enter_context(mock.patch.object(config, "_BOUND_APP_ROOT", getattr(config, "_BOUND_APP_ROOT", None), create=True))
|
|
248
|
+
sorts = [{"fieldKey": "amount", "direction": "desc"}]
|
|
249
|
+
write_args = ["setReportSorts", "--app-root", root, "--formNumber", "kdtest_rpt", "--sorts", json.dumps(sorts)]
|
|
250
|
+
for arguments in (["describeCommand", "setReportSorts"], write_args, [*write_args, "--dryRun"]):
|
|
251
|
+
with self.subTest(arguments=arguments), mock.patch.object(sys, "argv", ["cosmic_api.py", "--stdout", *arguments]), \
|
|
252
|
+
mock.patch.object(cosmic_api, "do_auth") as auth, \
|
|
253
|
+
mock.patch.object(cosmic_api, "load_config") as load, \
|
|
254
|
+
mock.patch.object(cosmic_api, "_resolve_owned_report_target") as resolve, \
|
|
255
|
+
mock.patch.object(report_ops, "_api_call") as api, \
|
|
256
|
+
redirect_stdout(io.StringIO()) as output, redirect_stderr(io.StringIO()):
|
|
257
|
+
code = cosmic_api.main()
|
|
258
|
+
result = json.loads(output.getvalue())
|
|
259
|
+
auth.assert_not_called()
|
|
260
|
+
load.assert_not_called()
|
|
261
|
+
resolve.assert_not_called()
|
|
262
|
+
api.assert_not_called()
|
|
263
|
+
if arguments[0] == "describeCommand":
|
|
264
|
+
self.assertEqual(code, 0)
|
|
265
|
+
self.assertEqual(result["action"]["availability"], "blocked")
|
|
266
|
+
self.assertFalse(result["action"]["available"])
|
|
267
|
+
else:
|
|
268
|
+
self.assertNotEqual(code, 0)
|
|
269
|
+
self.assertEqual(result["errorCode"], "REPORT_SORTS_API_UNSAFE")
|
|
270
|
+
self.assertFalse(result["writeAttempted"])
|
|
271
|
+
self.assertEqual(baseline.read_bytes(), original)
|
|
272
|
+
with mock.patch.object(report_ops, "_api_call") as api, \
|
|
273
|
+
mock.patch.object(cosmic_api, "_resolve_owned_report_target") as resolve:
|
|
274
|
+
with self.assertRaisesRegex(RuntimeError, "REPORT_SORTS_API_UNSAFE"):
|
|
275
|
+
cosmic_api._dispatch_serve({}, "token", "setReportSorts", {
|
|
276
|
+
"appRoot": root, "formNumber": "kdtest_rpt", "sorts": sorts})
|
|
277
|
+
api.assert_not_called()
|
|
278
|
+
resolve.assert_not_called()
|
|
279
|
+
self.assertEqual(baseline.read_bytes(), original)
|
|
280
|
+
|
|
281
|
+
def test_dynamic_precision_cli_refuses_before_auth_and_preserves_baseline(self):
|
|
282
|
+
import io
|
|
283
|
+
import json
|
|
284
|
+
from contextlib import ExitStack, redirect_stdout, redirect_stderr
|
|
285
|
+
from _core import config, report_ops
|
|
286
|
+
with tempfile.TemporaryDirectory() as root, ExitStack() as stack:
|
|
287
|
+
Path(root, "archive.json").write_text(json.dumps({
|
|
288
|
+
"type": "cosmic", "appCode": "fixture", "appId": "APP",
|
|
289
|
+
}), encoding="utf-8")
|
|
290
|
+
baseline = report_ops.report_authoring_path(root, "kdtest_rpt")
|
|
291
|
+
baseline.parent.mkdir(parents=True, exist_ok=True)
|
|
292
|
+
baseline.write_text('{"status":"current","fixture":"unchanged"}', encoding="utf-8")
|
|
293
|
+
original = baseline.read_bytes()
|
|
294
|
+
# 真实 CLI 会绑定运行目录;还原进程全局状态,门禁本身不 mock。
|
|
295
|
+
stack.enter_context(mock.patch.dict(os.environ))
|
|
296
|
+
names = ("RUNTIME_DIR", "TOKEN_CACHE", "BIZ_CACHE", "CONTEXT_MD", "CONFIG_MD",
|
|
297
|
+
"ARTIFACTS_DIR", "BATCH_MANIFESTS_DIR", "TEMP_ARTIFACT", "WORKSPACE_JSON")
|
|
298
|
+
for module in (cosmic_api, config):
|
|
299
|
+
stack.enter_context(mock.patch.multiple(module, **{name: getattr(module, name) for name in names}))
|
|
300
|
+
stack.enter_context(mock.patch.object(config, "_BOUND_APP_ROOT", getattr(config, "_BOUND_APP_ROOT", None), create=True))
|
|
301
|
+
for kind in ("amount", "price", "qty"):
|
|
302
|
+
key = "measureUnitField" if kind == "qty" else "currencyField"
|
|
303
|
+
columns = [{"kind": "column", "fieldKey": "value", "dateIndex": "value",
|
|
304
|
+
"fieldType": "decimal", "caption": "Value", "presentation": {"kind": kind, key: "reference"}},
|
|
305
|
+
{"kind": "column", "fieldKey": "reference", "dateIndex": "reference",
|
|
306
|
+
"fieldType": "bigint", "caption": "Reference", "hide": True}]
|
|
307
|
+
for cmd in ("setReportColumns", "addReportColumns"):
|
|
308
|
+
for dry_run in (False, True):
|
|
309
|
+
arguments = [cmd, "--app-root", root, "--formNumber", "kdtest_rpt", "--columns", json.dumps(columns)]
|
|
310
|
+
with self.subTest(kind=kind, cmd=cmd, dryRun=dry_run), \
|
|
311
|
+
mock.patch.object(sys, "argv", ["cosmic_api.py", "--stdout", *arguments, *(["--dryRun"] if dry_run else [])]), \
|
|
312
|
+
mock.patch.object(cosmic_api, "do_auth") as auth, \
|
|
313
|
+
mock.patch.object(cosmic_api, "load_config") as load, \
|
|
314
|
+
mock.patch.object(cosmic_api, "_resolve_owned_report_target") as resolve, \
|
|
315
|
+
mock.patch.object(cosmic_api, "do_set_report_columns") as write, \
|
|
316
|
+
mock.patch.object(cosmic_api, "do_add_report_columns") as append, \
|
|
317
|
+
mock.patch.object(report_ops, "_api_call") as http, \
|
|
318
|
+
redirect_stdout(io.StringIO()) as output, redirect_stderr(io.StringIO()):
|
|
319
|
+
self.assertNotEqual(cosmic_api.main(), 0)
|
|
320
|
+
result = json.loads(output.getvalue())
|
|
321
|
+
self.assertFalse(result["status"])
|
|
322
|
+
self.assertEqual(result["errorCode"], "REPORT_INPUT_INVALID")
|
|
323
|
+
self.assertIn("REPORT_PRECISION_REFERENCE_UNVERIFIED", result["message"])
|
|
324
|
+
for boundary in (auth, load, resolve, write, append, http):
|
|
325
|
+
boundary.assert_not_called()
|
|
326
|
+
self.assertEqual(baseline.read_bytes(), original)
|
|
327
|
+
|
|
120
328
|
def test_valid_payload_reaches_the_write_normalized(self):
|
|
121
329
|
with mock.patch.object(cosmic_api, "do_register_report_plugin") as write:
|
|
122
330
|
write.return_value = "kd.example.report.P"
|