@shundoo-ai/dsh-cosmic 1.0.19 → 1.0.21
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/cordis.patch.yml +5 -5
- package/package.json +1 -1
- package/skills/cosmic-build/CHANGELOG.md +4 -0
- package/skills/cosmic-build/LOCAL-PATCHES.md +91 -40
- package/skills/cosmic-build/SKILL.md +46 -13
- package/skills/cosmic-build/assets/platform-report-filters/TypedReportFilterFields.java +169 -0
- package/skills/cosmic-build/assets/platform-report-filters/platform.patch +98 -0
- package/skills/cosmic-build/assets/report-algo-benchmark/ReportAlgoBenchmark.java +48 -0
- package/skills/cosmic-build/assets/report-algo-benchmark/ReportBenchmarkLogFactory.java +7 -0
- package/skills/cosmic-build/examples/java-rules/CosmicEntryQuery.java +26 -0
- package/skills/cosmic-build/examples/java-rules/CosmicReportAggregation.java +35 -0
- package/skills/cosmic-build/examples/java-rules/CosmicReportFilters.java +77 -0
- package/skills/cosmic-build/examples/java-rules/CosmicTimeAdapter.java +25 -0
- package/skills/cosmic-build/examples/java-rules/CosmicTimeAdapterCheck.java +26 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/api/algo/GroupbyDataSet.md +47 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/api/entity/report/FilterInfo.md +12 -12
- package/skills/cosmic-build/knowledge/cosmic/platform/api/entity/report/FilterItemInfo.md +2 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/form-plugin.md +2 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/java-type-contracts.md +11 -2
- package/skills/cosmic-build/knowledge/cosmic/platform/list-plugin.md +18 -14
- package/skills/cosmic-build/knowledge/cosmic/platform/operation-plugin.md +4 -4
- package/skills/cosmic-build/knowledge/cosmic/platform/reportlistdata-plugin.md +17 -9
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/cross-entity-aggregate-writeback.md +2 -0
- package/skills/cosmic-build/knowledge/cosmic/platform/scenes/operation/pre-post-operation.md +15 -7
- package/skills/cosmic-build/knowledge/cosmic/platform/sdk/sdk-algo.md +24 -68
- package/skills/cosmic-build/knowledge/cosmic/platform/workflow-plugin.md +2 -7
- package/skills/cosmic-build/references/application-context.md +35 -1
- package/skills/cosmic-build/references/common-reference.md +1 -1
- package/skills/cosmic-build/references/compile-guide.md +55 -68
- package/skills/cosmic-build/references/delivery.md +59 -7
- package/skills/cosmic-build/references/execution-contract.md +38 -0
- package/skills/cosmic-build/references/form-discovery.md +7 -1
- package/skills/cosmic-build/references/java-plugin-delivery.md +32 -162
- package/skills/cosmic-build/references/operation-dispatch.md +17 -0
- package/skills/cosmic-build/references/plugin-handoff-input.md +131 -0
- package/skills/cosmic-build/references/plugin-runtime-routing.md +1 -1
- package/skills/cosmic-build/references/report-filter-contract.md +78 -0
- package/skills/cosmic-build/references/sdk-inspection.md +23 -0
- package/skills/cosmic-build/schemas/data-model-authoring.schema.json +2 -3
- package/skills/cosmic-build/scripts/_http_client.py +55 -55
- package/skills/cosmic-build/scripts/appconfig_load.py +2 -2
- package/skills/cosmic-build/scripts/apply_report_filter_extension.py +39 -0
- package/skills/cosmic-build/scripts/bootstrap_cosmic_context.py +37 -2
- package/skills/cosmic-build/scripts/compile_plugin_source.py +279 -156
- package/skills/cosmic-build/scripts/execution_binding.py +40 -0
- package/skills/cosmic-build/scripts/inspect_platform_sdk.py +169 -0
- package/skills/cosmic-build/scripts/kd_api.py +23 -22
- package/skills/cosmic-build/scripts/kd_auth.py +1 -1
- package/skills/cosmic-build/scripts/kd_env_loader.py +19 -10
- package/skills/cosmic-build/scripts/kd_token_cache.py +2 -2
- package/skills/cosmic-build/scripts/kingscript_sdk_sync.py +350 -0
- package/skills/cosmic-build/scripts/kingscript_source_gate.py +11 -8
- package/skills/cosmic-build/scripts/platform_api_contract.py +246 -11
- package/skills/cosmic-build/scripts/plugin_corpus/build_index.py +403 -0
- package/skills/cosmic-build/scripts/plugin_corpus/jvm_classfile.py +281 -0
- package/skills/cosmic-build/scripts/plugin_corpus/measure_api_usage.py +446 -0
- package/skills/cosmic-build/scripts/plugin_source_gate.py +48 -7
- package/skills/cosmic-build/scripts/plugin_target_sdk.py +45 -0
- package/skills/cosmic-build/scripts/prepare_plugin_handoff.py +493 -47
- package/skills/cosmic-build/scripts/preview_url.py +35 -6
- package/skills/cosmic-build/scripts/query_plugin_handoff.py +250 -0
- package/skills/cosmic-build/scripts/register_plugins.py +28 -2
- package/skills/cosmic-build/scripts/render_output_cards.py +42 -9
- package/skills/cosmic-build/scripts/report_algo_benchmark.py +46 -0
- package/skills/cosmic-build/scripts/run.py +2 -2
- package/skills/cosmic-build/scripts/sync_generated_locale_assets.py +43 -0
- package/skills/cosmic-build/scripts/test/fixtures/ReportSdkCheck.java +64 -0
- package/skills/cosmic-build/scripts/test/fixtures/java_context_booking_native.json +541 -0
- package/skills/cosmic-build/scripts/test/test_add_rule_business_contract.py +85 -14
- package/skills/cosmic-build/scripts/test/test_batch5_drift_governance.py +12 -3
- package/skills/cosmic-build/scripts/test/test_build_meta_order.py +0 -16
- package/skills/cosmic-build/scripts/test/test_cli_continuation.py +87 -0
- package/skills/cosmic-build/scripts/test/test_compile_plugin_source.py +224 -3
- package/skills/cosmic-build/scripts/test/test_control_authoring.py +123 -4
- package/skills/cosmic-build/scripts/test/test_cosmic_sdk_examples.py +141 -0
- package/skills/cosmic-build/scripts/test/test_data_model_contract.py +30 -5
- package/skills/cosmic-build/scripts/test/test_delivery_stage_boundary.py +2 -10
- package/skills/cosmic-build/scripts/test/test_dym_sync_background.py +11 -0
- package/skills/cosmic-build/scripts/test/test_dynamic_form_metadata_only_contract.py +59 -1
- package/skills/cosmic-build/scripts/test/test_guide_skeleton_contract.py +87 -43
- package/skills/cosmic-build/scripts/test/test_inspect_platform_sdk.py +71 -0
- package/skills/cosmic-build/scripts/test/test_kd_api.py +67 -19
- package/skills/cosmic-build/scripts/test/test_kingscript_sdk_sync.py +221 -0
- package/skills/cosmic-build/scripts/test/test_kingscript_source_gate.py +10 -0
- package/skills/cosmic-build/scripts/test/test_list_configuration_closure.py +18 -0
- package/skills/cosmic-build/scripts/test/test_long_session_regressions.py +116 -0
- package/skills/cosmic-build/scripts/test/test_mcp_cli.py +524 -0
- package/skills/cosmic-build/scripts/test/test_modeling_input_recovery.py +141 -0
- package/skills/cosmic-build/scripts/test/test_open_world_cli_catalog.py +36 -2
- package/skills/cosmic-build/scripts/test/test_platform_api_contract.py +161 -1
- package/skills/cosmic-build/scripts/test/test_plugin_business_contract.py +91 -0
- package/skills/cosmic-build/scripts/test/test_plugin_corpus.py +195 -0
- package/skills/cosmic-build/scripts/test/test_plugin_handoff.py +346 -1
- package/skills/cosmic-build/scripts/test/test_plugin_target_sdk.py +70 -0
- package/skills/cosmic-build/scripts/test/test_preview_url.py +44 -1
- package/skills/cosmic-build/scripts/test/test_property_batch.py +22 -0
- package/skills/cosmic-build/scripts/test/test_property_input_discovery.py +13 -11
- package/skills/cosmic-build/scripts/test/test_query_plugin_handoff.py +160 -0
- package/skills/cosmic-build/scripts/test/test_register_plugins.py +31 -1
- package/skills/cosmic-build/scripts/test/test_render_output_cards.py +50 -4
- package/skills/cosmic-build/scripts/test/test_report_business_contract.py +27 -0
- package/skills/cosmic-build/scripts/test/test_report_doc_contract_sync.py +5 -4
- package/skills/cosmic-build/scripts/test/test_report_sdk_optimization.py +36 -0
- package/skills/cosmic-build/scripts/test/test_rule_batch_evidence.py +1 -1
- package/skills/cosmic-build/scripts/test/test_rule_domain_contract.py +20 -0
- package/skills/cosmic-build/scripts/test/test_rule_function_formula.py +16 -6
- package/skills/cosmic-build/scripts/test/test_skill_entry_contract.py +1 -0
- package/skills/cosmic-build/scripts/test/test_test_environment.py +45 -0
- package/skills/cosmic-build/scripts/test/test_verify_plugin_loading.py +79 -0
- package/skills/cosmic-build/scripts/test_environment.py +49 -0
- package/skills/cosmic-build/scripts/verify_plugin_loading.py +71 -0
- package/skills/cosmic-build/skills/cosmic-coding-impl/SKILL.md +86 -162
- package/skills/cosmic-build/skills/cosmic-coding-script/ATTRIBUTION.md +1 -1
- package/skills/cosmic-build/skills/cosmic-coding-script/SKILL.md +45 -14
- package/skills/cosmic-build/skills/cosmic-coding-script/references/kwc-controller-authoring.md +6 -0
- package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/README.md +29 -19
- package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/docs//350/204/232/346/234/254SDK.md +3 -4
- package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/indexes/package-index.md +1 -1
- package/skills/cosmic-build/skills/cosmic-coding-script/references/sdk/strategy.md +27 -18
- package/skills/cosmic-build/skills/cosmic-meta-api/SKILL.md +11 -17
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/ai_meta.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/app_menu.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/apis/meta_log.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/control-authoring.json +45 -4
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/form-control-traits.json +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/layout-ui.json +18 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/menu-publish.json +8 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/property-batch.json +7 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/property-input-index.json +3 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/report-write-contracts.json +1 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-domain.json +37 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/rule-event-contract.json +24 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/cli/run-build-meta.json +61 -12
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/dropdown-menu.schema.json +69 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/report-write.schema.json +2 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/rule-set.schema.json +1 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/contracts/schema/rule-write.schema.json +14 -7
- package/skills/cosmic-build/skills/cosmic-meta-api/docs/api-summary.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/app-action/SKILL.md +6 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/develop/SKILL.md +1 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/export/SKILL.md +5 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/init/SKILL.md +13 -36
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/SKILL.md +8 -9
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/pdm/scripts/config.json.example +5 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/reset/SKILL.md +6 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/usage/SKILL.md +20 -148
- package/skills/cosmic-build/skills/cosmic-meta-api/modules/verify/SKILL.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/build_receipts.py +171 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_apps.py +25 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_controls.py +55 -21
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_fields.py +10 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_formactions.py +63 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_forms.py +59 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_listui.py +8 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_metaops.py +36 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_plugins.py +115 -90
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_properties.py +666 -4
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/cmd_rules.py +1415 -87
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/commands.py +6 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/config.py +28 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/execution_errors.py +40 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/formatters.py +32 -5
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/property_policy_contract.py +6 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/report_ops.py +113 -17
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/reset.py +3 -3
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/_core/rule_contract.py +107 -26
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/build_meta_order.py +50 -49
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/cosmic_api.py +856 -150
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/field_decorator.py +47 -14
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_build_receipt_resume.py +374 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_cli_failure_contract.py +24 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_creation_identity_boundary.py +4 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_dropdown_menu_contract.py +382 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_existing_form_ui.py +0 -25
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_field_decorator.py +50 -6
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_functional_group_resolution.py +83 -16
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_identity_template_resolution.py +103 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_plugin_contract.py +28 -2
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_readback_derivation.py +22 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_report_typed_filters.py +65 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_rule_write_closure.py +1041 -15
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_post_build_recovery.py +65 -0
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_run_batch_verification_evidence.py +17 -0
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/api-param-checklist.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-knowledge/generation/rule-modeling-guide.md +7 -4
- package/skills/cosmic-build/skills/cosmic-meta-modeling/SKILL.md +61 -13
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/app-menu/SKILL.md +8 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/formtemplate/SKILL.md +5 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/formtemplate/dynamic-form-artifact-schema.md +5 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/modules/report/SKILL.md +21 -9
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-entity-graphs.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-storage-constraints.md +5 -3
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/authoring-ui-composition.md +6 -3
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/creation-recovery.md +13 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/references/parse-core.md +3 -0
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_contract.py +6 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/data_model_emit.py +135 -87
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/field_type_registry.py +11 -1
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/isv_resolver.py +1 -3
- package/skills/cosmic-build/skills/cosmic-meta-modeling/scripts/report_validator.py +29 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/SKILL.md +4 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/context/SKILL.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/execute/SKILL.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/SKILL.md +5 -4
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/operation/operation-write.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 +20 -5
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/complex-apis.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/property/policy-grammar.md +4 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/report-op/SKILL.md +9 -0
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/SKILL.md +61 -4
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/raw-rule-authoring.md +3 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/rule-actions.md +2 -2
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/rule-reference.md +6 -6
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/rule/state.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/unit/SKILL.md +6 -3
- package/skills/cosmic-build/skills/cosmic-meta-modifier/modules/verify/usage-guide.md +4 -4
- package/skills/cosmic-build/skills/cosmic-meta-modifier/references/list-filter-modification.md +4 -1
- package/skills/cosmic-build/skills/cosmic-meta-ui/SKILL.md +9 -1
- package/skills/cosmic-build/skills/cosmic-meta-ui/modules/button/SKILL.md +3 -0
- package/skills/cosmic-build/skills/mcp-server/SKILL.md +210 -0
- package/skills/cosmic-build/skills/mcp-server/scripts/mcp_cli.py +431 -0
- package/skills/cosmic-build/skills/openapi-gen/SKILL.md +3 -3
- package/skills/cosmic-build/skills/openapi-gen/scripts/api/gen_runner.py +2 -2
- package/skills/cosmic-dev-best-practices/references/bill-dev-convention.md +19 -9
- package/skills/cosmic-dev-best-practices/references/cosmic-meta-api.md +14 -11
- package/skills/cosmic-dev-best-practices/references/design-examples/basedata-design-example.md +1 -1
- package/skills/cosmic-dev-best-practices/references/design-examples/bill-design-example.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/AssistantField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/BasedataField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/BasedataPropField.md +3 -3
- package/skills/cosmic-dev-best-practices/references/elements/CreaterField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/CurrencyField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/CustomerField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/FlexField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/MaterielField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/OrgField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/SupplierField.md +2 -2
- package/skills/cosmic-dev-best-practices/references/elements/UnitField.md +1 -1
- package/skills/cosmic-dev-best-practices/references/elements/UserField.md +3 -3
- package/skills/cosmic-dev-best-practices/references/elements/common-field-property-conventions.md +16 -11
- package/skills/cosmic-dev-best-practices/references/layout-control-convention.md +5 -4
- package/skills/erp-analysis/references/delivery-contract.md +4 -2
- package/skills/erp-analysis/templates/AGENTS.template.md +1 -1
- package/skills/cosmic-build/skills/cosmic-meta-api/scripts/.dsh/cosmic/.kd/config.json +0 -21
package/skills/cosmic-build/skills/cosmic-meta-api/scripts/test/test_dropdown_menu_contract.py
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from unittest import mock
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
11
|
+
from _core import cmd_listui, cmd_operations, cmd_properties
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.parametrize("locale", ["", ".locales/zh"])
|
|
15
|
+
def test_menu_contract_is_discoverable_without_local_application_files(tmp_path, locale):
|
|
16
|
+
root = Path(__file__).resolve().parents[4]
|
|
17
|
+
cli = root / locale / "skills/cosmic-meta-api/scripts/cosmic_api.py"
|
|
18
|
+
result = subprocess.run(
|
|
19
|
+
[sys.executable, str(cli), "--stdout", "describeCommand", "configureDropdownMenu"],
|
|
20
|
+
cwd=tmp_path, capture_output=True, text=True, check=True,
|
|
21
|
+
)
|
|
22
|
+
action = json.loads(result.stdout)["action"]
|
|
23
|
+
assert action["command"] == "configureDropdownMenu"
|
|
24
|
+
assert {"append", "update", "order"} <= set(action["payloadSchema"]["properties"])
|
|
25
|
+
flags = {flag for argument in action["arguments"] for flag in argument["flags"]}
|
|
26
|
+
assert {"--form-name", "--element", "--input", "--app-root"} <= flags
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# JavaBean projection used by getProperties, including LocaleString and derived values.
|
|
30
|
+
CURRENT = [
|
|
31
|
+
{"id": "id-a", "key": "a", "title": {"zh_CN": "设置"},
|
|
32
|
+
"operationKey": "op_a", "visible": "init,new", "index": 1,
|
|
33
|
+
"visibleValue": 9, "isv": "uhyl"},
|
|
34
|
+
{"id": "id-b", "key": "b", "title": {"zh_CN": "预览"},
|
|
35
|
+
"operationKey": "op_b", "index": 2},
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.fixture
|
|
40
|
+
def platform(monkeypatch):
|
|
41
|
+
state = {"items": copy.deepcopy(CURRENT), "writes": [], "operations": 0}
|
|
42
|
+
def read(*args, **kwargs):
|
|
43
|
+
return {"status": True, "data": {"dropdownItems": json.dumps(state["items"])}}
|
|
44
|
+
def load(*args, **kwargs):
|
|
45
|
+
if state.get("saved_document") is not None:
|
|
46
|
+
return {"status": True, "data": copy.deepcopy(state["saved_document"])}
|
|
47
|
+
native, locale_rows = [], []
|
|
48
|
+
for row in state["items"]:
|
|
49
|
+
item = cmd_properties._dropdown_authored_item(row)
|
|
50
|
+
if "Isv" in item:
|
|
51
|
+
item["IsvFlag"] = item.pop("Isv")
|
|
52
|
+
for field in ("Title", "SubTitle"):
|
|
53
|
+
value = item.get(field)
|
|
54
|
+
if not isinstance(value, dict):
|
|
55
|
+
continue
|
|
56
|
+
resource = f"app.form.button-id.DropdownItems.{item['Id']}.{field}"
|
|
57
|
+
locale_rows.append({resource: {"resId": resource, **value}})
|
|
58
|
+
item[field] = value.get("zh_CN") or next(iter(value.values()), "")
|
|
59
|
+
native.append(item)
|
|
60
|
+
return {"status": True, "data": {
|
|
61
|
+
"appNum": "app",
|
|
62
|
+
"formmeta": {"Key": "form", "Id": "form", "Language": "zh_CN", "IsvFlag": "uhyl",
|
|
63
|
+
"Items": [{"Key": "print", "Id": "button-id", "DropdownItems": native}]},
|
|
64
|
+
"entitymeta": {"Key": "form", "IsvFlag": "uhyl", "Rules": [{"Id": "keep-rule"}]},
|
|
65
|
+
"localeItems": {"form": locale_rows},
|
|
66
|
+
}}
|
|
67
|
+
def save(cfg, token, form, payload, **kwargs):
|
|
68
|
+
state["saved_document"] = copy.deepcopy(payload)
|
|
69
|
+
items = payload["formmeta"]["Items"][0]["DropdownItems"]
|
|
70
|
+
assert payload["entitymeta"]["Rules"] == [{"Id": "keep-rule"}]
|
|
71
|
+
state["writes"].append(copy.deepcopy(items))
|
|
72
|
+
locales = {key: value for row in payload["localeItems"]["form"]
|
|
73
|
+
for key, value in row.items()}
|
|
74
|
+
state["items"] = []
|
|
75
|
+
for item in items:
|
|
76
|
+
projected = {
|
|
77
|
+
key[:1].lower() + key[1:]: value
|
|
78
|
+
for key, value in cmd_properties._dropdown_authored_item(item).items()
|
|
79
|
+
}
|
|
80
|
+
for field in ("Title", "SubTitle"):
|
|
81
|
+
resource = next((value for key, value in locales.items()
|
|
82
|
+
if key.endswith(f".{item['Id']}.{field}")), None)
|
|
83
|
+
if resource:
|
|
84
|
+
projected[field[:1].lower() + field[1:]] = {
|
|
85
|
+
key: value for key, value in resource.items() if key != "resId"}
|
|
86
|
+
state["items"].append(projected)
|
|
87
|
+
return {"status": True, "data": {}}
|
|
88
|
+
def operations(*args, **kwargs):
|
|
89
|
+
state["operations"] += 1
|
|
90
|
+
return {"status": True, "data": [
|
|
91
|
+
{"operationKey": key, "operationType": "printpreview"}
|
|
92
|
+
for key in ("op_a", "op_b", "op_c")]}
|
|
93
|
+
monkeypatch.setattr(cmd_properties, "do_get_properties", read)
|
|
94
|
+
monkeypatch.setattr(cmd_listui, "do_load_full_metadata", load)
|
|
95
|
+
monkeypatch.setattr(cmd_listui, "do_save_full_metadata", save)
|
|
96
|
+
monkeypatch.setattr(cmd_properties, "do_get_property_schema", lambda *args: {
|
|
97
|
+
"status": True, "data": [{"propertyName": "DropdownItems", "editable": True}]})
|
|
98
|
+
monkeypatch.setattr(cmd_operations, "do_list_operations", operations)
|
|
99
|
+
return state
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def edit(intent):
|
|
103
|
+
return cmd_properties.do_configure_dropdown_menu({}, "", "form", "print", intent)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_real_projection_combined_edit_is_one_save_and_preserves_ids(platform):
|
|
107
|
+
result = edit({
|
|
108
|
+
"update": [{"key": "设置", "subtitle": "打印配置"}],
|
|
109
|
+
"append": [{"key": "c", "title": "PDF", "operation": "op_c"}],
|
|
110
|
+
"moves": [{"key": "c", "before": "预览"}],
|
|
111
|
+
})
|
|
112
|
+
assert result["status"] is True
|
|
113
|
+
assert result["verification"]["verified"] is True
|
|
114
|
+
assert len(platform["writes"]) == 1
|
|
115
|
+
items = platform["writes"][0]
|
|
116
|
+
assert [item["Key"] for item in items] == ["a", "c", "b"]
|
|
117
|
+
assert items[0]["Id"] == "id-a"
|
|
118
|
+
assert items[2]["Id"] == "id-b"
|
|
119
|
+
assert items[0]["SubTitle"] == "打印配置"
|
|
120
|
+
assert items[2]["Title"] == "预览"
|
|
121
|
+
assert platform["items"][0]["isv"] == "uhyl"
|
|
122
|
+
assert platform["operations"] == 1
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_target_resolution_accepts_unique_operation_and_exact_key_wins(platform):
|
|
126
|
+
result = edit({"update": [{"key": "op_a", "title": "打印设置"}]})
|
|
127
|
+
assert result["status"] is True
|
|
128
|
+
assert platform["writes"][0][0]["Key"] == "a"
|
|
129
|
+
assert platform["operations"] == 0
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_expected_snapshot_includes_exact_locale_side_effects(platform):
|
|
133
|
+
platform["items"][0]["title"]["en_US"] = "Settings"
|
|
134
|
+
result = cmd_properties.do_configure_dropdown_menu({}, "", "form", "print", {
|
|
135
|
+
"update": [{"key": "a", "title": "打印参数设置", "subtitle": "选择参数和模板"}],
|
|
136
|
+
}, locale="zh_CN")
|
|
137
|
+
assert result["status"] is True
|
|
138
|
+
assert result["verification"]["verified"] is True
|
|
139
|
+
assert len(result["verification"]["changedLocalePaths"]) == 2
|
|
140
|
+
resources = platform["saved_document"]["localeItems"]["form"]
|
|
141
|
+
localized = {key: value for row in resources for key, value in row.items()}
|
|
142
|
+
title = next(value for key, value in localized.items() if key.endswith(".Title"))
|
|
143
|
+
subtitle = next(value for key, value in localized.items() if key.endswith(".SubTitle"))
|
|
144
|
+
assert title["zh_CN"] == "打印参数设置"
|
|
145
|
+
assert title["en_US"] == "Settings"
|
|
146
|
+
assert subtitle["zh_CN"] == "选择参数和模板"
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_new_item_gets_owned_locale_resources(platform):
|
|
150
|
+
result = edit({"append": [{"key": "new", "title": "新菜单", "subtitle": "说明"}]})
|
|
151
|
+
assert result["status"] is True
|
|
152
|
+
resources = platform["saved_document"]["localeItems"]["form"]
|
|
153
|
+
created = {key: value for row in resources for key, value in row.items()
|
|
154
|
+
if ".DropdownItems." in key and value.get("zh_CN") in ("新菜单", "说明")}
|
|
155
|
+
assert len(created) == 2
|
|
156
|
+
assert all(key == value["resId"] for key, value in created.items())
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_remove_keeps_unmentioned_item_and_avoids_operation_scan(platform):
|
|
160
|
+
result = edit({"remove": ["预览"]})
|
|
161
|
+
assert result["status"] is True
|
|
162
|
+
assert [item["Id"] for item in platform["writes"][0]] == ["id-a"]
|
|
163
|
+
assert platform["writes"][0][0]["IsvFlag"] == "uhyl"
|
|
164
|
+
assert platform["operations"] == 0
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_noop_does_not_save_or_query_operations(platform):
|
|
168
|
+
result = edit({"update": [{"key": "a", "title": "设置"}]})
|
|
169
|
+
assert result["status"] is True
|
|
170
|
+
assert result["data"]["changed"] is False
|
|
171
|
+
assert not platform["writes"]
|
|
172
|
+
assert not platform["operations"]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@pytest.mark.parametrize("intent", [
|
|
176
|
+
{"clear": True}, {"replace": []}, {"clear": True, "append": []},
|
|
177
|
+
{"append": [{"key": "a", "title": "duplicate"}]},
|
|
178
|
+
{"moves": [{"key": "a", "before": "b", "after": "b"}]},
|
|
179
|
+
{"update": [{"key": "missing", "title": "X"}]},
|
|
180
|
+
{"order": ["a", "a"]},
|
|
181
|
+
{"update": [{"key": "a", "id": "replacement"}]},
|
|
182
|
+
{"update": [{"key": "a", "uploadable": True}]},
|
|
183
|
+
])
|
|
184
|
+
def test_invalid_edits_never_write(platform, intent):
|
|
185
|
+
result = edit(intent)
|
|
186
|
+
assert result["status"] is False
|
|
187
|
+
assert result["writeAttempted"] is False
|
|
188
|
+
assert not platform["writes"]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_ambiguous_title_returns_candidates_without_write(platform):
|
|
192
|
+
platform["items"][1]["title"] = {"zh_CN": "设置"}
|
|
193
|
+
result = edit({"remove": ["设置"]})
|
|
194
|
+
assert result["errorCode"] == "DROPDOWN_MENU_TARGET_AMBIGUOUS"
|
|
195
|
+
assert len(result["data"]["candidates"]) == 2
|
|
196
|
+
assert not platform["writes"]
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_missing_property_is_not_empty_menu(platform, monkeypatch):
|
|
200
|
+
monkeypatch.setattr(cmd_properties, "do_get_properties",
|
|
201
|
+
lambda *args: {"status": True, "data": {}})
|
|
202
|
+
result = edit({"append": [{"key": "c", "title": "C"}]})
|
|
203
|
+
assert result["errorCode"] == "DROPDOWN_MENU_READ_UNAVAILABLE"
|
|
204
|
+
assert not platform["writes"]
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def test_upload_aliases_are_written_and_read_back(platform):
|
|
208
|
+
result = edit({"append": [{
|
|
209
|
+
"key": "upload", "title": "上传", "uploadable": True,
|
|
210
|
+
"uploadConfig": {"accept": "pdf", "maxFiles": 3, "maxFileSize": 1024, "multiple": True},
|
|
211
|
+
}]})
|
|
212
|
+
assert result["status"] is True
|
|
213
|
+
assert platform["writes"][0][-1]["UploadConfig"] == {
|
|
214
|
+
"Suffix": "pdf", "LimitCount": 3, "LimitSize": 1024, "Multiple": True,
|
|
215
|
+
}
|
|
216
|
+
assert platform["operations"] == 0
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_upload_patch_retains_unmentioned_configuration(platform):
|
|
220
|
+
platform["items"][1].update(operationKey="", uploadable=True, uploadConfig={
|
|
221
|
+
"suffix": "pdf", "limitCount": 3, "limitSize": 1024, "multiple": True,
|
|
222
|
+
})
|
|
223
|
+
result = edit({"update": [{"key": "b", "uploadConfig": {"maxFiles": 5}}]})
|
|
224
|
+
assert result["status"] is True
|
|
225
|
+
assert platform["writes"][0][1]["UploadConfig"]["LimitSize"] == 1024
|
|
226
|
+
assert platform["writes"][0][1]["UploadConfig"]["LimitCount"] == 5
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def test_unknown_upload_keys_stop_before_write(platform):
|
|
230
|
+
result = edit({"append": [{"key": "c", "uploadConfig": {"typo": 3}}]})
|
|
231
|
+
assert result["status"] is False
|
|
232
|
+
assert not platform["writes"]
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@pytest.mark.parametrize("extra", [
|
|
236
|
+
{"isv": "another_vendor"},
|
|
237
|
+
{"newAuthoredProperty": "must retain"},
|
|
238
|
+
{"title": {"zh_CN": "设置", "en_US": "Settings"}},
|
|
239
|
+
])
|
|
240
|
+
def test_original_vendor_languages_and_unknown_properties_are_retained(platform, extra):
|
|
241
|
+
platform["items"][0].update(extra)
|
|
242
|
+
result = edit({"moves": [{"key": "a", "after": "b"}]})
|
|
243
|
+
assert result["status"] is True
|
|
244
|
+
retained = next(item for item in platform["items"] if item["key"] == "a")
|
|
245
|
+
assert all(retained[key] == value for key, value in extra.items())
|
|
246
|
+
assert platform["writes"][0][1]["Id"] == "id-a"
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def test_partial_order_keeps_unmentioned_tail(platform):
|
|
250
|
+
result = edit({"order": ["预览"]})
|
|
251
|
+
assert result["status"] is True
|
|
252
|
+
assert [item["key"] for item in platform["items"]] == ["b", "a"]
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def test_empty_operation_cannot_serve_as_business_implementation(platform, monkeypatch):
|
|
256
|
+
monkeypatch.setattr(cmd_operations, "do_list_operations", lambda *args: {
|
|
257
|
+
"status": True, "data": [{"operationKey": "empty", "operationType": "donothing",
|
|
258
|
+
"pluginCount": 0, "opBizRuleCount": 0}]})
|
|
259
|
+
result = edit({"append": [{"key": "new", "operation": "empty"}]})
|
|
260
|
+
assert result["errorCode"] == "DROPDOWN_MENU_OPERATION_IMPLEMENTATION_REQUIRED"
|
|
261
|
+
assert not platform["writes"]
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def test_same_value_upload_aliases_are_accepted():
|
|
265
|
+
assert cmd_properties._dropdown_upload_config({"suffix": "pdf", "accept": "pdf"}) == {"Suffix": "pdf"}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@pytest.mark.parametrize("encoded", [False, True])
|
|
269
|
+
def test_readback_extension_upload_fields_survive_title_and_option_edits(platform, monkeypatch, encoded):
|
|
270
|
+
config = {"Multiple": True, "Suffix": "pdf", "LimitCount": 5, "UnCompresspic": False,
|
|
271
|
+
"FutureOption": {"enabled": None}}
|
|
272
|
+
platform["items"][1].update(operationKey="", uploadable=True, uploadConfig={
|
|
273
|
+
"multiple": True, "suffix": "pdf", "limitCount": 5, "unCompresspic": False,
|
|
274
|
+
"futureOption": {"enabled": None},
|
|
275
|
+
})
|
|
276
|
+
load = cmd_listui.do_load_full_metadata
|
|
277
|
+
def native_load(*args, **kwargs):
|
|
278
|
+
result = load(*args, **kwargs)
|
|
279
|
+
if not platform.get("saved_document"):
|
|
280
|
+
result["data"]["formmeta"]["Items"][0]["DropdownItems"][1]["UploadConfig"] = (
|
|
281
|
+
json.dumps(config) if encoded else copy.deepcopy(config))
|
|
282
|
+
return result
|
|
283
|
+
monkeypatch.setattr(cmd_listui, "do_load_full_metadata", native_load)
|
|
284
|
+
result = edit({"update": [{"key": "b", "title": "上传模板", "uploadConfig": {"maxFiles": 6}}]})
|
|
285
|
+
assert result["status"] is True
|
|
286
|
+
actual = platform["writes"][0][1]["UploadConfig"]
|
|
287
|
+
assert isinstance(actual, str) == encoded
|
|
288
|
+
actual = json.loads(actual) if encoded else actual
|
|
289
|
+
assert actual == {**config, "LimitCount": 6}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def test_unknown_upload_input_is_still_rejected():
|
|
293
|
+
with pytest.raises(ValueError, match="UNKNOWN"):
|
|
294
|
+
cmd_properties._normalize_dropdown_item({"key": "new", "uploadConfig": {"futureOption": True}})
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def test_item_title_can_resolve_parent_without_button_selector(platform):
|
|
298
|
+
result = cmd_properties.do_configure_dropdown_menu({}, "", "form", None, {
|
|
299
|
+
"update": [{"key": "设置", "title": "打印参数设置", "subtitle": "选择参数"}],
|
|
300
|
+
}, locale="zh_CN")
|
|
301
|
+
assert result["status"] is True
|
|
302
|
+
assert platform["writes"][0][0]["OperationKey"] == "op_a"
|
|
303
|
+
assert platform["writes"][0][0]["SubTitle"] == "选择参数"
|
|
304
|
+
assert platform["operations"] == 0
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def test_ambiguous_menu_parent_returns_candidates_without_write(platform, monkeypatch):
|
|
308
|
+
load = cmd_listui.do_load_full_metadata
|
|
309
|
+
def ambiguous(*args, **kwargs):
|
|
310
|
+
result = load(*args, **kwargs)
|
|
311
|
+
items = result["data"]["formmeta"]["Items"]
|
|
312
|
+
items.append({**copy.deepcopy(items[0]), "Key": "other_button", "Id": "other-id"})
|
|
313
|
+
return result
|
|
314
|
+
monkeypatch.setattr(cmd_listui, "do_load_full_metadata", ambiguous)
|
|
315
|
+
result = cmd_properties.do_configure_dropdown_menu({}, "", "form", None, {
|
|
316
|
+
"update": [{"key": "设置", "title": "新名称"}],
|
|
317
|
+
})
|
|
318
|
+
assert result["errorCode"] == "DROPDOWN_MENU_PARENT_REQUIRED"
|
|
319
|
+
assert len(result["data"]["candidates"]) == 2
|
|
320
|
+
assert not platform["writes"]
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@pytest.mark.parametrize("args,owner", [
|
|
324
|
+
(["queryFormsByApp", "--bad-option"], "describeCommand queryFormsByApp"),
|
|
325
|
+
(["nonexistentCommand"], "describeCommand <selected-command>"),
|
|
326
|
+
])
|
|
327
|
+
def test_argument_errors_keep_discovery_bounded(tmp_path, args, owner):
|
|
328
|
+
cli = Path(__file__).resolve().parents[1] / "cosmic_api.py"
|
|
329
|
+
result = subprocess.run([sys.executable, str(cli), "--stdout", *args],
|
|
330
|
+
cwd=tmp_path, capture_output=True, text=True)
|
|
331
|
+
assert result.returncode == 2
|
|
332
|
+
receipt = json.loads(result.stdout)
|
|
333
|
+
assert receipt["recovery"]["describeCommand"] == owner
|
|
334
|
+
assert "choose from" not in receipt["message"]
|
|
335
|
+
assert len(receipt["message"]) < 500
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def test_failed_save_never_retries_or_renames(platform, monkeypatch):
|
|
339
|
+
api = mock.Mock(return_value={"status": False, "errorCode": "META_SAVE_FAIL",
|
|
340
|
+
"message": "key collision"})
|
|
341
|
+
monkeypatch.setattr(cmd_listui, "do_save_full_metadata", api)
|
|
342
|
+
result = edit({"update": [{"key": "a", "subtitle": "X"}]})
|
|
343
|
+
assert result["status"] is False
|
|
344
|
+
assert result["recovery"]["automaticRetry"] is False
|
|
345
|
+
api.assert_called_once()
|
|
346
|
+
assert api.call_args.args[3]["formmeta"]["Items"][0]["DropdownItems"][0]["Key"] == "a"
|
|
347
|
+
assert platform["items"] == CURRENT
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def test_readback_mismatch_is_not_success(platform, monkeypatch):
|
|
351
|
+
# API says saved, but independently read metadata still contains the old title.
|
|
352
|
+
monkeypatch.setattr(cmd_listui, "do_save_full_metadata",
|
|
353
|
+
lambda *args, **kwargs: {"status": True, "data": {}})
|
|
354
|
+
result = edit({"update": [{"key": "a", "title": "New"}]})
|
|
355
|
+
assert result["status"] is False
|
|
356
|
+
assert result["verification"]["verified"] is False
|
|
357
|
+
assert result["data"]["changed"] is None
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def test_missing_operation_is_not_replaced_by_placeholder(platform):
|
|
361
|
+
result = edit({"append": [{"key": "c", "operation": "missing"}]})
|
|
362
|
+
assert result["errorCode"] == "DROPDOWN_MENU_OPERATION_NOT_FOUND"
|
|
363
|
+
assert not platform["writes"]
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def test_generic_replacement_requires_semantic_edit(platform):
|
|
367
|
+
result = cmd_properties.do_set_complex_property({}, "", "form", "dropdownItems", [],
|
|
368
|
+
element_key="print")
|
|
369
|
+
assert result["errorCode"] == "DROPDOWN_MENU_EDIT_REQUIRED"
|
|
370
|
+
assert not platform["writes"]
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def test_identity_comparison_is_case_sensitive():
|
|
374
|
+
assert cmd_properties._dropdown_mismatches([{"Key": "a"}], [{"Key": "A"}])
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def test_property_discovery_routes_to_semantic_menu_edit():
|
|
378
|
+
result = cmd_properties._describe_one_property(
|
|
379
|
+
{"propertyName": "DropdownItems", "editable": True, "complexity": "complex"},
|
|
380
|
+
None, None)
|
|
381
|
+
assert result["inputFormat"] == "menuEditIntent"
|
|
382
|
+
assert "configureDropdownMenu" in result["guidance"]
|
|
@@ -2734,31 +2734,6 @@ def test_apply_form_grid_converts_fieldset_to_runtime_flex(
|
|
|
2734
2734
|
assert "layoutProjection" not in result["data"]
|
|
2735
2735
|
|
|
2736
2736
|
|
|
2737
|
-
def test_form_control_traits_match_harvested_platform_superclasses():
|
|
2738
|
-
app_build = next(
|
|
2739
|
-
parent for parent in pathlib.Path(__file__).resolve().parents
|
|
2740
|
-
if parent.name == "app-build"
|
|
2741
|
-
)
|
|
2742
|
-
scripts_root = app_build / "scripts"
|
|
2743
|
-
if str(scripts_root) not in sys.path:
|
|
2744
|
-
sys.path.insert(0, str(scripts_root))
|
|
2745
|
-
from workspace_layout import repository_root
|
|
2746
|
-
repo = repository_root(app_build)
|
|
2747
|
-
source = __import__("json").loads(
|
|
2748
|
-
(repo / "tests/metadata-xml-corpus/contracts/element-superclasses.json")
|
|
2749
|
-
.read_text(encoding="utf-8")
|
|
2750
|
-
)
|
|
2751
|
-
for type_name, traits in _FORM_CONTROL_TRAITS.items():
|
|
2752
|
-
chain = []
|
|
2753
|
-
current = type_name
|
|
2754
|
-
seen = set()
|
|
2755
|
-
while current in source and current not in seen:
|
|
2756
|
-
seen.add(current)
|
|
2757
|
-
current = source[current]
|
|
2758
|
-
chain.append(current)
|
|
2759
|
-
assert chain == traits["superclasses"]
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
2737
|
def test_compile_form_tabs_moves_complete_subtrees_and_leaves_attachment_outside():
|
|
2763
2738
|
intent, evidence = compile_form_tabs_intent({
|
|
2764
2739
|
"pages": [
|
|
@@ -28,6 +28,48 @@ def _minimal_bill_entity(*, biz_name: str, fields: list[dict]) -> dict:
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class FieldDecoratorTrimAndAliasTest(unittest.TestCase):
|
|
31
|
+
def test_explicit_names_are_checked_after_prefixing(self):
|
|
32
|
+
raw = {"entities": [{
|
|
33
|
+
**_minimal_bill_entity(biz_name="bill", fields=[]), "logicalName": "x" * 25,
|
|
34
|
+
}]}
|
|
35
|
+
with self.assertRaisesRegex(ValueError, "including developer prefix"):
|
|
36
|
+
field_decorator.decorate_artifact(raw, "acme", "acme_app", standard=False)
|
|
37
|
+
raw["entities"][0]["logicalName"] = "mybill"
|
|
38
|
+
raw["entities"][0]["fields"] = [
|
|
39
|
+
{"bizWord": "one", "logicalName": "same", "displayName": "一", "dataType": "TextField"},
|
|
40
|
+
{"bizWord": "two", "logicalName": "acme_same", "displayName": "二", "dataType": "TextField"},
|
|
41
|
+
]
|
|
42
|
+
with self.assertRaisesRegex(ValueError, "duplicate fieldKey"):
|
|
43
|
+
field_decorator.decorate_artifact(raw, "acme", "acme_app", standard=False)
|
|
44
|
+
|
|
45
|
+
def test_explicit_logical_identifiers_are_namespaced_once_with_references(self):
|
|
46
|
+
for prefix in ("", "acme_"):
|
|
47
|
+
raw = {"entities": [{
|
|
48
|
+
**_minimal_bill_entity(biz_name="bill", fields=[{
|
|
49
|
+
"bizWord": "remark", "logicalName": prefix + "note",
|
|
50
|
+
"displayName": "备注", "dataType": "TextField",
|
|
51
|
+
}]), "logicalName": prefix + "mybill",
|
|
52
|
+
}], "propertySettings": [{"elementKey": prefix + "note", "settingType": "simple",
|
|
53
|
+
"properties": {"Name": "备注"}}],
|
|
54
|
+
"uiLayout": {"containers": [{"key": "panel", "children": [prefix + "note"]}]},
|
|
55
|
+
"rules": [{"ruleType": "formRule", "triggerField": prefix + "note",
|
|
56
|
+
"mountPoint": prefix + "mybill", "preCondition": prefix + "note" + " = '123'"}],
|
|
57
|
+
"operations": [{"operationKey": "save", "parameter": {
|
|
58
|
+
"fieldKey": prefix + "note", "formNumber": prefix + "mybill",
|
|
59
|
+
"message": prefix + "note", "entityId": "external-id"}}]}
|
|
60
|
+
decorated = field_decorator.decorate_artifact(raw, "acme", "acme_app", standard=False)
|
|
61
|
+
self.assertEqual(decorated["entities"][0]["entityKey"], "acme_mybill")
|
|
62
|
+
self.assertEqual(decorated["entities"][0]["fields"][0]["fieldKey"], "acme_note")
|
|
63
|
+
self.assertEqual(decorated["propertySettings"][0]["elementKey"], "acme_note")
|
|
64
|
+
self.assertEqual(decorated["uiLayout"]["containers"][0]["children"], ["acme_note"])
|
|
65
|
+
projection = field_decorator.make_entity_key_map(raw["entities"], "acme", standard=False)
|
|
66
|
+
self.assertEqual(projection[prefix + "mybill"], "acme_mybill")
|
|
67
|
+
self.assertEqual(decorated["rules"][0]["triggerField"], "acme_note")
|
|
68
|
+
self.assertEqual(decorated["rules"][0]["mountPoint"], "acme_mybill")
|
|
69
|
+
self.assertEqual(decorated["operations"][0]["parameter"]["fieldKey"], "acme_note")
|
|
70
|
+
self.assertEqual(decorated["operations"][0]["parameter"]["message"], prefix + "note")
|
|
71
|
+
self.assertEqual(decorated["operations"][0]["parameter"]["entityId"], "external-id")
|
|
72
|
+
|
|
31
73
|
"""decorate_artifact: trimming + alias mapping, ensuring rules/entryToolbar can be replaced."""
|
|
32
74
|
|
|
33
75
|
def setUp(self) -> None:
|
|
@@ -88,7 +130,7 @@ class FieldDecoratorTrimAndAliasTest(unittest.TestCase):
|
|
|
88
130
|
)
|
|
89
131
|
|
|
90
132
|
def test_explicit_entity_identity_is_preserved_while_table_is_projected(self) -> None:
|
|
91
|
-
logical_name = "
|
|
133
|
+
logical_name = "acme_PromotionCoupon2026" # prefixed identity within the entity budget
|
|
92
134
|
raw = {
|
|
93
135
|
"entities": [
|
|
94
136
|
{
|
|
@@ -220,7 +262,7 @@ class FieldDecoratorTrimAndAliasTest(unittest.TestCase):
|
|
|
220
262
|
self.assertEqual(253, action["RET"])
|
|
221
263
|
self.assertEqual(1, action["Seq"])
|
|
222
264
|
|
|
223
|
-
def
|
|
265
|
+
def test_call_field_change_is_serialized_by_authoring_compiler(self) -> None:
|
|
224
266
|
raw = {
|
|
225
267
|
"entities": [_minimal_bill_entity(
|
|
226
268
|
biz_name="rule_bill",
|
|
@@ -231,10 +273,12 @@ class FieldDecoratorTrimAndAliasTest(unittest.TestCase):
|
|
|
231
273
|
"trueActions": [{"actionType": "CallFieldChangeAction", "expression": "target"}],
|
|
232
274
|
}],
|
|
233
275
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
276
|
+
out = field_decorator.decorate_artifact(
|
|
277
|
+
raw, isv="acme", app_number="acme_app", standard=False
|
|
278
|
+
)
|
|
279
|
+
action = out["rules"][0]["trueActions"][0]
|
|
280
|
+
self.assertEqual("CallFieldChangeAction", action["ActionType"])
|
|
281
|
+
self.assertEqual(773, action["RET"])
|
|
238
282
|
|
|
239
283
|
def test_bizword_projection_updates_dynamic_layout_children(self) -> None:
|
|
240
284
|
long_bw = "next_followup_date"
|