@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_typed_filters.py
CHANGED
|
@@ -1,65 +1,274 @@
|
|
|
1
|
+
"""Typed report filter editors, against the platform's typed report filter API.
|
|
2
|
+
|
|
3
|
+
The API ships as a platform patch and this skill ships with the client, so every test here is
|
|
4
|
+
one of three platforms: one with the patch (it answers with a per-filter receipt), one without
|
|
5
|
+
it (it accepts the request, ignores the editor part and renders the legacy control), and one
|
|
6
|
+
with an older patch (it refuses an editor type it does not offer, before saving anything).
|
|
7
|
+
"""
|
|
1
8
|
import sys
|
|
2
9
|
from pathlib import Path
|
|
10
|
+
|
|
3
11
|
import pytest
|
|
4
12
|
|
|
5
13
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
6
14
|
from _core import report_ops
|
|
7
15
|
|
|
8
16
|
|
|
9
|
-
def
|
|
10
|
-
return report_ops.normalize_report_filters_input(
|
|
11
|
-
"fieldPath": "flt_supplier", "displayName": "Supplier", "compare": "in",
|
|
12
|
-
"source": {"formNumber": "purchase", "fieldKey": "supplier"},
|
|
13
|
-
}])
|
|
17
|
+
def _filters(*items):
|
|
18
|
+
return report_ops.normalize_report_filters_input(list(items))
|
|
14
19
|
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
SUPPLIER = {"fieldPath": "flt_supplier", "displayName": "Supplier", "compare": "in",
|
|
22
|
+
"fieldType": "MulBasedataField", "basedataNumber": "bd_supplier"}
|
|
23
|
+
PERIOD = {"fieldPath": "flt_period", "displayName": "Period", "compare": "between",
|
|
24
|
+
"fieldType": "DateField"}
|
|
25
|
+
STATUS = {"fieldPath": "flt_status", "displayName": "Status", "compare": "=",
|
|
26
|
+
"fieldType": "combo", "comboItems": [{"value": 0, "caption": "Draft"},
|
|
27
|
+
{"value": "01", "caption": "Audited"}]}
|
|
21
28
|
|
|
22
29
|
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
assert writes == []
|
|
30
|
+
def _receipt(*rows):
|
|
31
|
+
return {"errorCode": "0", "status": True, "data": {
|
|
32
|
+
"success": True, "errorCode": "0",
|
|
33
|
+
"data": {"filters": [{"fieldPath": key, "fieldType": field_type, "fieldId": "ID",
|
|
34
|
+
"action": "created"} for key, field_type in rows],
|
|
35
|
+
"removed": []}}}
|
|
30
36
|
|
|
31
37
|
|
|
32
|
-
def
|
|
38
|
+
def _write(monkeypatch, responses, filters):
|
|
33
39
|
calls = []
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
assert
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
40
|
+
|
|
41
|
+
def set_complex(*args):
|
|
42
|
+
calls.append(args)
|
|
43
|
+
return responses[min(len(calls), len(responses)) - 1]
|
|
44
|
+
|
|
45
|
+
monkeypatch.setattr(report_ops, "_assert_report_entity_writable", lambda *a, **k: None)
|
|
46
|
+
monkeypatch.setattr(report_ops, "_set_complex", set_complex)
|
|
47
|
+
outcome = {}
|
|
48
|
+
ids = report_ops.set_report_filters({}, "token", "FID", filters, form_number="kdtest_rpt",
|
|
49
|
+
outcome=outcome)
|
|
50
|
+
return ids, outcome, calls
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ── input contract: the patch's own rules, enforced before the write ──────────────────────
|
|
54
|
+
|
|
55
|
+
def test_aliases_normalize_and_a_single_date_editor_becomes_a_range_for_between():
|
|
56
|
+
supplier, period, status = _filters(
|
|
57
|
+
{**SUPPLIER, "fieldType": "mulbasedata"}, PERIOD, STATUS)
|
|
58
|
+
assert supplier["fieldType"] == "MulBasedataField"
|
|
59
|
+
assert period["fieldType"] == "DateRangeField"
|
|
60
|
+
assert status["fieldType"] == "ComboField"
|
|
61
|
+
# Stored dropdown values are kept exactly: 0 and leading zeros are values.
|
|
62
|
+
assert [row["value"] for row in status["comboItems"]] == ["0", "01"]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@pytest.mark.parametrize("item, message", [
|
|
66
|
+
({**SUPPLIER, "basedataNumber": None}, "basedataNumber is required"),
|
|
67
|
+
({**SUPPLIER, "compare": "between"}, "needs a range editor"),
|
|
68
|
+
({"fieldPath": "flt_d", "displayName": "D", "compare": "=", "fieldType": "DateRangeField"},
|
|
69
|
+
"needs compare=between"),
|
|
70
|
+
({"fieldPath": "flt_o", "displayName": "O", "compare": "=", "fieldType": "OrgField",
|
|
71
|
+
"basedataNumber": "bos_org"}, "does not apply to fieldType OrgField"),
|
|
72
|
+
({"fieldPath": "flt_s", "displayName": "S", "compare": "=",
|
|
73
|
+
"comboItems": [{"value": "A", "caption": "A"}]}, "needs fieldType"),
|
|
74
|
+
({**SUPPLIER, "fieldType": "AmountField"}, "not a report filter editor type"),
|
|
75
|
+
({"fieldPath": "flt_u", "displayName": "U", "compare": "=", "fieldType": "UserField",
|
|
76
|
+
"defaultValue": "1"}, "defaultValue is not supported on UserField"),
|
|
77
|
+
({**STATUS, "comboItems": [{"value": "A", "caption": "A"}, {"value": "A", "caption": "B"}]},
|
|
78
|
+
"duplicate"),
|
|
79
|
+
({**SUPPLIER, "mustInput": True, "visible": False}, "mustInput and visible:false"),
|
|
80
|
+
({"fieldPath": "flt_a", "displayName": "A", "compare": "=", "fieldType": "AssistantField",
|
|
81
|
+
"asstTypeId": "group-a"}, "numeric id"),
|
|
82
|
+
])
|
|
83
|
+
def test_editor_rules_fail_before_any_write(item, message):
|
|
84
|
+
with pytest.raises(ValueError, match=message):
|
|
85
|
+
_filters({k: v for k, v in item.items() if v is not None})
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_the_retired_source_and_a_raw_id_are_refused_with_the_replacement():
|
|
89
|
+
with pytest.raises(ValueError, match="source is retired: declare fieldType"):
|
|
90
|
+
_filters({"fieldPath": "flt_supplier", "displayName": "S", "compare": "in",
|
|
91
|
+
"source": {"formNumber": "pm_purorderbill", "fieldKey": "supplier"}})
|
|
92
|
+
with pytest.raises(ValueError, match="pass basedataNumber"):
|
|
93
|
+
_filters({**SUPPLIER, "baseEntityId": "1234567890"})
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_an_item_class_editor_names_its_type_filter_in_the_same_request():
|
|
97
|
+
kind = {"fieldPath": "flt_kind", "displayName": "Kind", "compare": "=",
|
|
98
|
+
"fieldType": "ItemClassTypeField",
|
|
99
|
+
"itemClassTypes": [{"basedataNumber": "bd_customer"}, {"basedataNumber": "bd_supplier"}]}
|
|
100
|
+
party = {"fieldPath": "flt_party", "displayName": "Party", "compare": "=",
|
|
101
|
+
"fieldType": "ItemClassField", "typeFieldKey": "flt_kind"}
|
|
102
|
+
assert _filters(kind, party)[1]["typeFieldKey"] == "flt_kind"
|
|
103
|
+
with pytest.raises(ValueError, match="must name an ItemClassTypeField filter"):
|
|
104
|
+
_filters(party)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_the_editor_part_travels_only_with_its_field_type():
|
|
108
|
+
payload = report_ops._build_filters_payload(_filters(SUPPLIER, PERIOD))
|
|
109
|
+
assert payload[0] == {"fieldPath": "flt_supplier", "displayName": "Supplier", "compare": "9",
|
|
110
|
+
"fieldType": "MulBasedataField", "basedataNumber": "bd_supplier"}
|
|
111
|
+
assert payload[1]["fieldType"] == "DateRangeField" and payload[1]["compare"] == "13"
|
|
112
|
+
plain = report_ops._build_filters_payload(_filters(
|
|
113
|
+
{"fieldPath": "flt_text", "displayName": "T", "compare": "="}))[0]
|
|
114
|
+
assert "fieldType" not in plain
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_dry_run_names_each_typed_editor_and_its_legacy_fallback():
|
|
118
|
+
result = report_ops.set_report_filters({}, None, "FID", _filters(SUPPLIER, PERIOD), dry_run=True)
|
|
119
|
+
assert result["typedFilterEditors"] == [
|
|
120
|
+
{"fieldPath": "flt_supplier", "fieldType": "MulBasedataField", "legacyFieldType": "TextField"},
|
|
121
|
+
{"fieldPath": "flt_period", "fieldType": "DateRangeField", "legacyFieldType": "DateRangeField"},
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ── the write against each kind of platform ───────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
def test_a_patched_platform_proves_every_editor_with_its_receipt(monkeypatch):
|
|
128
|
+
ids, outcome, calls = _write(monkeypatch, [_receipt(
|
|
129
|
+
("flt_supplier", "MulBasedataField"), ("flt_period", "DateRangeField"))],
|
|
130
|
+
_filters(SUPPLIER, PERIOD))
|
|
131
|
+
assert ids == ["flt_supplier", "flt_period"]
|
|
132
|
+
assert len(calls) == 1 and calls[0][3] == "ReportFilters"
|
|
133
|
+
assert outcome["platformReceipt"] is True
|
|
134
|
+
assert outcome["typedFilterSupport"] == "available"
|
|
135
|
+
assert [row["status"] for row in outcome["editors"]] == ["typed", "typed"]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_an_unpatched_platform_is_not_a_failed_write(monkeypatch):
|
|
139
|
+
"""No receipt: the platform ignored fieldType. The write stands; the read-back decides."""
|
|
140
|
+
ids, outcome, calls = _write(monkeypatch, [{"errorCode": "0", "status": True,
|
|
141
|
+
"data": {"success": True}}],
|
|
142
|
+
_filters(SUPPLIER, PERIOD))
|
|
143
|
+
assert len(calls) == 1
|
|
144
|
+
assert outcome["platformReceipt"] is False
|
|
145
|
+
assert outcome["typedFilterSupport"] == "unconfirmed"
|
|
146
|
+
resolved, mismatches = report_ops.resolve_filter_editors(outcome, [
|
|
147
|
+
{"fieldPath": "flt_supplier", "fieldType": "TextField"},
|
|
148
|
+
{"fieldPath": "flt_period", "fieldType": "DateRangeField"}])
|
|
149
|
+
assert mismatches == []
|
|
150
|
+
assert [row["status"] for row in resolved["editors"]] == ["degraded", "typed"]
|
|
151
|
+
assert resolved["typedFilterSupport"] == "partial"
|
|
152
|
+
assert resolved["degradedFilters"] == ["flt_supplier"]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_an_older_patch_refusing_an_editor_type_gets_that_filter_resent_without_it(monkeypatch):
|
|
156
|
+
refused = {"errorCode": "META_SAVE_FAIL", "status": False, "message": (
|
|
157
|
+
"过滤字段 flt_supplier 的 fieldType 不支持: MulBasedataField。可选值: TextField, "
|
|
158
|
+
"DateField, DateRangeField, ComboField, BasedataField。")}
|
|
159
|
+
ids, outcome, calls = _write(monkeypatch, [refused, _receipt(
|
|
160
|
+
("flt_supplier", "TextField"), ("flt_period", "DateRangeField"))],
|
|
161
|
+
_filters(SUPPLIER, PERIOD))
|
|
162
|
+
assert len(calls) == 2
|
|
163
|
+
first, second = calls[0][4]["filters"], calls[1][4]["filters"]
|
|
164
|
+
assert first[0]["fieldType"] == "MulBasedataField"
|
|
165
|
+
assert "fieldType" not in second[0] and "basedataNumber" not in second[0]
|
|
166
|
+
assert second[1]["fieldType"] == "DateRangeField"
|
|
167
|
+
assert outcome["typedFilterSupport"] == "partial"
|
|
168
|
+
assert outcome["platformFieldTypes"] == [
|
|
169
|
+
"BasedataField", "ComboField", "DateField", "DateRangeField", "TextField"]
|
|
170
|
+
assert outcome["editors"][0]["status"] == "degraded"
|
|
171
|
+
assert "does not offer MulBasedataField" in outcome["editors"][0]["reason"]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_any_other_refusal_fails_the_write_without_a_retry(monkeypatch):
|
|
175
|
+
refused = {"errorCode": "META_SAVE_FAIL", "status": False,
|
|
176
|
+
"message": "过滤字段 flt_supplier:基础资料实体不存在: bd_nothing。"}
|
|
177
|
+
with pytest.raises(RuntimeError, match="setReportFilters failed"):
|
|
178
|
+
_write(monkeypatch, [refused], _filters({**SUPPLIER, "basedataNumber": "bd_nothing"}))
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_a_receipt_showing_neither_editor_is_a_mismatch(monkeypatch):
|
|
182
|
+
_ids, outcome, _calls = _write(monkeypatch, [_receipt(
|
|
183
|
+
("flt_supplier", "ComboField"), ("flt_period", "DateRangeField"))],
|
|
184
|
+
_filters(SUPPLIER, PERIOD))
|
|
185
|
+
assert outcome["typedFilterSupport"] == "mismatch"
|
|
186
|
+
_resolved, mismatches = report_ops.resolve_filter_editors(outcome, [])
|
|
187
|
+
assert mismatches and mismatches[0]["actual"] == "ComboField"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# ── read-back ─────────────────────────────────────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
def _derived_schema(rows, outcome=None):
|
|
193
|
+
schema = {"readBackAvailable": True, "readBackSource": "formLayout",
|
|
194
|
+
"undecidableFilterProperties": ["compare", "mustInput", "visible", "defaultValue"],
|
|
195
|
+
"reportFilters": rows}
|
|
196
|
+
if outcome:
|
|
197
|
+
schema["filterEditorOutcome"] = outcome
|
|
198
|
+
return schema
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_read_back_holds_a_typed_editor_to_what_the_receipt_claimed():
|
|
202
|
+
filters = _filters(SUPPLIER)
|
|
203
|
+
outcome = report_ops.build_filter_editor_outcome(
|
|
204
|
+
filters, {"filters": [{"fieldPath": "flt_supplier", "fieldType": "MulBasedataField"}]})
|
|
205
|
+
row = {"fieldPath": "flt_supplier", "displayName": "Supplier", "controlType": "FieldAp"}
|
|
206
|
+
proven = report_ops.report_readback_verification(
|
|
207
|
+
"filters", filters, _derived_schema([{**row, "fieldType": "MulBasedataField"}], outcome))
|
|
208
|
+
assert proven["verified"], proven["mismatches"]
|
|
209
|
+
assert proven["filterEditors"]["typedFilterSupport"] == "available"
|
|
210
|
+
wrong = report_ops.report_readback_verification(
|
|
211
|
+
"filters", filters, _derived_schema([{**row, "fieldType": "TextField"}], outcome))
|
|
212
|
+
assert not wrong["verified"]
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def test_read_back_without_a_write_outcome_still_tells_degraded_from_wrong():
|
|
216
|
+
filters = _filters(SUPPLIER)
|
|
217
|
+
row = {"fieldPath": "flt_supplier", "displayName": "Supplier", "controlType": "FieldAp"}
|
|
218
|
+
degraded = report_ops.report_readback_verification(
|
|
219
|
+
"filters", filters, _derived_schema([{**row, "fieldType": "TextField"}]))
|
|
220
|
+
assert degraded["verified"]
|
|
221
|
+
assert degraded["filterEditors"]["typedFilterSupport"] == "unavailable"
|
|
222
|
+
unseen = report_ops.report_readback_verification("filters", filters, _derived_schema([row]))
|
|
223
|
+
assert unseen["verified"] and "fieldType" in unseen["unverifiableRequestedProperties"]
|
|
224
|
+
wrong = report_ops.report_readback_verification(
|
|
225
|
+
"filters", filters, _derived_schema([{**row, "fieldType": "ComboField"}]))
|
|
226
|
+
assert not wrong["verified"]
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def test_a_basedata_editor_is_held_to_its_base_data():
|
|
230
|
+
filters = _filters({**SUPPLIER, "fieldType": "BasedataField", "compare": "="})
|
|
231
|
+
row = {"fieldPath": "flt_supplier", "displayName": "Supplier", "controlType": "FieldAp",
|
|
232
|
+
"fieldType": "BasedataField"}
|
|
233
|
+
assert report_ops.report_readback_verification(
|
|
234
|
+
"filters", filters, _derived_schema([{**row, "referenceNumber": "bd_supplier"}]))["verified"]
|
|
235
|
+
assert not report_ops.report_readback_verification(
|
|
236
|
+
"filters", filters, _derived_schema([{**row, "referenceNumber": "bd_customer"}]))["verified"]
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# ── plugin contract ───────────────────────────────────────────────────────────────────────
|
|
240
|
+
|
|
241
|
+
def test_the_plugin_contract_names_both_editors_and_what_this_report_rendered():
|
|
242
|
+
filters = _filters(SUPPLIER, PERIOD, STATUS)
|
|
243
|
+
editors, _ = report_ops.resolve_filter_editors(
|
|
244
|
+
report_ops.build_filter_editor_outcome(filters), [
|
|
245
|
+
{"fieldPath": "flt_supplier", "fieldType": "TextField"},
|
|
246
|
+
{"fieldPath": "flt_period", "fieldType": "DateRangeField"},
|
|
247
|
+
{"fieldPath": "flt_status", "fieldType": "ComboField"}])
|
|
248
|
+
contract = report_ops.compile_report_plugin_contract(
|
|
249
|
+
{"reportFilters": filters}, "kdtest_rpt", filter_editors=editors)
|
|
250
|
+
supplier, period, status = contract["filterValueContracts"]
|
|
251
|
+
assert supplier["editorFieldType"] == "MulBasedataField"
|
|
252
|
+
assert supplier["valueAccessor"] == 'ReportQueryParam.getFilter().getDynamicObjectCollection("flt_supplier")'
|
|
253
|
+
assert supplier["legacyEditor"]["fieldType"] == "TextField"
|
|
254
|
+
assert supplier["legacyEditor"]["valueAccessor"] == 'ReportQueryParam.getFilter().getString("flt_supplier")'
|
|
255
|
+
assert supplier["editorStatus"] == "degraded" and supplier["renderedFieldType"] == "TextField"
|
|
256
|
+
assert supplier["basedataNumber"] == "bd_supplier" and supplier["multiValue"] is True
|
|
257
|
+
assert period["rangeItemKeys"] == {"start": "flt_period_startdate", "end": "flt_period_enddate"}
|
|
258
|
+
assert "legacyEditor" not in period
|
|
259
|
+
assert status["comboValues"] == ["0", "01"] and status["valueEvidence"] == "measured"
|
|
260
|
+
assert contract["filterEditorSupport"] == "partial"
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def test_a_time_range_editor_reads_its_own_item_keys():
|
|
264
|
+
contract = report_ops.compile_report_plugin_contract({"reportFilters": _filters(
|
|
265
|
+
{"fieldPath": "flt_shift", "displayName": "Shift", "compare": "between",
|
|
266
|
+
"fieldType": "TimeField"},
|
|
267
|
+
{"fieldPath": "flt_stay", "displayName": "Stay", "compare": "between",
|
|
268
|
+
"fieldType": "DateRangeField", "startDateFieldKey": "stay_from", "endDateFieldKey": "stay_to"},
|
|
269
|
+
)}, "kdtest_rpt")
|
|
270
|
+
shift, stay = contract["filterValueContracts"]
|
|
271
|
+
assert shift["editorFieldType"] == "TimeRangeField"
|
|
272
|
+
assert shift["rangeItemKeys"] == {"start": "flt_shift_starttime", "end": "flt_shift_endtime"}
|
|
273
|
+
assert shift["legacyEditor"]["fieldType"] == "DateRangeField"
|
|
274
|
+
assert stay["rangeItemKeys"] == {"start": "stay_from", "end": "stay_to"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import sys
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
SCRIPTS = Path(__file__).resolve().parents[1]
|
|
6
|
+
sys.path.insert(0, str(SCRIPTS))
|
|
7
|
+
from _core import cmd_rules
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_copy_rule_reuses_field_inventory_only_within_each_write():
|
|
11
|
+
rows = {
|
|
12
|
+
"entity": [{"key": "demo", "name": "主实体", "id": "E", "type": "BillEntity"}],
|
|
13
|
+
"field": [
|
|
14
|
+
{"key": "billno", "name": "单据编号", "parentKey": "demo", "type": "TextField", "category": "field"},
|
|
15
|
+
{"key": "workno", "name": "工单编号", "parentKey": "demo", "type": "TextField", "category": "field"},
|
|
16
|
+
],
|
|
17
|
+
}
|
|
18
|
+
with (patch.object(cmd_rules, "do_list_elements", side_effect=lambda *a, **kw: {"status": True, "data": rows[kw["category"]]}) as list_elements,
|
|
19
|
+
patch.object(cmd_rules, "do_load_full_metadata", return_value={"status": True, "data": {"entitymeta": {"Items": []}}}),
|
|
20
|
+
patch.object(cmd_rules, "_ensure_rule_trigger_events", return_value=[]),
|
|
21
|
+
patch.object(cmd_rules, "do_add_rule", return_value={"status": True, "data": {"ruleId": "rule"}}) as write,
|
|
22
|
+
patch.object(cmd_rules, "_attach_rule_readback", side_effect=lambda cfg, token, form, rule, result: {**result, "verification": {"verified": True}})):
|
|
23
|
+
for _ in range(2):
|
|
24
|
+
result = cmd_rules.do_ensure_conditional_copy_field_value(
|
|
25
|
+
{}, "token", "demo", "主实体", "单据编号", "not-empty", None, "单据编号", "工单编号")
|
|
26
|
+
assert result["verification"]["verified"]
|
|
27
|
+
assert write.call_count == 2
|
|
28
|
+
assert [call.kwargs["category"] for call in list_elements.call_args_list] == ["entity", "field", "entity", "field"]
|
|
29
|
+
assert cmd_rules._RULE_BATCH_INVENTORY.get() is None
|
|
@@ -2452,6 +2452,26 @@ class RuleWriteClosureTest(unittest.TestCase):
|
|
|
2452
2452
|
self.assertFalse(repeated["writeAttempted"])
|
|
2453
2453
|
add_verified.assert_not_called()
|
|
2454
2454
|
|
|
2455
|
+
@patch.object(cmd_rules, "do_add_rule_verified")
|
|
2456
|
+
@patch.object(cmd_rules, "do_load_full_metadata")
|
|
2457
|
+
@patch.object(cmd_rules, "do_list_elements")
|
|
2458
|
+
def test_document_number_not_empty_copies_to_work_order(self, list_elements, load_full, add_verified):
|
|
2459
|
+
list_elements.side_effect = lambda *args, **kwargs: {"status": True, "data": (
|
|
2460
|
+
[{"key": "demo", "name": "主实体", "id": "entity-id", "type": "BillEntity"}]
|
|
2461
|
+
if kwargs["category"] == "entity" else [
|
|
2462
|
+
{"key": "billno", "name": "单据编号", "parentKey": "demo", "type": "TextField", "category": "field"},
|
|
2463
|
+
{"key": "work_no", "name": "工单编号", "parentKey": "demo", "type": "TextField", "category": "field"},
|
|
2464
|
+
])}
|
|
2465
|
+
load_full.return_value = {"status": True, "data": {"entitymeta": {"Items": []}}}
|
|
2466
|
+
add_verified.return_value = {"status": True, "data": {"ruleId": "copy"}, "verification": {"verified": True}}
|
|
2467
|
+
result = cmd_rules.do_ensure_conditional_copy_field_value(
|
|
2468
|
+
{}, "token", "demo", "主实体", "单据编号", "not-empty", None, "单据编号", "工单编号")
|
|
2469
|
+
requested = add_verified.call_args.args[3]
|
|
2470
|
+
self.assertEqual("(billno <> null and billno <> '')", requested["preCondition"])
|
|
2471
|
+
self.assertEqual("单据编号 not-empty 时把单据编号复制到工单编号", requested["preDescription"])
|
|
2472
|
+
self.assertEqual([{"TargetField": "work_no", "SrcField": "billno"}], requested["trueActions"][0]["expression"])
|
|
2473
|
+
self.assertTrue(result["verification"]["verified"])
|
|
2474
|
+
|
|
2455
2475
|
@patch.object(cmd_rules, "do_add_rule_verified")
|
|
2456
2476
|
@patch.object(cmd_rules, "do_load_full_metadata")
|
|
2457
2477
|
@patch.object(cmd_rules, "do_get_properties")
|
|
@@ -40,6 +40,24 @@ def test_unknown_root_does_not_fabricate_a_retained_identity(tmp_path):
|
|
|
40
40
|
assert actual == {"ok": False}
|
|
41
41
|
|
|
42
42
|
|
|
43
|
+
def test_known_configuration_difference_reuses_readback_and_unknowns_still_reconcile(tmp_path):
|
|
44
|
+
payload = {"data": {"entityResults": [{"formId": "SAVED", "formNumber": "example"}]}}
|
|
45
|
+
issue = {"type": "combo_options_equal", "target": "mode", "status": "fail",
|
|
46
|
+
"expected": [{"label": "Ready", "value": "ready"}], "actual": [{"label": "Ready", "value": "generated"}]}
|
|
47
|
+
summary = cosmic_api._attach_post_build_phase_failure({}, payload, phase="verify", app_root=str(tmp_path),
|
|
48
|
+
artifact_path=str(tmp_path / "artifact.json"), verification={"summary": {"failed": 1, "errors": 0}, "issues": [issue]})
|
|
49
|
+
assert summary["reconciliation"] == []
|
|
50
|
+
assert summary["repairPlans"][0]["observedIssues"] == [issue]
|
|
51
|
+
assert summary["doNotRetryBuildMeta"] is True
|
|
52
|
+
assert summary["retryable"] is False
|
|
53
|
+
unknown = cosmic_api._attach_post_build_phase_failure({}, payload, phase="verify", app_root=str(tmp_path),
|
|
54
|
+
artifact_path=str(tmp_path / "artifact.json"), verification={"issues": [{**issue, "status": "error", "actual": None}]})
|
|
55
|
+
assert unknown["reconciliation"][0]["command"] == "getFormSchema"
|
|
56
|
+
truncated = cosmic_api._attach_post_build_phase_failure({}, payload, phase="verify", app_root=str(tmp_path),
|
|
57
|
+
artifact_path=str(tmp_path / "artifact.json"), verification={"summary": {"failed": 1, "errors": 1}, "issues": [issue]})
|
|
58
|
+
assert truncated["reconciliation"][0]["command"] == "getFormSchema"
|
|
59
|
+
|
|
60
|
+
|
|
43
61
|
def test_batch_keeps_post_failure_separate_from_verified_and_unwritten_targets(tmp_path):
|
|
44
62
|
saved = {"ok": True, "formNumbers": [{"number": "uhyl_check_in"}]}
|
|
45
63
|
cosmic_api._attach_post_build_phase_failure(
|
|
@@ -31,6 +31,27 @@ def test_compact_verified_targets_exposes_only_successful_exact_assertions():
|
|
|
31
31
|
]
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
def test_verified_property_receipt_names_the_property_and_readback():
|
|
35
|
+
report = SimpleNamespace(results=[SimpleNamespace(status="pass", actual=False,
|
|
36
|
+
spec=SimpleNamespace(type="property_equals", target="field", expected=False,
|
|
37
|
+
metadata={"property": "MustInput"}))])
|
|
38
|
+
assert cosmic_api._compact_verified_targets(report) == [{"type": "property_equals", "target": "field",
|
|
39
|
+
"property": "MustInput", "expected": False, "actual": False}]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_auto_file_summary_retains_verification_details(tmp_path, monkeypatch, capsys):
|
|
43
|
+
from _core import formatters
|
|
44
|
+
monkeypatch.setattr(formatters, "_stdout_mode", False)
|
|
45
|
+
monkeypatch.setattr(formatters, "_output_file", str(tmp_path / "result.json"))
|
|
46
|
+
verification = {"status": "pass", "summary": {"passed": 1}, "verifiedTargets": [
|
|
47
|
+
{"type": "unit_binding_equals", "target": "quantity", "expected": "unit", "actual": "unit"}]}
|
|
48
|
+
formatters.pjson({"results": [{"ok": True, "verification": verification}]})
|
|
49
|
+
import json
|
|
50
|
+
summary = json.loads(capsys.readouterr().out.split("RESULT_SUMMARY=", 1)[1])
|
|
51
|
+
assert summary["results"][0]["verification"] == verification
|
|
52
|
+
assert Path(summary["resultPath"]).is_file()
|
|
53
|
+
|
|
54
|
+
|
|
34
55
|
def test_batch_receipt_persists_raw_verified_results_and_artifact_paths(tmp_path):
|
|
35
56
|
import json
|
|
36
57
|
from _core.formatters import save_run_batch_result
|
|
@@ -46,3 +67,17 @@ def test_batch_receipt_persists_raw_verified_results_and_artifact_paths(tmp_path
|
|
|
46
67
|
second = save_run_batch_result(raw, str(tmp_path / "app"), [str(artifact)])
|
|
47
68
|
assert second["resultPath"] != result["resultPath"]
|
|
48
69
|
assert Path(result["resultPath"]).is_file()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_compact_receipt_preserves_failure_recovery_without_raw_payload(tmp_path):
|
|
73
|
+
from _core.formatters import result_summary
|
|
74
|
+
raw = {"resultPath": str(tmp_path / "original.json"), "failed": 1, "doNotRetryBuildMeta": True,
|
|
75
|
+
"repairPlans": [{"observedIssues": [{"expected": "one", "actual": "other"}]}],
|
|
76
|
+
"results": [{"ok": False, "phase": "verify", "result": {"largeRawPayload": "not needed"},
|
|
77
|
+
"verification": {"status": "fail", "issues": [{"expected": "one", "actual": "other"}]}}]}
|
|
78
|
+
compact = result_summary(raw, raw["resultPath"])
|
|
79
|
+
assert compact["doNotRetryBuildMeta"] is True
|
|
80
|
+
assert compact["repairPlans"] == raw["repairPlans"]
|
|
81
|
+
assert compact["results"][0]["verification"] == raw["results"][0]["verification"]
|
|
82
|
+
assert "result" not in compact["results"][0]
|
|
83
|
+
assert "result" in raw["results"][0]
|
|
@@ -1081,3 +1081,28 @@ def test_layout_verifies_readback_parentage_and_syncs_archive() -> None:
|
|
|
1081
1081
|
assert result["data"]["sourceRelationshipIndependentlyVerified"] is False
|
|
1082
1082
|
assert result["data"]["unitPlacementIndependentlyVerified"] is False
|
|
1083
1083
|
archive_sync.assert_called_once_with("/tmp/app", "uhyl_layout", "Layout")
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
def test_explicit_blank_bill_is_distinct_from_missing_template(tmp_path: Path) -> None:
|
|
1087
|
+
artifact = tmp_path / "bill.json"
|
|
1088
|
+
artifact.write_text(json.dumps({"templateNumber": "blankPage", "entities": [{"type": "BillEntity"}]}), encoding="utf-8")
|
|
1089
|
+
template = cosmic_api._resolve_template_number(None, str(artifact), None)
|
|
1090
|
+
assert template == "blankPage"
|
|
1091
|
+
assert cosmic_api._resolve_template_number("BLANK", str(artifact), None) == "blankPage"
|
|
1092
|
+
assert cosmic_api._artifact_file_supports_blank_template(str(artifact), template)
|
|
1093
|
+
assert not cosmic_api._artifact_file_supports_blank_template(str(artifact), "")
|
|
1094
|
+
assert not cosmic_api._artifact_supports_blank_template({"entities": [{"type": "BaseEntity"}]}, template)
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
@pytest.mark.parametrize("operator", ["empty", "not-empty"])
|
|
1098
|
+
def test_conditional_copy_cli_accepts_empty_operator_without_value(monkeypatch, operator):
|
|
1099
|
+
calls = []
|
|
1100
|
+
monkeypatch.setattr(sys, "argv", ["cosmic_api", "ensureConditionalCopyFieldValue", "--formNumber", "demo", "--condition-field", "source", "--operator", operator, "--source", "source", "--target", "target"])
|
|
1101
|
+
monkeypatch.setattr(cosmic_api, "load_config", lambda: {})
|
|
1102
|
+
monkeypatch.setattr(cosmic_api, "do_auth", lambda cfg: "test")
|
|
1103
|
+
monkeypatch.setattr(cosmic_api, "_bind_rule_domain_target", lambda *args: None)
|
|
1104
|
+
monkeypatch.setattr(cosmic_api, "_assert_form_number_target_owned", lambda *args: None)
|
|
1105
|
+
monkeypatch.setattr(cosmic_api, "do_ensure_conditional_copy_field_value", lambda *args: calls.append(args) or {"status": True})
|
|
1106
|
+
monkeypatch.setattr(cosmic_api, "_attach_bound_form_identity", lambda result, args: result)
|
|
1107
|
+
assert cosmic_api.main() == 0
|
|
1108
|
+
assert calls[0][5:7] == (operator, None)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from unittest.mock import patch
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
6
|
+
import pytest
|
|
7
|
+
from _core import cmd_rules
|
|
8
|
+
|
|
9
|
+
ROWS = [{"key":"hours", "type":"TimeRangeField", "parentKey":"demo"}]
|
|
10
|
+
META = {"status":True, "data":{"entitymeta":{"Key":"demo", "Items":[
|
|
11
|
+
{"_Type_":"TimeRangeField", "Key":"hours", "StartDateFieldKey":"begin", "EndDateFieldKey":"finish"},
|
|
12
|
+
{"_Type_":"BizRule", "Id":"r1"},
|
|
13
|
+
]}}}
|
|
14
|
+
|
|
15
|
+
def rule(value="09:00-18:00"):
|
|
16
|
+
return {"ruleType":"bizRule", "mountPoint":"demo", "trueActions":[{"actionType":"FormulaAction", "expression":f"hours = '{value}'", "ret":1}], "falseActions":[]}
|
|
17
|
+
|
|
18
|
+
def test_compile_range_uses_metadata_endpoint_keys():
|
|
19
|
+
with patch.object(cmd_rules, "do_list_elements", return_value={"status":True,"data":ROWS}), patch.object(cmd_rules, "do_load_full_metadata", return_value=META):
|
|
20
|
+
got, virtual = cmd_rules._compile_time_range_actions({}, "token", "demo", rule())
|
|
21
|
+
assert [a["expression"] for a in got["trueActions"]] == ["begin = 32400", "finish = 64800"]
|
|
22
|
+
assert [a["seq"] for a in got["trueActions"]] == [1,2]
|
|
23
|
+
assert [a["key"] for a in virtual] == ["begin","finish"]
|
|
24
|
+
with patch.object(cmd_rules, "do_list_elements", return_value={"status":True,"data":ROWS}):
|
|
25
|
+
assert cmd_rules._validate_formula_action_scope({}, "token", "demo", got, virtual_fields=virtual) == got
|
|
26
|
+
|
|
27
|
+
def test_compile_range_preserves_original_and_finds_update_owner():
|
|
28
|
+
original = rule()
|
|
29
|
+
original.pop("mountPoint")
|
|
30
|
+
original["ruleId"] = "r1"
|
|
31
|
+
original["trueActions"][0]["id"] = "a1"
|
|
32
|
+
with patch.object(cmd_rules, "do_list_elements", return_value={"status":True,"data":ROWS}), patch.object(cmd_rules, "do_load_full_metadata", return_value=META):
|
|
33
|
+
got, _ = cmd_rules._compile_time_range_actions({}, "token", "demo", original)
|
|
34
|
+
assert got["trueActions"][0]["id"] == "a1"
|
|
35
|
+
assert "id" not in got["trueActions"][1]
|
|
36
|
+
assert original["trueActions"][0]["expression"] == "hours = '09:00-18:00'"
|
|
37
|
+
|
|
38
|
+
@pytest.mark.parametrize("value", ["25:00-18:00","09:60-18:00","18:00-09:00"])
|
|
39
|
+
def test_invalid_clock_does_not_compile(value):
|
|
40
|
+
with patch.object(cmd_rules, "do_list_elements", return_value={"status":True,"data":ROWS}), patch.object(cmd_rules, "do_load_full_metadata", return_value=META), pytest.raises(ValueError):
|
|
41
|
+
cmd_rules._compile_time_range_actions({}, "token", "demo", rule(value))
|
|
42
|
+
|
|
43
|
+
def test_ordinary_text_range_is_untouched():
|
|
44
|
+
with patch.object(cmd_rules, "do_list_elements", return_value={"status":True,"data":[{"key":"hours","type":"TextField"}]}), patch.object(cmd_rules, "do_load_full_metadata") as read:
|
|
45
|
+
got, virtual = cmd_rules._compile_time_range_actions({}, "token", "demo", rule())
|
|
46
|
+
assert got == rule()
|
|
47
|
+
assert not virtual
|
|
48
|
+
read.assert_not_called()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_verified_update_sends_encoded_actions():
|
|
52
|
+
request = rule()
|
|
53
|
+
request.pop("mountPoint")
|
|
54
|
+
request["ruleId"] = "r1"
|
|
55
|
+
with (patch.object(cmd_rules, "do_list_elements", return_value={"status": True, "data": ROWS}),
|
|
56
|
+
patch.object(cmd_rules, "do_load_full_metadata", return_value=META),
|
|
57
|
+
patch.object(cmd_rules, "_ensure_rule_trigger_events", return_value=[]),
|
|
58
|
+
patch.object(cmd_rules, "do_update_rule", return_value={"status": True}) as write,
|
|
59
|
+
patch.object(cmd_rules, "_attach_rule_readback", side_effect=lambda *a, **k: {"status": True, "verification": {"verified": True}})):
|
|
60
|
+
result = cmd_rules.do_update_rule_verified({}, "token", "demo", request)
|
|
61
|
+
assert result["verification"]["verified"]
|
|
62
|
+
assert [a["expression"] for a in write.call_args.args[3]["trueActions"]] == ["begin = 32400", "finish = 64800"]
|
|
63
|
+
|
|
64
|
+
def test_missing_endpoint_rejects_before_write():
|
|
65
|
+
metadata = {"status": True, "data": {"Items": [{"_Type_": "TimeRangeField", "Key": "hours", "StartDateFieldKey": "begin"}]}}
|
|
66
|
+
with (patch.object(cmd_rules, "do_list_elements", return_value={"status": True, "data": ROWS}),
|
|
67
|
+
patch.object(cmd_rules, "do_load_full_metadata", return_value=metadata),
|
|
68
|
+
patch.object(cmd_rules, "do_add_rule") as write,
|
|
69
|
+
patch.object(cmd_rules, "_resolve_rule_entity", side_effect=lambda *a: a[3])):
|
|
70
|
+
request = {**rule(), "description": "test", "preDescription": "test", "preCondition": "1=1", "itemId": "entity"}
|
|
71
|
+
with pytest.raises(ValueError, match="TIME_RANGE_METADATA_AMBIGUOUS"):
|
|
72
|
+
cmd_rules.do_add_rule_verified({}, "token", "demo", request)
|
|
73
|
+
write.assert_not_called()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_existing_same_entity_endpoint_with_extra_metadata_is_accepted():
|
|
77
|
+
original = rule()
|
|
78
|
+
with patch.object(cmd_rules, "do_list_elements", return_value={"status": True, "data": ROWS}), patch.object(cmd_rules, "do_load_full_metadata", return_value=META):
|
|
79
|
+
compiled, virtual = cmd_rules._compile_time_range_actions({}, "token", "demo", original)
|
|
80
|
+
actual_rows = ROWS + [{"key": "begin", "type": "TimeField", "parentKey": "demo", "id": "start-id", "name": "开始"}]
|
|
81
|
+
with patch.object(cmd_rules, "do_list_elements", return_value={"status": True, "data": actual_rows}):
|
|
82
|
+
assert cmd_rules._validate_formula_action_scope({}, "token", "demo", compiled, virtual_fields=virtual) == compiled
|
|
83
|
+
actual_rows[1]["parentKey"] = "other"
|
|
84
|
+
with patch.object(cmd_rules, "do_list_elements", return_value={"status": True, "data": actual_rows}), pytest.raises(ValueError, match="TIME_RANGE_ENDPOINT_COLLISION"):
|
|
85
|
+
cmd_rules._validate_formula_action_scope({}, "token", "demo", compiled, virtual_fields=virtual)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_update_rejects_endpoint_in_other_entity_before_write():
|
|
89
|
+
request = rule()
|
|
90
|
+
request.pop("mountPoint")
|
|
91
|
+
request["ruleId"] = "r1"
|
|
92
|
+
rows = ROWS + [{"key": "begin", "type": "TimeField", "parentKey": "other"}]
|
|
93
|
+
with (patch.object(cmd_rules, "do_list_elements", return_value={"status": True, "data": rows}),
|
|
94
|
+
patch.object(cmd_rules, "do_load_full_metadata", return_value=META),
|
|
95
|
+
patch.object(cmd_rules, "do_update_rule") as write,
|
|
96
|
+
pytest.raises(ValueError, match="TIME_RANGE_ENDPOINT_COLLISION")):
|
|
97
|
+
cmd_rules.do_update_rule_verified({}, "token", "demo", request)
|
|
98
|
+
write.assert_not_called()
|