@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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
import zipfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
11
|
+
from plugin_target_sdk import TargetSdkError, verify_target_sdk
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def target(tmp_path, monkeypatch):
|
|
16
|
+
if not shutil.which('javac') or not shutil.which('javap'):
|
|
17
|
+
pytest.skip('JDK tools unavailable')
|
|
18
|
+
project = tmp_path / 'gradle' / 'code' / 'probe'
|
|
19
|
+
project.mkdir(parents=True)
|
|
20
|
+
gradle = project.parents[1]
|
|
21
|
+
(gradle / 'settings.gradle').write_text("include(':probe')\nproject(':probe').projectDir=new File('code/probe')\n")
|
|
22
|
+
(gradle / 'gradlew').touch()
|
|
23
|
+
sdk = tmp_path / 'sdk'
|
|
24
|
+
bos = sdk / 'mservice-cosmic' / 'lib' / 'bos'
|
|
25
|
+
bos.mkdir(parents=True)
|
|
26
|
+
(gradle / 'gradle.properties').write_text(f'systemProp.cosmic_home={sdk}\n')
|
|
27
|
+
(gradle / 'config.gradle').write_text('cosmic_libs_path="${cosmic_home}/mservice-cosmic/lib"\nbos="${cosmic_libs_path}/bos"')
|
|
28
|
+
source = tmp_path / 'Probe.java'
|
|
29
|
+
source.write_text('package kd.bos.probe; public class Probe { public void trigger() {} }')
|
|
30
|
+
subprocess.run(['javac', '-d', str(tmp_path), str(source)], check=True, capture_output=True)
|
|
31
|
+
with zipfile.ZipFile(bos / 'probe.jar', 'w') as jar:
|
|
32
|
+
jar.write(tmp_path / 'kd/bos/probe/Probe.class', 'kd/bos/probe/Probe.class')
|
|
33
|
+
config = tmp_path / 'appconfig.json'
|
|
34
|
+
config.write_text(json.dumps({'compile': True, 'GRADLE_ROOT': str(gradle),
|
|
35
|
+
'JAVA_HOME': str(Path(shutil.which('java')).parent.parent)}))
|
|
36
|
+
monkeypatch.setenv('KD_APPCONFIG', str(config))
|
|
37
|
+
return project, config
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_bound_target_uses_real_sdk_declarations_without_config_changes(target):
|
|
41
|
+
project, config = target
|
|
42
|
+
before = config.read_bytes()
|
|
43
|
+
result = verify_target_sdk(str(project), {'baseContract': {'class': 'kd.bos.probe.Probe'}},
|
|
44
|
+
[{'class': 'kd.bos.probe.Probe', 'members': ['trigger']}])
|
|
45
|
+
assert result['status'] == 'verified' and result['module'] == ':probe'
|
|
46
|
+
assert result['classes'][0]['jarSha256']
|
|
47
|
+
assert result['contracts'][0]['members'][0]['declaration'] == 'public void trigger();'
|
|
48
|
+
assert config.read_bytes() == before
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.mark.parametrize('usage,code', [
|
|
52
|
+
([{'class': 'kd.bos.probe.Absent'}], 'SDK_CLASS_NOT_FOUND'),
|
|
53
|
+
([{'class': 'kd.bos.probe.Probe', 'members': ['absent']}], 'PLUGIN_SDK_MEMBER_UNVERIFIED'),
|
|
54
|
+
([{'class': 'kd.bos.probe.Probe', 'members': 'trigger'}], 'PLUGIN_SDK_USAGE_INVALID'),
|
|
55
|
+
])
|
|
56
|
+
def test_unproven_sdk_selectors_fail_before_generation(target, usage, code):
|
|
57
|
+
with pytest.raises(TargetSdkError) as error:
|
|
58
|
+
verify_target_sdk(str(target[0]), {}, usage)
|
|
59
|
+
assert error.value.error_code == code
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_unmapped_module_and_explicit_source_only_are_distinct(target):
|
|
63
|
+
project, config = target
|
|
64
|
+
with pytest.raises(TargetSdkError) as error:
|
|
65
|
+
verify_target_sdk(str(project.parent / 'stale'), {})
|
|
66
|
+
assert error.value.error_code == 'PLUGIN_MODULE_UNRESOLVED'
|
|
67
|
+
config.write_text('{"compile":false}')
|
|
68
|
+
result = verify_target_sdk(str(project), {})
|
|
69
|
+
assert result['status'] == 'not-run'
|
|
70
|
+
assert 'unverified' in result['evidenceScope']
|
|
@@ -43,7 +43,8 @@ def test_list_view_type_is_forwarded_as_extra_query_param() -> None:
|
|
|
43
43
|
config = {"baseUrl": "http://example.test/ierp"}
|
|
44
44
|
with mock.patch.object(preview_url.kd_api, "get", return_value=response) as request:
|
|
45
45
|
preview_url.get_preview_url(
|
|
46
|
-
config, "http://example.test/ierp", "extension_form_number", view_type="list"
|
|
46
|
+
config, "http://example.test/ierp", "extension_form_number", view_type="list",
|
|
47
|
+
form_preview_url="http://example.test/ierp/?formId=extension_form_number",
|
|
47
48
|
)
|
|
48
49
|
|
|
49
50
|
request.assert_called_once_with(
|
|
@@ -54,6 +55,48 @@ def test_list_view_type_is_forwarded_as_extra_query_param() -> None:
|
|
|
54
55
|
)
|
|
55
56
|
|
|
56
57
|
|
|
58
|
+
def test_duplicate_list_response_uses_verified_standard_list_route() -> None:
|
|
59
|
+
config = {"baseUrl": "http://example.test/ierp"}
|
|
60
|
+
|
|
61
|
+
def api(_base, path, _config, params):
|
|
62
|
+
if path == preview_url.PREVIEW_URL_PATH:
|
|
63
|
+
assert params == {"formNumber": "cup_request", "viewType": "list"}
|
|
64
|
+
return {"status": True, "errorCode": "0", "data": "http://example.test/ierp/?formId=cup_request"}
|
|
65
|
+
assert path == preview_url.FORM_SCHEMA_PATH
|
|
66
|
+
assert params == {"formId": "cup_request"}
|
|
67
|
+
return {"status": True, "errorCode": "0", "data": {
|
|
68
|
+
"listLayout": {"type": "FormAp"},
|
|
69
|
+
"listConfig": {"entityId": "cup_request"}, "listColumns": [{"key": "billno"}],
|
|
70
|
+
}}
|
|
71
|
+
|
|
72
|
+
with mock.patch.object(preview_url.kd_api, "get", side_effect=api) as request:
|
|
73
|
+
actual = preview_url.get_preview_url(
|
|
74
|
+
config, config["baseUrl"], "cup_request", view_type="list",
|
|
75
|
+
form_preview_url="http://example.test/ierp/?formId=cup_request",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
assert actual == "http://example.test/ierp/?formId=bos_list&type=list&billFormId=cup_request"
|
|
79
|
+
assert request.call_count == 2
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_duplicate_list_response_without_list_metadata_is_not_exposed() -> None:
|
|
83
|
+
config = {"baseUrl": "http://example.test/ierp"}
|
|
84
|
+
|
|
85
|
+
def api(_base, path, _config, _params):
|
|
86
|
+
if path == preview_url.PREVIEW_URL_PATH:
|
|
87
|
+
return {"status": True, "errorCode": "0", "data": "http://example.test/ierp/?formId=plain_page"}
|
|
88
|
+
return {"status": True, "errorCode": "0", "data": {
|
|
89
|
+
"formNumber": "plain_page", "listLayout": None, "listConfig": None,
|
|
90
|
+
}}
|
|
91
|
+
|
|
92
|
+
with mock.patch.object(preview_url.kd_api, "get", side_effect=api):
|
|
93
|
+
with pytest.raises(preview_url.PreviewUrlError, match="LIST_PREVIEW_METADATA_UNAVAILABLE"):
|
|
94
|
+
preview_url.get_preview_url(
|
|
95
|
+
config, config["baseUrl"], "plain_page", view_type="list",
|
|
96
|
+
form_preview_url="http://example.test/ierp/?formId=plain_page",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
57
100
|
def test_relative_platform_preview_url_is_resolved_against_environment() -> None:
|
|
58
101
|
assert preview_url._response_preview_url(
|
|
59
102
|
{"status": True, "errorCode": "0", "data": "/ierp/?formId=source"},
|
|
@@ -23,6 +23,12 @@ from _core import cmd_properties # noqa: E402
|
|
|
23
23
|
from _core import cmd_workspace # noqa: E402
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def test_lock_in_complex_policy_reports_the_supported_set_shape():
|
|
27
|
+
with pytest.raises(ValueError, match="PROPERTY_POLICY_LOCK_SHAPE_INVALID") as raised:
|
|
28
|
+
cmd_properties._normalize_recorded_complex_properties({"Lock": {"new": "lock"}})
|
|
29
|
+
assert '"set":{"Lock":"new,edit,submit,audit"}' in str(raised.value)
|
|
30
|
+
|
|
31
|
+
|
|
26
32
|
ROWS = [
|
|
27
33
|
{
|
|
28
34
|
"key": "demo",
|
|
@@ -73,6 +79,22 @@ ROWS = [
|
|
|
73
79
|
]
|
|
74
80
|
|
|
75
81
|
|
|
82
|
+
def test_same_caption_entry_fields_use_explicit_entity_scope_for_a_single_target():
|
|
83
|
+
rows = [
|
|
84
|
+
{"key": "booking", "name": "定场单", "type": "BillEntity", "category": "entity", "id": "H"},
|
|
85
|
+
{"key": "details", "name": "订场明细", "type": "EntryEntity", "category": "entity", "id": "E"},
|
|
86
|
+
{"key": "services", "name": "附加服务", "type": "SubEntryEntity", "category": "entity", "id": "S"},
|
|
87
|
+
{"key": "booking_price", "name": "单价", "type": "PriceField", "category": "field", "parentKey": "details", "parentId": "E"},
|
|
88
|
+
{"key": "service_price", "name": "单价", "type": "PriceField", "category": "field", "parentKey": "services", "parentId": "S"},
|
|
89
|
+
]
|
|
90
|
+
bulk = cmd_properties._property_policy_field_rows(rows, {"targets": {"fields": ["单价"], "entities": ["entries"]}}, "booking")
|
|
91
|
+
assert {row["key"] for row in bulk} == {"booking_price", "service_price"}
|
|
92
|
+
exact = cmd_properties._property_policy_field_rows(rows, {"targets": {"fields": [{"entity": "订场明细", "field": "单价"}]}}, "booking")
|
|
93
|
+
assert [row["key"] for row in exact] == ["booking_price"]
|
|
94
|
+
with pytest.raises(ValueError):
|
|
95
|
+
cmd_properties._property_policy_field_rows(rows, {"targets": {"fields": [{"field": "单价"}]}}, "booking")
|
|
96
|
+
|
|
97
|
+
|
|
76
98
|
def test_lock_policy_uses_verified_form_control_path() -> None:
|
|
77
99
|
from _core import cmd_listui
|
|
78
100
|
|
|
@@ -11,6 +11,7 @@ building it and had to be fixed.
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
import json
|
|
14
|
+
import subprocess
|
|
14
15
|
import sys
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
|
|
@@ -226,8 +227,6 @@ def test_both_locales_resolve_the_contract_index() -> None:
|
|
|
226
227
|
while still returning a well-formed answer - a silent wrong result, which is worse
|
|
227
228
|
than an error. Both locales must load the same index.
|
|
228
229
|
"""
|
|
229
|
-
import importlib.util
|
|
230
|
-
|
|
231
230
|
loaded = {}
|
|
232
231
|
# 契约解析器已随属性域搬到 _core/cmd_properties.py;按定义位置取,不写死文件。
|
|
233
232
|
import sys as _sys
|
|
@@ -237,15 +236,18 @@ def test_both_locales_resolve_the_contract_index() -> None:
|
|
|
237
236
|
assert len(holders) == 2, sorted(str(x) for x in holders)
|
|
238
237
|
for path in sorted(holders, key=lambda x: ".locales" in str(x)):
|
|
239
238
|
label = "zh" if ".locales" in str(path) else "canonical"
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
239
|
+
scripts = path.parents[1]
|
|
240
|
+
result = subprocess.run(
|
|
241
|
+
[
|
|
242
|
+
sys.executable, "-c",
|
|
243
|
+
"import json,sys; sys.path.insert(0,sys.argv[1]); "
|
|
244
|
+
"from _core.cmd_properties import _load_json_contract; "
|
|
245
|
+
"print(json.dumps(_load_json_contract('property-input-index.json')))",
|
|
246
|
+
str(scripts),
|
|
247
|
+
],
|
|
248
|
+
check=True, capture_output=True, text=True,
|
|
249
|
+
)
|
|
250
|
+
loaded[label] = json.loads(result.stdout)
|
|
249
251
|
|
|
250
252
|
for label, doc in loaded.items():
|
|
251
253
|
assert doc.get("properties"), f"{label} failed to load the index"
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import json
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
SCRIPT = Path(__file__).resolve().parents[1] / "query_plugin_handoff.py"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def handoff_file(tmp_path):
|
|
12
|
+
file = tmp_path / "_runtime" / "plugin-handoffs" / "BookingPlugin.json"
|
|
13
|
+
file.parent.mkdir(parents=True)
|
|
14
|
+
document = {
|
|
15
|
+
"status": True,
|
|
16
|
+
"handoffPath": str(file),
|
|
17
|
+
"handoff": {
|
|
18
|
+
"formGraph": {"formNumber": "booking", "entities": [
|
|
19
|
+
{"entityKey": "booking_head", "fields": [
|
|
20
|
+
{"fieldKey": "quantity", "fieldName": "数量", "comboOptions": ["1", "2"]},
|
|
21
|
+
{"fieldKey": "member", "fieldName": "会员", "required": False},
|
|
22
|
+
]},
|
|
23
|
+
{"entityKey": "booking_entry", "role": "entries", "parentEntityKey": "booking_head",
|
|
24
|
+
"collectionPath": ["booking_entry"], "fields": [
|
|
25
|
+
{"fieldKey": "start_time", "fieldType": "TimeProp", "javaType": "Integer",
|
|
26
|
+
"valueAccessor": 'getInt("start_time")', "timeValueUnit": "seconds-after-midnight"}]},
|
|
27
|
+
]},
|
|
28
|
+
"operationContracts": [
|
|
29
|
+
{"operationKey": "save", "parameter": {"entity": "booking_head"}},
|
|
30
|
+
{"operationKey": "confirm", "parameter": {"entity": "booking_head"}},
|
|
31
|
+
],
|
|
32
|
+
"codingEvidence": {"signatureTypes": [{"class": "ExtendedDataEntity", "import": "kd.bos.entity.ExtendedDataEntity"}],
|
|
33
|
+
"apiContracts": [{"class": "kd.bos.servicehelper.QueryServiceHelper", "import": "import kd.bos.servicehelper.QueryServiceHelper;", "members": [{"name": "query", "signature": "public static DynamicObjectCollection query(String entityName)"}]}],
|
|
34
|
+
"guide": "large-evidence-" * 1800},
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
file.write_text(json.dumps(document, ensure_ascii=False), encoding="utf-8")
|
|
38
|
+
return file, document
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def query(file, operation, *args):
|
|
42
|
+
result = subprocess.run([sys.executable, str(SCRIPT), operation, "--handoff", str(file), *args],
|
|
43
|
+
capture_output=True, text=True, check=False)
|
|
44
|
+
return result.returncode, json.loads(result.stdout), result.stdout
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_target_readiness_is_bounded_and_missing_evidence_is_not_claimed(tmp_path):
|
|
48
|
+
file, document = handoff_file(tmp_path)
|
|
49
|
+
code, context, _ = query(file, "coding-start")
|
|
50
|
+
assert code == 0 and context['targetSdk']['status'] == 'not-provided'
|
|
51
|
+
assert 'path' not in context['targetSdk']
|
|
52
|
+
document['handoff']['targetSdk'] = {'status': 'verified', 'module': ':probe',
|
|
53
|
+
'evidenceScope': 'SDK declarations only', 'classes': [{'class': 'large-evidence-' * 1800}]}
|
|
54
|
+
file.write_text(json.dumps(document))
|
|
55
|
+
for operation in ['coding-start', 'coding-context']:
|
|
56
|
+
code, context, output = query(file, operation)
|
|
57
|
+
assert code == 0 and context['targetSdk']['status'] == 'verified'
|
|
58
|
+
assert context['targetSdk']['path'] == '/handoff/targetSdk'
|
|
59
|
+
assert 'classes' not in context['targetSdk'] and 'large-evidence-' not in output
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_index_find_and_get_return_only_requested_native_context(tmp_path):
|
|
63
|
+
file, document = handoff_file(tmp_path)
|
|
64
|
+
original = hashlib.sha256(file.read_bytes()).hexdigest()
|
|
65
|
+
code, root, stdout = query(file, "index")
|
|
66
|
+
assert code == 0 and len(stdout.encode("utf-8")) <= 8192
|
|
67
|
+
assert [item["path"] for item in root["items"]] == ["/status", "/handoffPath", "/handoff"]
|
|
68
|
+
assert "large-evidence" not in stdout
|
|
69
|
+
|
|
70
|
+
code, graph, stdout = query(file, "index", "--path", "/handoff/formGraph/entities")
|
|
71
|
+
assert code == 0 and graph["items"][0]["identity"] == {"entityKey": "booking_head"}
|
|
72
|
+
code, field, _ = query(file, "find", "--path", "/handoff/formGraph/entities/0/fields",
|
|
73
|
+
"--field", "fieldKey", "--equals", "member")
|
|
74
|
+
assert code == 0 and [item["path"] for item in field["matches"]] == ["/handoff/formGraph/entities/0/fields/1"]
|
|
75
|
+
code, item, _ = query(file, "get", "--path", field["matches"][0]["path"])
|
|
76
|
+
assert code == 0 and item["value"] == document["handoff"]["formGraph"]["entities"][0]["fields"][1]
|
|
77
|
+
|
|
78
|
+
code, operation, _ = query(file, "find", "--path", "/handoff/operationContracts",
|
|
79
|
+
"--field", "operationKey", "--equals", "confirm")
|
|
80
|
+
assert code == 0 and operation["matches"][0]["path"] == "/handoff/operationContracts/1"
|
|
81
|
+
code, signature, _ = query(file, "get", "--path", "/handoff/codingEvidence/signatureTypes/0")
|
|
82
|
+
assert code == 0 and signature["value"] == document["handoff"]["codingEvidence"]["signatureTypes"][0]
|
|
83
|
+
assert hashlib.sha256(file.read_bytes()).hexdigest() == original
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_large_or_invalid_queries_fail_without_dumping_handoff(tmp_path):
|
|
87
|
+
file, _ = handoff_file(tmp_path)
|
|
88
|
+
for operation, arguments, expected in [
|
|
89
|
+
("get", ["--path", "/handoff"], "HANDOFF_QUERY_TOO_LARGE"),
|
|
90
|
+
("get", ["--path", "/handoff/formGraph/entities/9"], "HANDOFF_POINTER_NOT_FOUND"),
|
|
91
|
+
("get", ["--path", "handoff/formGraph"], "HANDOFF_POINTER_INVALID"),
|
|
92
|
+
("find", ["--path", "/handoff/formGraph", "--field", "entityKey", "--equals", "booking_head"], "HANDOFF_NOT_RECORD_LIST"),
|
|
93
|
+
("index", ["--path", "/handoff/operationContracts", "--offset", "-1"], "HANDOFF_PAGE_INVALID"),
|
|
94
|
+
]:
|
|
95
|
+
code, output, stdout = query(file, operation, *arguments)
|
|
96
|
+
assert code == 2 and output["errorCode"] == expected
|
|
97
|
+
assert len(stdout.encode("utf-8")) < 8192 and "large-evidence" not in stdout
|
|
98
|
+
|
|
99
|
+
code, page, _ = query(file, "index", "--path", "/handoff/operationContracts", "--limit", "1")
|
|
100
|
+
assert code == 0 and page["nextOffset"] == 1
|
|
101
|
+
code, page, _ = query(file, "index", "--path", "/handoff/operationContracts", "--offset", "1", "--limit", "1")
|
|
102
|
+
assert code == 0 and page["nextOffset"] is None and page["items"][0]["identity"]["operationKey"] == "confirm"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_query_rejects_unrelated_json_even_when_shape_looks_like_handoff(tmp_path):
|
|
106
|
+
file, document = handoff_file(tmp_path)
|
|
107
|
+
document["handoffPath"] = str(tmp_path / "other.json")
|
|
108
|
+
file.write_text(json.dumps(document), encoding="utf-8")
|
|
109
|
+
code, output, _ = query(file, "index")
|
|
110
|
+
assert code == 2 and output["errorCode"] == "HANDOFF_PATH_MISMATCH"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_coding_context_returns_required_types_once_without_large_guide(tmp_path):
|
|
114
|
+
file, _ = handoff_file(tmp_path)
|
|
115
|
+
original = hashlib.sha256(file.read_bytes()).hexdigest()
|
|
116
|
+
code, context, stdout = query(file, "coding-context")
|
|
117
|
+
assert code == 0 and context["contextMode"] == "coding-context"
|
|
118
|
+
assert context["handoffSha256"] == original
|
|
119
|
+
assert context["formGraph"]["entities"][0]["fields"][0]["fieldKey"] == "quantity"
|
|
120
|
+
assert context["queryRootEvidence"]["formNumber"] == "booking"
|
|
121
|
+
assert context["queryRootEvidence"]["nonRootEntityKeys"] == ["booking_entry"]
|
|
122
|
+
assert "standalone QueryServiceHelper roots" in context["queryRootEvidence"]["boundary"]
|
|
123
|
+
assert context["formGraph"]["entities"][1]["fields"][0]["valueAccessor"] == 'getInt("start_time")'
|
|
124
|
+
assert context["formGraph"]["entities"][1]["fields"][0]["timeValueUnit"] == "seconds-after-midnight"
|
|
125
|
+
assert context["operationContracts"][0]["operationKey"] == "save"
|
|
126
|
+
assert context["codingEvidence"]["apiContracts"][0]["members"][0]["signature"].startswith("public static DynamicObjectCollection query")
|
|
127
|
+
assert context["stateWarnings"] == []
|
|
128
|
+
assert "large-evidence" not in stdout and len(stdout.encode("utf-8")) < 32 * 1024
|
|
129
|
+
assert hashlib.sha256(file.read_bytes()).hexdigest() == original
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_startup_context_keeps_field_facts_and_exact_paths_without_expanding_api_or_related_forms(tmp_path):
|
|
133
|
+
file, document = handoff_file(tmp_path)
|
|
134
|
+
document["handoff"]["relatedForms"] = [{"formNumber": "member", "entities": [{"fields": [{"private": "not needed at startup"}]}]}]
|
|
135
|
+
document["handoff"]["availableOperations"] = [{"operationKey": "complete"}]
|
|
136
|
+
document["handoff"]["codingEvidence"]["operationDispatch"] = {"validator": {"accessor": "this.getOperateKey()"}}
|
|
137
|
+
file.write_text(json.dumps(document), encoding="utf-8")
|
|
138
|
+
code, result, output = query(file, "coding-start")
|
|
139
|
+
assert code == 0 and result["contextMode"] == "coding-start"
|
|
140
|
+
assert result["entities"][1]["fields"][0]["valueAccessor"] == 'getInt("start_time")'
|
|
141
|
+
assert result["relatedForms"][0]["path"] == "/handoff/relatedForms/0"
|
|
142
|
+
assert result["availableOperationKeys"] == ["complete"]
|
|
143
|
+
assert result["availableOperationsPath"] == "/handoff/availableOperations"
|
|
144
|
+
assert result["codingEvidence"]["operationDispatch"]["validator"]["accessor"] == "this.getOperateKey()"
|
|
145
|
+
assert result["codingEvidence"]["apiContracts"][0]["members"] == ["query"]
|
|
146
|
+
assert "large-evidence" not in output and "not needed at startup" not in output
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_large_startup_inventory_is_explicitly_referenced_instead_of_dumped(tmp_path):
|
|
150
|
+
file, document = handoff_file(tmp_path)
|
|
151
|
+
document["handoff"]["formGraph"]["entities"][0]["fields"] = [
|
|
152
|
+
{"fieldKey": f"field_{index}", "fieldName": "A large inventory field", "fieldType": "TextProp"}
|
|
153
|
+
for index in range(1000)]
|
|
154
|
+
file.write_text(json.dumps(document), encoding="utf-8")
|
|
155
|
+
code, result, output = query(file, "coding-start")
|
|
156
|
+
assert code == 0 and len(output.encode()) <= 16 * 1024
|
|
157
|
+
assert result["entities"][0]["fieldsOmitted"] is True
|
|
158
|
+
assert result["entities"][0]["fieldCount"] == 1000
|
|
159
|
+
assert result["entities"][0]["fieldsPath"] == "/handoff/formGraph/entities/0/fields"
|
|
160
|
+
assert "fields" not in result["entities"][0]
|
|
@@ -137,9 +137,28 @@ class BuildRegisterPayloadTest(unittest.TestCase):
|
|
|
137
137
|
"description": "desc",
|
|
138
138
|
"enabled": True,
|
|
139
139
|
"targetType": "form",
|
|
140
|
+
"pluginType": "JAVA",
|
|
140
141
|
})
|
|
141
142
|
self.assertNotIn("operationKey", payload)
|
|
142
143
|
|
|
144
|
+
def test_application_binding_comes_from_archive(self):
|
|
145
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
146
|
+
Path(tmp, "archive.json").write_text(json.dumps({
|
|
147
|
+
"appId": "selected-app", "javas": [{
|
|
148
|
+
"className": "example.Plugin", "type": "billform",
|
|
149
|
+
"filePath": "Plugin.java", "formNumber": "booking",
|
|
150
|
+
"description": "booking", "operationKey": "",
|
|
151
|
+
"bizAppId": "different-app",
|
|
152
|
+
}],
|
|
153
|
+
}))
|
|
154
|
+
candidate = register_plugins._load_classes_from_archive(tmp)[0]
|
|
155
|
+
payload = register_plugins._build_register_payload(
|
|
156
|
+
candidate["formNumber"], candidate["className"], candidate["description"],
|
|
157
|
+
"BILL_FORM", biz_app_id=candidate["bizAppId"],
|
|
158
|
+
)
|
|
159
|
+
self.assertEqual(payload["bizAppId"], "selected-app")
|
|
160
|
+
self.assertEqual(payload["pluginType"], "JAVA")
|
|
161
|
+
|
|
143
162
|
def test_operation_key_included_when_truthy(self):
|
|
144
163
|
payload = register_plugins._build_register_payload(
|
|
145
164
|
"sch_job", "com.example.OpPlugin", "desc", "form", operation_key="save",
|
|
@@ -334,7 +353,7 @@ class MainTest(unittest.TestCase):
|
|
|
334
353
|
mock_config, mock_post):
|
|
335
354
|
mock_load_classes.return_value = [
|
|
336
355
|
{"className": "com.example.FormPlugin", "type": "form_plugin",
|
|
337
|
-
"formNumber": "sch_job", "description": "test plugin"},
|
|
356
|
+
"formNumber": "sch_job", "description": "test plugin", "bizAppId": "selected-app"},
|
|
338
357
|
]
|
|
339
358
|
mock_registry.return_value = {"form_plugin": "form"}
|
|
340
359
|
mock_config.return_value = {"baseUrl": "https://api.example.com"}
|
|
@@ -343,6 +362,10 @@ class MainTest(unittest.TestCase):
|
|
|
343
362
|
output = self._run()
|
|
344
363
|
self.assertEqual(output["status"], "success")
|
|
345
364
|
self.assertEqual(output["summary"], {"total": 1, "success": 1, "failed": 0})
|
|
365
|
+
self.assertEqual(output["activation"]["state"], "registration_unverified")
|
|
366
|
+
self.assertIsNone(output["activation"]["requiresServiceRestart"])
|
|
367
|
+
self.assertFalse(output["activation"]["runtimeVerified"])
|
|
368
|
+
self.assertNotIn("nextAction", output["activation"])
|
|
346
369
|
|
|
347
370
|
# Verify kd_api.post call: legacy_api style + correct payload
|
|
348
371
|
args, kwargs = mock_post.call_args
|
|
@@ -354,6 +377,8 @@ class MainTest(unittest.TestCase):
|
|
|
354
377
|
self.assertEqual(body["className"], "com.example.FormPlugin")
|
|
355
378
|
self.assertEqual(body["targetType"], "form")
|
|
356
379
|
self.assertEqual(body["formNumber"], "sch_job")
|
|
380
|
+
self.assertEqual(body["bizAppId"], "selected-app")
|
|
381
|
+
self.assertEqual(body["pluginType"], "JAVA")
|
|
357
382
|
|
|
358
383
|
@mock.patch("register_plugins.kd_api.post")
|
|
359
384
|
@mock.patch("register_plugins._load_config")
|
|
@@ -417,6 +442,11 @@ class MainTest(unittest.TestCase):
|
|
|
417
442
|
|
|
418
443
|
self.assertEqual(output["status"], "success")
|
|
419
444
|
self.assertEqual(output["verification"]["status"], "pass")
|
|
445
|
+
self.assertEqual(output["activation"]["registeredClasses"], ["com.example.OpPlugin"])
|
|
446
|
+
self.assertEqual(output["activation"]["state"], "runtime_unverified")
|
|
447
|
+
self.assertNotIn("nextAction", output["activation"])
|
|
448
|
+
self.assertIsNone(output["activation"]["requiresServiceRestart"])
|
|
449
|
+
self.assertFalse(output["activation"]["runtimeVerified"])
|
|
420
450
|
mock_get.assert_called_once_with(
|
|
421
451
|
"https://api.example.com", register_plugins.QUERY_PATH,
|
|
422
452
|
{"baseUrl": "https://api.example.com"}, {"formNumber": "sch_job"},
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import hashlib
|
|
4
4
|
import importlib.util
|
|
5
5
|
import json
|
|
6
|
+
import os
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
import subprocess
|
|
8
9
|
import sys
|
|
@@ -67,16 +68,42 @@ def test_cli_delivers_one_block_and_the_output_check_handoff(capsys, tmp_path):
|
|
|
67
68
|
block = ':::render:kdform\n{"title":"Example","url":"https://example.test"}\n:::'
|
|
68
69
|
with mock.patch.object(sys, "argv", [
|
|
69
70
|
str(SCRIPT), "--app-root", str(tmp_path), "--form-number", "example",
|
|
70
|
-
]), mock.patch.object(CARDS, "render", return_value=[block]) as render
|
|
71
|
+
]), mock.patch.object(CARDS, "render", return_value=[block]) as render, mock.patch.object(
|
|
72
|
+
CARDS, "_DYM_BACKGROUND_NOTICE", ""
|
|
73
|
+
):
|
|
71
74
|
assert CARDS.main() == 0
|
|
72
75
|
output = capsys.readouterr()
|
|
73
76
|
assert output.out == block + "\n"
|
|
74
77
|
assert "output_check" in output.err
|
|
75
|
-
assert "search_tools" in output.err
|
|
76
78
|
assert "Markdown" in output.err
|
|
77
79
|
render.assert_called_once()
|
|
78
80
|
|
|
79
81
|
|
|
82
|
+
def test_cli_binds_auth_cache_to_explicit_app_root(tmp_path):
|
|
83
|
+
block = ':::render:kdform\n{"title":"Example","url":"https://example.test"}\n:::'
|
|
84
|
+
with mock.patch.dict(os.environ, {}, clear=False), mock.patch.object(sys, "argv", [
|
|
85
|
+
str(SCRIPT), "--app-root", str(tmp_path), "--form-number", "example",
|
|
86
|
+
]), mock.patch.object(CARDS, "render", return_value=[block]), mock.patch.object(
|
|
87
|
+
CARDS, "_DYM_BACKGROUND_NOTICE", ""
|
|
88
|
+
):
|
|
89
|
+
assert CARDS.main() == 0
|
|
90
|
+
assert os.environ["PROJECT_ROOT"] == str(tmp_path.resolve())
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_background_handoff_survives_suppressed_stderr(capsys, tmp_path):
|
|
94
|
+
block = ':::render:kdform\n{"title":"Example","url":"https://example.test"}\n:::'
|
|
95
|
+
notice = 'DYM_BACKGROUND_REQUEST={"formNumbers":["a","b"]}'
|
|
96
|
+
with mock.patch.object(sys, "argv", [
|
|
97
|
+
str(SCRIPT), "--app-root", str(tmp_path), "--form-number", "a", "--form-number", "b",
|
|
98
|
+
]), mock.patch.object(CARDS, "render", return_value=[block]), mock.patch.object(
|
|
99
|
+
CARDS, "_DYM_BACKGROUND_NOTICE", notice
|
|
100
|
+
):
|
|
101
|
+
assert CARDS.main() == 0
|
|
102
|
+
output = capsys.readouterr()
|
|
103
|
+
assert output.out == block + "\n" + notice + "\n"
|
|
104
|
+
assert "Markdown" in output.err
|
|
105
|
+
|
|
106
|
+
|
|
80
107
|
def test_cli_does_not_request_output_check_when_rendering_fails(capsys, tmp_path):
|
|
81
108
|
with mock.patch.object(sys, "argv", [
|
|
82
109
|
str(SCRIPT), "--app-root", str(tmp_path), "--form-number", "example",
|
|
@@ -149,6 +176,7 @@ def test_existing_form_modification_renders_only_exact_target(tmp_path: Path) ->
|
|
|
149
176
|
"http://example.test/ierp",
|
|
150
177
|
"target_form",
|
|
151
178
|
view_type="list",
|
|
179
|
+
form_preview_url="http://example.test/ierp/?formId=platform_original_id",
|
|
152
180
|
),
|
|
153
181
|
]
|
|
154
182
|
saved = json.loads((tmp_path / "archive.json").read_text(encoding="utf-8"))
|
|
@@ -436,7 +464,7 @@ def test_card_carries_list_preview_url_and_degrades_silently(tmp_path: Path) ->
|
|
|
436
464
|
json.dumps(archive, ensure_ascii=False), encoding="utf-8"
|
|
437
465
|
)
|
|
438
466
|
|
|
439
|
-
def preview_with_list(config, base_url, form_number, view_type=""):
|
|
467
|
+
def preview_with_list(config, base_url, form_number, view_type="", form_preview_url=""):
|
|
440
468
|
if view_type == "list":
|
|
441
469
|
return "http://example.test/ierp/?formId=list_view_id"
|
|
442
470
|
return "http://example.test/ierp/?formId=form_view_id"
|
|
@@ -454,7 +482,7 @@ def test_card_carries_list_preview_url_and_degrades_silently(tmp_path: Path) ->
|
|
|
454
482
|
saved = json.loads((tmp_path / "archive.json").read_text(encoding="utf-8"))
|
|
455
483
|
assert saved["artifacts"][0]["listUrl"].endswith("formId=list_view_id")
|
|
456
484
|
|
|
457
|
-
def preview_without_list(config, base_url, form_number, view_type=""):
|
|
485
|
+
def preview_without_list(config, base_url, form_number, view_type="", form_preview_url=""):
|
|
458
486
|
if view_type == "list":
|
|
459
487
|
raise RuntimeError("list view unsupported")
|
|
460
488
|
return "http://example.test/ierp/?formId=form_view_id"
|
|
@@ -471,6 +499,24 @@ def test_card_carries_list_preview_url_and_degrades_silently(tmp_path: Path) ->
|
|
|
471
499
|
assert "formId=form_view_id" in degraded[0]
|
|
472
500
|
assert "listUrl" not in degraded[0]
|
|
473
501
|
|
|
502
|
+
def preview_with_duplicate_list(config, base_url, form_number, view_type="", form_preview_url=""):
|
|
503
|
+
return "http://example.test/ierp/?formId=form_view_id"
|
|
504
|
+
|
|
505
|
+
with mock.patch(
|
|
506
|
+
"kd_env_loader.load_effective_skill_config",
|
|
507
|
+
return_value={"server": {"baseUrl": "http://example.test/ierp"}},
|
|
508
|
+
), mock.patch.object(CARDS, "get_preview_url", side_effect=preview_with_duplicate_list):
|
|
509
|
+
duplicate = CARDS.render(
|
|
510
|
+
str(tmp_path), include_plan=False, target_form_number="target_form"
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
assert len(duplicate) == 1
|
|
514
|
+
assert "listUrl" not in duplicate[0]
|
|
515
|
+
saved = json.loads((tmp_path / "archive.json").read_text(encoding="utf-8"))
|
|
516
|
+
assert "listUrl" not in saved["artifacts"][0]
|
|
517
|
+
handoff = json.loads((tmp_path / "_runtime/test-environment.json").read_text(encoding="utf-8"))
|
|
518
|
+
assert "listUrl" not in handoff["forms"][0]
|
|
519
|
+
|
|
474
520
|
|
|
475
521
|
def test_localized_card_cli_keeps_target_filter_contract() -> None:
|
|
476
522
|
localized = SCRIPT.parents[1] / ".locales" / "zh" / "scripts" / SCRIPT.name
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import importlib
|
|
4
4
|
import json
|
|
5
|
+
import subprocess
|
|
5
6
|
import sys
|
|
6
7
|
import tempfile
|
|
7
8
|
import unittest
|
|
@@ -19,6 +20,32 @@ cosmic_api = importlib.import_module("cosmic_api")
|
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class ReportBusinessContractTest(unittest.TestCase):
|
|
23
|
+
def test_report_schema_cli_contains_all_payload_definitions_locally(self):
|
|
24
|
+
completed = subprocess.run([
|
|
25
|
+
sys.executable, str(APP_BUILD_ROOT / "skills/cosmic-meta-modeling/scripts/report_validator.py"), "--schema",
|
|
26
|
+
], capture_output=True, text=True, check=True)
|
|
27
|
+
schema = json.loads(completed.stdout)
|
|
28
|
+
self.assertEqual(schema["properties"]["reportColumns"]["$ref"], "#/$defs/reportWrite_columns")
|
|
29
|
+
self.assertIn("kind", schema["$defs"]["reportWrite_column"]["required"])
|
|
30
|
+
self.assertIn("caption", schema["$defs"]["reportWrite_column"]["required"])
|
|
31
|
+
self.assertEqual(schema["$defs"]["reportWrite_sort"]["properties"]["direction"]["enum"], ["asc", "desc"])
|
|
32
|
+
|
|
33
|
+
def check_references(value):
|
|
34
|
+
if isinstance(value, list):
|
|
35
|
+
for item in value:
|
|
36
|
+
check_references(item)
|
|
37
|
+
if not isinstance(value, dict):
|
|
38
|
+
return
|
|
39
|
+
if "$ref" in value:
|
|
40
|
+
self.assertTrue(value["$ref"].startswith("#/"))
|
|
41
|
+
target = schema
|
|
42
|
+
for segment in value["$ref"][2:].split("/"):
|
|
43
|
+
target = target[segment.replace("~1", "/").replace("~0", "~")]
|
|
44
|
+
for item in value.values():
|
|
45
|
+
check_references(item)
|
|
46
|
+
|
|
47
|
+
check_references(schema)
|
|
48
|
+
|
|
22
49
|
def test_report_pipeline_returns_authoritative_readback_form_number(self) -> None:
|
|
23
50
|
artifact = {
|
|
24
51
|
"templateNumber": "bos_rpttpl",
|
|
@@ -353,13 +353,14 @@ class TreeSemanticsTest(unittest.TestCase):
|
|
|
353
353
|
)
|
|
354
354
|
self.assertGreater(checked, 0, "the id/pid scan matched nothing; the pattern is stale")
|
|
355
355
|
|
|
356
|
-
def
|
|
356
|
+
def test_routing_uses_registered_report_types(self):
|
|
357
357
|
routing = _read(APP_BUILD_ROOT / "references/plugin-runtime-routing.md")
|
|
358
|
-
|
|
358
|
+
types = {entry["type"] for entry in json.loads(
|
|
359
|
+
_read(APP_BUILD_ROOT / "references/type-registry.json"))["types"]}
|
|
359
360
|
for plugin_type in re.findall(r"`(report[a-z]+)`", routing):
|
|
360
361
|
with self.subTest(type=plugin_type):
|
|
361
|
-
self.assertIn(
|
|
362
|
-
f"routing offers type {plugin_type}
|
|
362
|
+
self.assertIn(plugin_type, types,
|
|
363
|
+
f"routing offers type {plugin_type} absent from the registry")
|
|
363
364
|
|
|
364
365
|
def test_left_tree_type_is_described_by_its_real_signature(self):
|
|
365
366
|
routing = _read(APP_BUILD_ROOT / "references/plugin-runtime-routing.md")
|