@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
|
@@ -45,6 +45,7 @@ def _readback(
|
|
|
45
45
|
"menu": {
|
|
46
46
|
"menuId": "MENU-ID",
|
|
47
47
|
"name": "测试菜单",
|
|
48
|
+
"description": "测试菜单",
|
|
48
49
|
"menuType": entity_type,
|
|
49
50
|
"formNumber": form_number,
|
|
50
51
|
"openType": "MainNewTabPage",
|
|
@@ -73,6 +74,21 @@ def test_add_menu_defaults_bill_to_list_and_verifies_readback() -> None:
|
|
|
73
74
|
assert result["data"]["verified"] is True
|
|
74
75
|
add_payload = api.call_args_list[1].args[3]
|
|
75
76
|
assert add_payload["parameterType"] == "ListShowParameter"
|
|
77
|
+
assert add_payload["description"] == "测试菜单"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_add_menu_preserves_explicit_description_and_refuses_null_readback():
|
|
81
|
+
for actual, verified in (("说明", True), (None, False)):
|
|
82
|
+
readback = _readback(None, form_number=None)
|
|
83
|
+
readback["data"]["menu"]["description"] = actual
|
|
84
|
+
with mock.patch.object(APPS, "_api_call", side_effect=[_write(), readback]) as api:
|
|
85
|
+
result = APPS.do_add_menu({}, "token", "APP-ID", "测试菜单",
|
|
86
|
+
menu_type="category", description="说明")
|
|
87
|
+
assert api.call_args_list[0].args[3]["description"] == "说明"
|
|
88
|
+
assert result["status"] is verified
|
|
89
|
+
if not verified:
|
|
90
|
+
assert result["errorCode"] == "MENU_WRITE_VERIFY_FAILED"
|
|
91
|
+
assert any("description" in error for error in result["data"]["errors"])
|
|
76
92
|
|
|
77
93
|
|
|
78
94
|
def test_add_menu_defaults_non_list_form_to_form_entry() -> None:
|
|
@@ -139,10 +139,24 @@ def test_failed_child_query_preserves_underlying_error(tmp_path, capsys):
|
|
|
139
139
|
"print(json.dumps({'status': False, 'errorCode': 'APP_ROOT_IDENTITY_INVALID'}))\n"
|
|
140
140
|
"sys.exit(2)\n"
|
|
141
141
|
)
|
|
142
|
-
assert emitter._run_cosmic_api(str(script), "queryPublicTemplates", {}, app_root=tmp_path)
|
|
142
|
+
assert emitter._run_cosmic_api(str(script), "queryPublicTemplates", {}, app_root=tmp_path)["errorCode"] == "APP_ROOT_IDENTITY_INVALID"
|
|
143
143
|
assert "APP_ROOT_IDENTITY_INVALID" in capsys.readouterr().err
|
|
144
144
|
|
|
145
145
|
|
|
146
|
+
def test_template_lookup_stops_at_environment_failure_without_mislabeling_input(monkeypatch, tmp_path):
|
|
147
|
+
calls = []
|
|
148
|
+
def read(*args, **kwargs):
|
|
149
|
+
calls.append(args)
|
|
150
|
+
return {"status": False, "errorCategory": "environment", "errorCode": "SKILL_REVISION_MISMATCH",
|
|
151
|
+
"message": "Use the current installed source"}
|
|
152
|
+
monkeypatch.setattr(emitter, "_run_cosmic_api", read)
|
|
153
|
+
logs = emitter._Logs(tmp_path)
|
|
154
|
+
section = {"artifact": {"entities": [{"type": "BillEntity", "bizName": "request", "templateKey": "bos_billtpl"}]}}
|
|
155
|
+
assert emitter._validate_template_keys(section, logs, "cosmic_api.py", app_root=tmp_path) is False
|
|
156
|
+
assert len(calls) == 1
|
|
157
|
+
assert logs.error_details[0]["code"] == "SKILL_REVISION_MISMATCH"
|
|
158
|
+
|
|
159
|
+
|
|
146
160
|
def test_template_query_normalizes_legacy_model_type(monkeypatch):
|
|
147
161
|
calls = []
|
|
148
162
|
monkeypatch.setattr(cmd_forms, "_api_call", lambda *args, **kwargs: calls.append(args) or {
|
|
@@ -154,6 +168,21 @@ def test_template_query_normalizes_legacy_model_type(monkeypatch):
|
|
|
154
168
|
assert calls[1][3] == {"modelType": "BaseFormModel", "bizAppNumber": "demo"}
|
|
155
169
|
|
|
156
170
|
|
|
171
|
+
def test_compiler_forwards_before_generating_files(tmp_path, monkeypatch, capfd):
|
|
172
|
+
import execution_binding
|
|
173
|
+
selected = tmp_path / "selected compiler.py"
|
|
174
|
+
selected.write_text('import json,sys\nprint(json.dumps({"argv":sys.argv[1:]}))\n')
|
|
175
|
+
source = tmp_path / "request.json"
|
|
176
|
+
source.write_text('{"artifact":{"entities":[]}}')
|
|
177
|
+
original = source.read_bytes()
|
|
178
|
+
arguments = ["--app-root", str(tmp_path), "--authoring-file", str(source)]
|
|
179
|
+
monkeypatch.setattr(execution_binding, "bound_entrypoint", lambda *args: selected)
|
|
180
|
+
assert emitter.main(arguments) == 0
|
|
181
|
+
assert json.loads(capfd.readouterr().out)["argv"] == arguments
|
|
182
|
+
assert source.read_bytes() == original
|
|
183
|
+
assert not (tmp_path / "_runtime").exists()
|
|
184
|
+
|
|
185
|
+
|
|
157
186
|
@pytest.mark.parametrize("kind,expected", [("BillEntity", "bos_billtpl"), ("BaseEntity", "bos_basetpl"), ("MainEntity", None), ("EntryEntity", None)])
|
|
158
187
|
def test_default_template_only_for_entity_roots(kind, expected):
|
|
159
188
|
data = {"artifact": {"entities": [{"type": kind}]}}
|
|
@@ -246,3 +275,28 @@ def test_builtin_carry_uses_declared_target_without_overwriting_explicit_referen
|
|
|
246
275
|
assert builtin["reference"] == {"mode": "platform", "formNumber": "bd_customer"}
|
|
247
276
|
assert explicit["reference"]["formNumber"] == "user_selected"
|
|
248
277
|
assert "reference" not in plain
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def test_explicit_blank_bill_preserves_fields_and_emits_blank_template(monkeypatch, tmp_path):
|
|
281
|
+
entity = {"type": "BillEntity", "bizName": "blankbill", "displayName": "Blank bill", "templateKey": "blankPage", "fields": [
|
|
282
|
+
{"bizWord": "description", "displayName": "Description", "dataType": "TextField"},
|
|
283
|
+
]}
|
|
284
|
+
section = {"artifact": {"entities": [entity]}, "assumptions": []}
|
|
285
|
+
(tmp_path / "archive.json").write_text(json.dumps({"isvNumber": "demo", "appCode": "sample", "standard": False}))
|
|
286
|
+
monkeypatch.setattr(emitter, "_run_cosmic_api", lambda *args, **kwargs: {
|
|
287
|
+
"status": True, "data": {"success": True, "templates": [{"number": "blankPage", "name": "空白页面", "modelType": "BillFormModel"}]},
|
|
288
|
+
})
|
|
289
|
+
logs = emitter._Logs(tmp_path)
|
|
290
|
+
assert emitter._validate_template_keys(section, logs, "unused", app_root=tmp_path)
|
|
291
|
+
with patch.object(emitter, "_call_get_entity_fields") as get_preset:
|
|
292
|
+
emitter._merge_template_fields(section, logs, "unused", app_root=tmp_path)
|
|
293
|
+
get_preset.assert_not_called()
|
|
294
|
+
assert entity["fields"][0]["bizWord"] == "description"
|
|
295
|
+
files, manifest, _ = emitter.assemble_artifact_emit(section, tmp_path, "blank-bill", logs)
|
|
296
|
+
assert not logs.errors
|
|
297
|
+
artifact = json.loads(files[0].read_text())
|
|
298
|
+
assert artifact["templateNumber"] == "blankPage"
|
|
299
|
+
assert manifest["artifacts"][0]["templateNumber"] == "blankPage"
|
|
300
|
+
assert len(artifact["entities"][0]["fields"]) == 1
|
|
301
|
+
assert cli._resolve_template_number(None, str(files[0]), None) == "blankPage"
|
|
302
|
+
assert cli._artifact_supports_blank_template(artifact, "blankPage")
|
|
@@ -69,7 +69,7 @@ def test_remove_field_publishes_mutually_exclusive_single_and_batch_selectors():
|
|
|
69
69
|
def test_add_field_batch_validate_only_runs_without_platform_access(tmp_path):
|
|
70
70
|
source = tmp_path / "fields.json"
|
|
71
71
|
source.write_text(json.dumps([
|
|
72
|
-
{"name": "说明", "bizWord": "note"},
|
|
72
|
+
{"name": "说明", "bizWord": "note", "fieldType": "TextField"},
|
|
73
73
|
{"name": "来源", "bizWord": "source", "fieldType": "ComboField",
|
|
74
74
|
"comboOptions": [{"label": "线上", "value": "online"}, {"label": "电话", "value": "phone"}]},
|
|
75
75
|
]), encoding="utf-8")
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Replay task02's real source through the native compiler before registration."""
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
SCRIPTS = Path(__file__).resolve().parents[1]
|
|
11
|
+
sys.path.insert(0, str(SCRIPTS))
|
|
12
|
+
from compile_plugin_source import operation_field_issues
|
|
13
|
+
|
|
14
|
+
SOURCE = Path(__file__).with_name("fixtures") / "operation-entry-field-prepare.java"
|
|
15
|
+
CONTEXT = {"formGraph": {"entities": [{"role": "entries", "collectionProperty": "uhyl_book_order_detail",
|
|
16
|
+
"fields": [{"fieldKey": "uhyl_start_time"}, {"fieldKey": "uhyl_end_time"}]}]}}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_real_batch_failure_identifies_both_missing_fields():
|
|
20
|
+
issues = operation_field_issues(SOURCE.read_text(), CONTEXT)
|
|
21
|
+
assert {item["fieldKey"] for item in issues} == {"uhyl_start_time", "uhyl_end_time"}
|
|
22
|
+
assert all(item["collectionProperty"] == "uhyl_book_order_detail" for item in issues)
|
|
23
|
+
assert all(item["line"] > 50 for item in issues)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.mark.parametrize("change", [
|
|
27
|
+
lambda text: text.replace("fieldKeys.add(ENTRY_COLLECTION);", "fieldKeys.add(FIELD_START_TIME); fieldKeys.add(FIELD_END_TIME);"),
|
|
28
|
+
lambda text: text.replace("fieldKeys.add(ENTRY_COLLECTION);", 'fieldKeys.add("uhyl_start_time"); fieldKeys.add("uhyl_end_time");'),
|
|
29
|
+
lambda text: text.replace("fieldKeys.add(ENTRY_COLLECTION);", 'fieldKeys.add("uhyl_book_order_detail.uhyl_start_time"); fieldKeys.add("uhyl_book_order_detail.uhyl_end_time");'),
|
|
30
|
+
lambda text: text.replace("extends AbstractOperationServicePlugIn", "extends ProjectBase"),
|
|
31
|
+
lambda text: text.replace("fieldKeys.add(ENTRY_COLLECTION);", "prepareFields(e);"),
|
|
32
|
+
lambda text: text.replace("getDynamicObjectCollection(ENTRY_COLLECTION)", 'getDynamicObjectCollection("unrelated")'),
|
|
33
|
+
lambda text: text.replace("fieldKeys.add(ENTRY_COLLECTION);", "fieldKeys.addAll(requiredKeys());"),
|
|
34
|
+
lambda text: text.replace("entity.getDataEntity()", "BusinessDataServiceHelper.loadSingle(id)"),
|
|
35
|
+
])
|
|
36
|
+
def test_correct_or_unresolved_data_flow_is_not_falsely_blocked(change):
|
|
37
|
+
assert operation_field_issues(change(SOURCE.read_text()), CONTEXT) == []
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_comments_do_not_prepare_fields_and_absent_metadata_is_not_invented():
|
|
41
|
+
source = SOURCE.read_text().replace("fieldKeys.add(ENTRY_COLLECTION);", "fieldKeys.add(ENTRY_COLLECTION); // fieldKeys.add(FIELD_START_TIME);")
|
|
42
|
+
assert len(operation_field_issues(source, CONTEXT)) == 2
|
|
43
|
+
assert operation_field_issues(source, None) == []
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_single_and_batch_native_compilation_reject_before_archiving(tmp_path):
|
|
47
|
+
module = tmp_path / "module"
|
|
48
|
+
source = module.joinpath("src", "main", "java", "kd", "bos", "app", "uhyl", "bfy11", "operate",
|
|
49
|
+
"UhylBookingOrderOpPlugin.java")
|
|
50
|
+
source.parent.mkdir(parents=True)
|
|
51
|
+
source.write_text(SOURCE.read_text())
|
|
52
|
+
app = module / "app/uhyl_bfy11"
|
|
53
|
+
app.mkdir(parents=True)
|
|
54
|
+
handoff = tmp_path / "handoff.json"
|
|
55
|
+
handoff.write_text(json.dumps({"status": True, "handoff": {**CONTEXT, "projectRoot": str(module),
|
|
56
|
+
"appRoot": str(app), "sourcePath": str(source), "className": source.stem, "type": "operation",
|
|
57
|
+
"formNumber": "uhyl_booking_order", "description": "提交时段校验", "operationKeys": ["submit"]}}))
|
|
58
|
+
config = tmp_path / "appconfig.json"
|
|
59
|
+
config.write_text('{"compile": false}')
|
|
60
|
+
env = {**os.environ, "KD_APPCONFIG": str(config)}
|
|
61
|
+
single = [sys.executable, str(SCRIPTS / "compile_plugin_source.py"), "--handoff", str(handoff)]
|
|
62
|
+
batch_file = tmp_path / "sources.json"
|
|
63
|
+
batch_file.write_text(json.dumps([{"handoffPath": str(handoff)}]))
|
|
64
|
+
batch = [sys.executable, str(SCRIPTS / "compile_plugin_source.py"), "--project-root", str(module),
|
|
65
|
+
"--app-root", str(app), "--sources-file", str(batch_file)]
|
|
66
|
+
for args in (single, batch):
|
|
67
|
+
run = subprocess.run(args, env=env, capture_output=True, text=True)
|
|
68
|
+
result = json.loads(run.stdout)
|
|
69
|
+
outcome = result["results"][0] if "results" in result else result
|
|
70
|
+
assert run.returncode != 0
|
|
71
|
+
assert outcome["errorCode"] == "PLUGIN_SOURCE_OPERATION_FIELD_NOT_PREPARED"
|
|
72
|
+
assert outcome["phase"] == "source_validation"
|
|
73
|
+
assert not (app / "archive.json").exists()
|
|
74
|
+
source.write_text(SOURCE.read_text().replace("fieldKeys.add(ENTRY_COLLECTION);",
|
|
75
|
+
"fieldKeys.add(ENTRY_COLLECTION); fieldKeys.add(FIELD_START_TIME); fieldKeys.add(FIELD_END_TIME);"))
|
|
76
|
+
run = subprocess.run(single, env=env, capture_output=True, text=True)
|
|
77
|
+
result = json.loads(run.stdout)
|
|
78
|
+
assert run.returncode == 0, result
|
|
79
|
+
assert result["compileSkipped"] is True # source-only proof, not a business pass
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_validator_preparation_is_not_mistaken_for_an_omission():
|
|
83
|
+
source = SOURCE.read_text().replace("public void validate() {", "public java.util.Set<String> preparePropertys() { return requiredFields(); } public void validate() {")
|
|
84
|
+
assert operation_field_issues(source, CONTEXT) == []
|
|
@@ -7,6 +7,7 @@ cases are as load-bearing as the positive ones.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import json
|
|
10
11
|
import re
|
|
11
12
|
import sys
|
|
12
13
|
from pathlib import Path
|
|
@@ -16,13 +17,97 @@ sys.path.insert(0, str(SCRIPTS))
|
|
|
16
17
|
|
|
17
18
|
from platform_api_contract import ( # noqa: E402
|
|
18
19
|
_page, _qcp_constants, _scenes_verified_imports, base_class_issues,
|
|
19
|
-
filter_operator_issues, override_signature_issues, return_type_issues,
|
|
20
|
+
filter_operator_issues, override_signature_issues, return_type_issues, operation_call_findings,
|
|
20
21
|
unverified_import_issues, unverified_member_issues,
|
|
21
22
|
describe_api, plugin_coding_evidence, scene_misuse_issues,
|
|
23
|
+
forbidden_member_issues, report_batch_issues, report_listener_issues,
|
|
24
|
+
report_source_cast_warnings, sdk_absent_name_warnings,
|
|
22
25
|
)
|
|
23
26
|
sys.path.append(str(SCRIPTS.parents[2] / "scripts" if SCRIPTS.parent.parent.name == ".locales" else SCRIPTS))
|
|
24
27
|
|
|
25
28
|
|
|
29
|
+
def test_operation_argument_roles_use_entity_evidence_not_identifier_spelling():
|
|
30
|
+
source = '''
|
|
31
|
+
import kd.bos.servicehelper.BusinessDataServiceHelper;
|
|
32
|
+
import kd.bos.servicehelper.operation.OperationServiceHelper;
|
|
33
|
+
class P {
|
|
34
|
+
static final String FLOCK_ENTITY = "demo_flock";
|
|
35
|
+
static final String DELETE = "delete";
|
|
36
|
+
void run(Object id) {
|
|
37
|
+
Object flk = BusinessDataServiceHelper.loadSingle(id, FLOCK_ENTITY);
|
|
38
|
+
OperationServiceHelper.executeOperate((FLOCK_ENTITY), DELETE, new Object[]{id}, option());
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
'''
|
|
42
|
+
issues, warnings = operation_call_findings(source)
|
|
43
|
+
assert [item["errorCode"] for item in issues] == ["PLUGIN_SOURCE_OPERATION_ARGUMENT_ORDER"]
|
|
44
|
+
assert issues[0]["line"] == 9
|
|
45
|
+
assert len(issues[0]["registeredSignatures"]) == 2
|
|
46
|
+
assert warnings == []
|
|
47
|
+
assert operation_call_findings(source.replace('(FLOCK_ENTITY), DELETE', 'DELETE, FLOCK_ENTITY')) == ([], [])
|
|
48
|
+
# 没有实体证据时只提示,不将合法自定义操作或变量名推断为错误。
|
|
49
|
+
unknown = source.replace('Object flk = BusinessDataServiceHelper.loadSingle(id, FLOCK_ENTITY);', '')
|
|
50
|
+
issues, warnings = operation_call_findings(unknown)
|
|
51
|
+
assert issues == []
|
|
52
|
+
assert warnings[0]["errorCode"] == "PLUGIN_SOURCE_OPERATION_ARGUMENT_ORDER_UNVERIFIED"
|
|
53
|
+
assert operation_call_findings(unknown, ("demo_flock",))[0]
|
|
54
|
+
assert operation_call_findings(source.replace('"delete"', '"custom_delete"')) == ([], [])
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_operation_overloads_do_not_borrow_signatures_from_a_different_helper():
|
|
58
|
+
source = 'import kd.bos.servicehelper.operation.OperationServiceHelper;\n'
|
|
59
|
+
issues, warnings = operation_call_findings(source + 'OperationServiceHelper.executeOperate("delete", "demo_flock", new Object[]{id});')
|
|
60
|
+
assert issues == []
|
|
61
|
+
assert warnings[0]["argumentCount"] == 3
|
|
62
|
+
assert warnings[0]["errorCode"] == "PLUGIN_SOURCE_OPERATION_OVERLOAD_UNVERIFIED"
|
|
63
|
+
assert operation_call_findings(source + 'OperationServiceHelper.executeOperate("delete", "demo_flock", new Object[]{id}, option);') == ([], [])
|
|
64
|
+
# DeleteServiceHelper 确有 (entityName, pks, option) 三参数签名,不能一律要求四参数。
|
|
65
|
+
assert operation_call_findings('import kd.bos.servicehelper.operation.DeleteServiceHelper;\n'
|
|
66
|
+
'DeleteServiceHelper.deleteOperate("demo_flock", new Object[]{id}, option);') == ([], [])
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_operation_scan_resolves_qualified_and_static_import_calls():
|
|
70
|
+
for call, imports in (
|
|
71
|
+
('kd.bos.servicehelper.operation.OperationServiceHelper.executeOperate', ''),
|
|
72
|
+
('executeOperate', 'import static kd.bos.servicehelper.operation.OperationServiceHelper.executeOperate;\n'),
|
|
73
|
+
('DeleteServiceHelper.deleteOperate', 'import kd.bos.servicehelper.operation.DeleteServiceHelper;\n'),
|
|
74
|
+
):
|
|
75
|
+
source = imports + call + '("demo_flock", "delete", new Object[]{id}, option);'
|
|
76
|
+
issues, warnings = operation_call_findings(source, ("demo_flock",))
|
|
77
|
+
assert len(issues) == 1
|
|
78
|
+
assert warnings == []
|
|
79
|
+
local = '''import static kd.bos.servicehelper.operation.OperationServiceHelper.executeOperate;
|
|
80
|
+
class P {
|
|
81
|
+
void executeOperate(String entity, String op, Object[] ids, Object option) { use(ids); }
|
|
82
|
+
void run() { executeOperate("demo_flock", "delete", ids, option); }
|
|
83
|
+
}'''
|
|
84
|
+
assert operation_call_findings(local, ("demo_flock",)) == ([], [])
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_operation_scan_ignores_literals_comments_other_receivers_and_shadowed_constants():
|
|
88
|
+
source = '''import kd.bos.servicehelper.operation.OperationServiceHelper;
|
|
89
|
+
class P {
|
|
90
|
+
final String ENTITY = "demo_flock";
|
|
91
|
+
void run(String ENTITY) { OperationServiceHelper.executeOperate(ENTITY, "delete", ids, option); }
|
|
92
|
+
String note = "OperationServiceHelper.executeOperate(ENTITY, delete, ids)";
|
|
93
|
+
// OperationServiceHelper.executeOperate("demo_flock", "delete", ids);
|
|
94
|
+
}'''
|
|
95
|
+
assert operation_call_findings(source, ("demo_flock",)) == ([], [])
|
|
96
|
+
assert operation_call_findings('import demo.OperationServiceHelper;\n'
|
|
97
|
+
'OperationServiceHelper.executeOperate("demo_flock", "delete", ids);', ("demo_flock",)) == ([], [])
|
|
98
|
+
assert operation_call_findings('import kd.bos.servicehelper.operation.OperationServiceHelper;\n'
|
|
99
|
+
'OperationServiceHelper.executeOperate(operation, entity, ids, option);') == ([], [])
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_operation_scenes_pass_the_new_call_contract():
|
|
103
|
+
platform = SCRIPTS.parent / "knowledge" / "cosmic" / "platform"
|
|
104
|
+
for name in ("operation/execute-operate.md", "operation/operation-custom-params.md",
|
|
105
|
+
"operation/operation-microservice.md", "data-access/business-data/delete-service.md"):
|
|
106
|
+
text = (platform / "scenes" / name).read_text(encoding="utf-8-sig")
|
|
107
|
+
for block in re.findall(r"```java\n(.*?)```", text, re.DOTALL):
|
|
108
|
+
assert operation_call_findings(block) == ([], []), name
|
|
109
|
+
|
|
110
|
+
|
|
26
111
|
def test_api_lookup_distinguishes_same_named_members_by_receiver():
|
|
27
112
|
plugin = describe_api("AbstractOperationServicePlugIn", ["getDataEntities"])
|
|
28
113
|
validator = describe_api("AbstractValidator", ["getDataEntities"])
|
|
@@ -984,3 +1069,239 @@ def test_scene_evidence_cannot_read_outside_registered_scene_tree():
|
|
|
984
1069
|
import pytest
|
|
985
1070
|
with pytest.raises(ValueError, match="unknown coding scene"):
|
|
986
1071
|
plugin_coding_evidence("operation", ["../../../../references/type-registry.json"])
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
def _report_form(body: str, imports: str = "") -> str:
|
|
1075
|
+
return (
|
|
1076
|
+
"package demo;\n"
|
|
1077
|
+
"import java.util.EventObject;\n"
|
|
1078
|
+
"import kd.bos.form.control.events.ItemClickEvent;\n"
|
|
1079
|
+
"import kd.bos.form.events.HyperLinkClickEvent;\n"
|
|
1080
|
+
"import kd.bos.form.events.HyperLinkClickListener;\n"
|
|
1081
|
+
"import kd.bos.report.ReportList;\n"
|
|
1082
|
+
"import kd.bos.report.plugin.AbstractReportFormPlugin;\n"
|
|
1083
|
+
f"{imports}"
|
|
1084
|
+
"public class DrillPlugin extends AbstractReportFormPlugin implements HyperLinkClickListener {\n"
|
|
1085
|
+
f"{body}"
|
|
1086
|
+
"}\n"
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
_DRILL = (
|
|
1091
|
+
" @Override\n"
|
|
1092
|
+
" public void hyperLinkClick(HyperLinkClickEvent evt) {\n"
|
|
1093
|
+
" String field = evt.getFieldName();\n"
|
|
1094
|
+
" }\n"
|
|
1095
|
+
)
|
|
1096
|
+
_REGISTER_DRILL = (
|
|
1097
|
+
" @Override\n"
|
|
1098
|
+
" public void registerListener(EventObject e) {\n"
|
|
1099
|
+
" super.registerListener(e);\n"
|
|
1100
|
+
" ReportList reportList = this.getControl(\"reportlistap\");\n"
|
|
1101
|
+
" reportList.addHyperClickListener(this);\n"
|
|
1102
|
+
" }\n"
|
|
1103
|
+
)
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
def test_a_report_drill_down_without_its_listener_is_rejected():
|
|
1107
|
+
"""Verified on the 8.0 bytecode: nothing adds a report form plugin to the grid's hyperlink
|
|
1108
|
+
listeners but addHyperClickListener, so this compiles, opens and the link does nothing."""
|
|
1109
|
+
issues = report_listener_issues(_report_form(_DRILL))
|
|
1110
|
+
|
|
1111
|
+
assert len(issues) == 1
|
|
1112
|
+
assert issues[0]["callback"] == "hyperLinkClick"
|
|
1113
|
+
assert issues[0]["requiredCall"] == "addHyperClickListener"
|
|
1114
|
+
assert issues[0]["line"] == 10
|
|
1115
|
+
assert "registerListener" in issues[0]["message"]
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
def test_a_report_drill_down_that_registers_itself_is_accepted():
|
|
1119
|
+
assert report_listener_issues(_report_form(_REGISTER_DRILL + _DRILL)) == []
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
def test_toolbar_and_cell_callbacks_each_need_their_own_listener():
|
|
1123
|
+
issues = report_listener_issues(_report_form(
|
|
1124
|
+
_REGISTER_DRILL + _DRILL
|
|
1125
|
+
+ " @Override\n"
|
|
1126
|
+
" public void itemClick(ItemClickEvent evt) { super.itemClick(evt); }\n"
|
|
1127
|
+
" public void cellClick(CellClickEvent evt) { }\n",
|
|
1128
|
+
imports="import kd.bos.form.control.events.CellClickEvent;\n"))
|
|
1129
|
+
|
|
1130
|
+
assert [(issue["callback"], issue["requiredCall"]) for issue in issues] == [
|
|
1131
|
+
("itemClick", "addItemClickListeners"), ("cellClick", "addCellClickListener")]
|
|
1132
|
+
assert report_listener_issues(_report_form(
|
|
1133
|
+
" @Override\n"
|
|
1134
|
+
" public void registerListener(EventObject e) {\n"
|
|
1135
|
+
" this.addItemClickListeners(\"toolbarap\");\n"
|
|
1136
|
+
" }\n"
|
|
1137
|
+
" @Override\n"
|
|
1138
|
+
" public void itemClick(ItemClickEvent evt) { super.itemClick(evt); }\n")) == []
|
|
1139
|
+
|
|
1140
|
+
|
|
1141
|
+
def test_the_listener_rule_judges_only_a_direct_report_form_plugin():
|
|
1142
|
+
"""A project base class may register for its subclass, and a bill plugin's toolbar is outside
|
|
1143
|
+
the report evidence, so neither is judged."""
|
|
1144
|
+
assert report_listener_issues(_report_form(_DRILL).replace(
|
|
1145
|
+
"extends AbstractReportFormPlugin", "extends ProjectReportFormPlugin")) == []
|
|
1146
|
+
assert report_listener_issues(
|
|
1147
|
+
"package demo;\n"
|
|
1148
|
+
"import kd.bos.bill.AbstractBillPlugIn;\n"
|
|
1149
|
+
"import kd.bos.form.control.events.ItemClickEvent;\n"
|
|
1150
|
+
"public class Bill extends AbstractBillPlugIn {\n"
|
|
1151
|
+
" public void itemClick(ItemClickEvent evt) { }\n"
|
|
1152
|
+
"}\n") == []
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
def _report_data(batch_by: str, query: str, helpers: str = "") -> str:
|
|
1156
|
+
return (
|
|
1157
|
+
"package demo;\n"
|
|
1158
|
+
"import kd.bos.algo.DataSet;\n"
|
|
1159
|
+
"import kd.bos.entity.report.AbstractReportListDataPlugin;\n"
|
|
1160
|
+
"import kd.bos.entity.report.ReportQueryParam;\n"
|
|
1161
|
+
"public class BatchPlugin extends AbstractReportListDataPlugin {\n"
|
|
1162
|
+
" @Override\n"
|
|
1163
|
+
" public DataSet queryBatchBy(ReportQueryParam queryParam) {\n"
|
|
1164
|
+
f"{batch_by}"
|
|
1165
|
+
" }\n"
|
|
1166
|
+
" @Override\n"
|
|
1167
|
+
" public DataSet query(ReportQueryParam queryParam, Object selectedObj) throws Throwable {\n"
|
|
1168
|
+
f"{query}"
|
|
1169
|
+
" }\n"
|
|
1170
|
+
f"{helpers}"
|
|
1171
|
+
"}\n"
|
|
1172
|
+
)
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
_BATCH_BY = (
|
|
1176
|
+
" queryParam.byBatchInfo().setCountPerBatch(100);\n"
|
|
1177
|
+
" return basis(queryParam);\n"
|
|
1178
|
+
)
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
def test_a_batched_query_that_ignores_its_batch_is_rejected():
|
|
1182
|
+
issues = report_batch_issues(_report_data(_BATCH_BY, " return all(queryParam);\n"))
|
|
1183
|
+
|
|
1184
|
+
assert len(issues) == 1
|
|
1185
|
+
assert issues[0]["method"] == "query"
|
|
1186
|
+
assert issues[0]["line"] == 12
|
|
1187
|
+
assert "selectedObj" in issues[0]["message"]
|
|
1188
|
+
assert "getCurrentBatchRows" in issues[0]["message"]
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
def test_a_batched_query_that_reads_its_batch_is_accepted():
|
|
1192
|
+
assert report_batch_issues(_report_data(
|
|
1193
|
+
_BATCH_BY, " return byRows(queryParam.byBatchInfo().getCurrentBatchRows());\n")) == []
|
|
1194
|
+
assert report_batch_issues(_report_data(_BATCH_BY, " return byBasis(selectedObj);\n")) == []
|
|
1195
|
+
# A helper that reads the batch is accepted without being followed.
|
|
1196
|
+
assert report_batch_issues(_report_data(
|
|
1197
|
+
_BATCH_BY, " return narrowed(queryParam);\n",
|
|
1198
|
+
" private DataSet narrowed(ReportQueryParam p) { return byRows(p.byBatchInfo()); }\n")) == []
|
|
1199
|
+
|
|
1200
|
+
|
|
1201
|
+
def test_a_query_batch_by_that_never_batches_is_not_judged():
|
|
1202
|
+
assert report_batch_issues(_report_data(
|
|
1203
|
+
" return null;\n", " return all(queryParam);\n")) == []
|
|
1204
|
+
|
|
1205
|
+
|
|
1206
|
+
def test_sdk_absent_report_names_are_warned_with_what_8_0_offers():
|
|
1207
|
+
"""No class file in the 8.0 SDK mentions these names, so no 8.0 receiver carries them."""
|
|
1208
|
+
warnings = sdk_absent_name_warnings(_report_form(
|
|
1209
|
+
" @Override\n"
|
|
1210
|
+
" public void beforeQuery(kd.bos.entity.report.ReportQueryParam queryParam) {\n"
|
|
1211
|
+
" ReportList reportList = this.getControl(\"reportlistap\");\n"
|
|
1212
|
+
" reportList.setAsyncDataCount(true);\n"
|
|
1213
|
+
" // reportList.setAsyncDataCount(true); is not code\n"
|
|
1214
|
+
" Object custom = queryParam.getFilter().getCustomFilter();\n"
|
|
1215
|
+
" Object kept = queryParam.getCustomFilter();\n"
|
|
1216
|
+
" }\n"))
|
|
1217
|
+
|
|
1218
|
+
assert [item["name"] for item in warnings] == ["setAsyncDataCount", "FilterInfo.getCustomFilter"]
|
|
1219
|
+
assert all(item["severity"] == "warning" for item in warnings)
|
|
1220
|
+
assert "setAsyncTotal" in warnings[0]["message"]
|
|
1221
|
+
assert "queryParam.getCustomFilter()" in warnings[1]["message"]
|
|
1222
|
+
|
|
1223
|
+
|
|
1224
|
+
def test_a_name_the_source_declares_itself_is_not_warned():
|
|
1225
|
+
assert sdk_absent_name_warnings(_report_form(
|
|
1226
|
+
" private void setAsyncDataCount(boolean on) { }\n"
|
|
1227
|
+
" void run() { setAsyncDataCount(true); }\n")) == []
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
def test_an_unguarded_source_cast_in_package_data_is_warned():
|
|
1231
|
+
package_data = (
|
|
1232
|
+
" @Override\n"
|
|
1233
|
+
" public void packageData(PackageDataEvent event) {\n"
|
|
1234
|
+
" ReportColumn column = (ReportColumn) event.getSource();\n"
|
|
1235
|
+
" }\n")
|
|
1236
|
+
imports = ("import kd.bos.entity.datamodel.events.PackageDataEvent;\n"
|
|
1237
|
+
"import kd.bos.entity.report.ReportColumn;\n")
|
|
1238
|
+
warnings = report_source_cast_warnings(_report_form(package_data, imports))
|
|
1239
|
+
|
|
1240
|
+
assert [item["errorCode"] for item in warnings] == ["PLUGIN_REPORT_SOURCE_CAST_UNGUARDED"]
|
|
1241
|
+
guarded = package_data.replace(
|
|
1242
|
+
" ReportColumn column",
|
|
1243
|
+
" if (!(event.getSource() instanceof ReportColumn)) { return; }\n ReportColumn column")
|
|
1244
|
+
assert report_source_cast_warnings(_report_form(guarded, imports)) == []
|
|
1245
|
+
|
|
1246
|
+
|
|
1247
|
+
def test_the_report_pages_pass_every_blocking_source_gate():
|
|
1248
|
+
"""The report scenes and guides are what the coding step copies. A blocking gate that rejects
|
|
1249
|
+
one of their complete classes means either the page teaches a defect or the gate is too wide,
|
|
1250
|
+
and both have to be settled here rather than at a user's compile boundary."""
|
|
1251
|
+
from java_source import duplicate_methods
|
|
1252
|
+
|
|
1253
|
+
platform = SCRIPTS.parent / "knowledge" / "cosmic" / "platform"
|
|
1254
|
+
pages = sorted((platform / "scenes" / "report").glob("*.md")) + [
|
|
1255
|
+
platform / "scenes" / "system-service" / "report-plugin-interface.md",
|
|
1256
|
+
platform / "report-plugin.md", platform / "reportlistdata-plugin.md",
|
|
1257
|
+
platform / "reporttreedata-plugin.md",
|
|
1258
|
+
]
|
|
1259
|
+
gates = (duplicate_methods, return_type_issues, base_class_issues, override_signature_issues,
|
|
1260
|
+
filter_operator_issues, forbidden_member_issues, unverified_import_issues,
|
|
1261
|
+
unverified_member_issues, scene_misuse_issues, report_listener_issues,
|
|
1262
|
+
report_batch_issues)
|
|
1263
|
+
findings: list[str] = []
|
|
1264
|
+
classes = 0
|
|
1265
|
+
for page in pages:
|
|
1266
|
+
for block in re.findall(r"```java\n(.*?)```",
|
|
1267
|
+
page.read_text(encoding="utf-8-sig", errors="replace"), re.DOTALL):
|
|
1268
|
+
if not re.search(r"^\s*public\s+(?:(?:final|abstract)\s+)*class\s+\w+[^;{]*\{",
|
|
1269
|
+
block, re.MULTILINE):
|
|
1270
|
+
continue
|
|
1271
|
+
classes += 1
|
|
1272
|
+
findings += [f"{page.name}: {gate.__name__}: {issue.get('message')}"
|
|
1273
|
+
for gate in gates for issue in gate(block)]
|
|
1274
|
+
|
|
1275
|
+
assert classes >= 30, "too few complete classes found, so the sweep proves little"
|
|
1276
|
+
assert not findings, "\n".join(findings)
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
def test_exact_api_and_specialized_knowledge_are_selected_without_a_scene():
|
|
1280
|
+
evidence = plugin_coding_evidence("form", core_logic=["QueryServiceHelper.queryOne 查询"],
|
|
1281
|
+
hit_modules=["idempotency", "security-input-validation"])
|
|
1282
|
+
contract = next(item for item in evidence["apiContracts"] if item["class"] == "kd.bos.servicehelper.QueryServiceHelper")
|
|
1283
|
+
assert {member["name"] for member in contract["members"]} == {"queryOne"}
|
|
1284
|
+
assert all(Path(path).is_file() for path in evidence["specializedReferences"].values())
|
|
1285
|
+
assert set(evidence["specializedReferences"]) == {"idempotency", "security-input-validation"}
|
|
1286
|
+
assert "apiContracts" not in plugin_coding_evidence("form", core_logic=["按业务规则处理页面"])
|
|
1287
|
+
|
|
1288
|
+
|
|
1289
|
+
def test_plugin_type_lookup_returns_exact_registry_guide_without_inventory():
|
|
1290
|
+
import subprocess
|
|
1291
|
+
result = subprocess.run([sys.executable, str(SCRIPTS / "platform_api_contract.py"), "--describe-plugin-type", "operation"], capture_output=True, text=True)
|
|
1292
|
+
assert result.returncode == 0
|
|
1293
|
+
payload = json.loads(result.stdout)
|
|
1294
|
+
assert payload["type"] == "operation"
|
|
1295
|
+
assert Path(payload["guide"]).is_file()
|
|
1296
|
+
assert payload["baseClass"]
|
|
1297
|
+
assert "types" not in payload
|
|
1298
|
+
invalid = subprocess.run([sys.executable, str(SCRIPTS / "platform_api_contract.py"), "--describe-plugin-type", "unknown"], capture_output=True, text=True)
|
|
1299
|
+
assert invalid.returncode == 2 and json.loads(invalid.stdout)["errorCode"] == "PLUGIN_TYPE_UNSUPPORTED"
|
|
1300
|
+
|
|
1301
|
+
|
|
1302
|
+
def test_documented_inherited_transaction_event_members_are_available():
|
|
1303
|
+
for name in ["BeginOperationTransactionArgs", "EndOperationTransactionArgs"]:
|
|
1304
|
+
value = describe_api("kd.bos.entity.plugin.args." + name, ["getOperationKey"])
|
|
1305
|
+
assert value["status"] is True
|
|
1306
|
+
assert value["undocumentedMembers"] == []
|
|
1307
|
+
assert any(item["declaredBy"] == "kd.bos.entity.plugin.args.OperationArgs" for item in value["members"])
|