@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
|
@@ -1,23 +1,20 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
`references/type-registry.json` is the authority — "downstream docs and prompts should resolve
|
|
4
|
-
per-type behavior from this registry". `cosmic-coding-impl`'s mapping table resolves the same
|
|
5
|
-
classSuffix and guide independently, and each guide's `## Skeleton Template` states which type it
|
|
6
|
-
serves. Every drift below reached delivery: `report-plugin` carried one suffix in the table and
|
|
7
|
-
another in the registry, the registry sent the three mobile types to a guide with no skeleton at
|
|
8
|
-
all, four guides declared a type name the registry does not have, and form-plugin.md used
|
|
9
|
-
`EventObject` in five places while its import block never named it.
|
|
10
|
-
|
|
11
|
-
Every check is narrow on purpose. A guess at what counts as a drift reports a finding nobody can
|
|
12
|
-
fix, and a false finding here blocks delivery while a missed one costs one compile round.
|
|
13
|
-
"""
|
|
1
|
+
"""The type registry drives coding evidence and each guide's supported skeletons."""
|
|
14
2
|
|
|
15
3
|
import json
|
|
4
|
+
import os
|
|
16
5
|
import re
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
17
9
|
from pathlib import Path
|
|
18
10
|
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
19
13
|
|
|
20
14
|
ROOT = Path(__file__).resolve().parents[2]
|
|
15
|
+
# Target SDK JARs (bos and trd) joined with the path separator. The compile check below runs only
|
|
16
|
+
# when it is set; javac comes from JAVA_HOME when set, otherwise PATH, and must accept -source 8.
|
|
17
|
+
SDK_CLASSPATH = os.environ.get("COSMIC_SDK_PLUGIN_CLASSPATH", "")
|
|
21
18
|
# This file is byte-identical in both skill trees, so it must work from either: the locale tree has
|
|
22
19
|
# no nested locale tree. From the English root both are checked, from the zh root only that one.
|
|
23
20
|
TREES = [ROOT] + [tree for tree in [ROOT / ".locales" / "zh"] if tree.is_dir()]
|
|
@@ -48,8 +45,6 @@ _DECLARED_TYPES_RE = re.compile(
|
|
|
48
45
|
r"(?:Applicable to|Applies to|适用于|适用)\s*[::]\s*((?:`[\w-]+`(?:\s*/\s*)?)+)")
|
|
49
46
|
_BACKTICKED_RE = re.compile(r"`([\w-]+)`")
|
|
50
47
|
_STATED_SUFFIX_RE = re.compile(r"(?:suffix follows|类名后缀按)\s*`([A-Za-z{][\w{}]*)`")
|
|
51
|
-
_TABLE_ROW_RE = re.compile(r"^\|\s*`([\w-]+)`\s*\|\s*`([A-Za-z]\w*)`\s*\|\s*(.+?)\s*\|\s*$",
|
|
52
|
-
re.MULTILINE)
|
|
53
48
|
|
|
54
49
|
|
|
55
50
|
def _registry(tree):
|
|
@@ -59,17 +54,10 @@ def _registry(tree):
|
|
|
59
54
|
|
|
60
55
|
|
|
61
56
|
def _guides(tree):
|
|
62
|
-
"""Every distinct guide a type points at
|
|
63
|
-
table records "no guide; every required API needs exact documentation" for it."""
|
|
57
|
+
"""Every distinct guide a type points at; service has no guide."""
|
|
64
58
|
return sorted({entry["guide"] for entry in _registry(tree).values() if entry.get("guide")})
|
|
65
59
|
|
|
66
60
|
|
|
67
|
-
def _mapping_table(tree):
|
|
68
|
-
"""type -> (classSuffix, guide) as cosmic-coding-impl's own table declares them."""
|
|
69
|
-
text = (tree / "skills" / "cosmic-coding-impl" / "SKILL.md").read_text(encoding="utf-8")
|
|
70
|
-
return {name: (suffix, guide.strip("`")) for name, suffix, guide in _TABLE_ROW_RE.findall(text)}
|
|
71
|
-
|
|
72
|
-
|
|
73
61
|
def _skeletons(tree, guide):
|
|
74
62
|
"""The java blocks under the skeleton heading that declare a type.
|
|
75
63
|
|
|
@@ -109,26 +97,27 @@ def test_a_guide_declares_the_type_name_the_registry_uses():
|
|
|
109
97
|
assert not findings, "guides naming a type absent from the registry: " + "; ".join(findings)
|
|
110
98
|
|
|
111
99
|
|
|
112
|
-
def
|
|
113
|
-
"""Alias types stay in both places on purpose: `form_plugin` is dispatched by formtemplate and
|
|
114
|
-
dynamic_form_emit, so dropping it from the table would break those two paths."""
|
|
100
|
+
def test_coding_evidence_resolves_every_registered_type_without_a_second_mapping():
|
|
115
101
|
for tree in TREES:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
102
|
+
result = subprocess.run(
|
|
103
|
+
[sys.executable, "-c",
|
|
104
|
+
"import json; from platform_api_contract import plugin_coding_evidence, TYPE_REGISTRY_PAGE; "
|
|
105
|
+
"entries=json.loads(TYPE_REGISTRY_PAGE.read_text())['types']; "
|
|
106
|
+
"print(json.dumps({e['type']:plugin_coding_evidence(e['type']) for e in entries}))"],
|
|
107
|
+
cwd=tree / "scripts", capture_output=True, text=True, check=True,
|
|
122
108
|
)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
109
|
+
evidence = json.loads(result.stdout)
|
|
110
|
+
for name, entry in _registry(tree).items():
|
|
111
|
+
actual = evidence[name]
|
|
112
|
+
if entry.get("guide"):
|
|
113
|
+
assert Path(actual["guide"]).resolve() == (tree / entry["guide"]).resolve()
|
|
114
|
+
assert Path(actual["guide"]).is_file()
|
|
115
|
+
else:
|
|
116
|
+
assert actual["guide"] is None
|
|
117
|
+
if entry.get("baseImport"):
|
|
118
|
+
assert actual["baseContract"]["class"] == entry["baseImport"]
|
|
119
|
+
assert Path(actual["typeKnowledge"]).is_file()
|
|
120
|
+
assert Path(actual["standards"]).is_file()
|
|
132
121
|
|
|
133
122
|
|
|
134
123
|
def test_a_guide_does_not_hardcode_a_class_suffix_that_contradicts_the_registry():
|
|
@@ -155,8 +144,7 @@ def test_a_guide_does_not_hardcode_a_class_suffix_that_contradicts_the_registry(
|
|
|
155
144
|
|
|
156
145
|
|
|
157
146
|
def test_no_skeleton_uses_a_jdk_type_it_does_not_import():
|
|
158
|
-
"""
|
|
159
|
-
skeleton using a type it never imports leaves the correct import with no legal source."""
|
|
147
|
+
"""A usable skeleton must import each non-implicit JDK type it uses."""
|
|
160
148
|
findings = []
|
|
161
149
|
for tree in TREES:
|
|
162
150
|
for guide in _guides(tree):
|
|
@@ -183,3 +171,59 @@ def test_only_the_one_known_guide_publishes_no_skeleton():
|
|
|
183
171
|
|
|
184
172
|
assert without == sorted(f"{tree.name}:knowledge/cosmic/platform/task-plugin.md"
|
|
185
173
|
for tree in TREES), without
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _javac():
|
|
177
|
+
home = os.environ.get("JAVA_HOME")
|
|
178
|
+
candidate = Path(home) / "bin" / ("javac.exe" if os.name == "nt" else "javac") if home else None
|
|
179
|
+
return str(candidate) if candidate and candidate.is_file() else shutil.which("javac")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _instantiated(entry, block):
|
|
183
|
+
"""The skeleton as the coding step fills it for one registry type.
|
|
184
|
+
|
|
185
|
+
The placeholder table tells the coding step to substitute the registry's suffix and base class
|
|
186
|
+
and to import that base; package placeholders become a fixed sample package.
|
|
187
|
+
"""
|
|
188
|
+
base = entry["baseImport"]
|
|
189
|
+
source = block.replace("{PluginSuffix}", entry["classSuffix"]).replace("{BaseClass}", base.rsplit(".", 1)[-1])
|
|
190
|
+
source = re.sub(r"\{\w+\}", "sample", source)
|
|
191
|
+
if "{BaseClass}" in block:
|
|
192
|
+
source = re.sub(r"^(package [^;]+;)", rf"\1\nimport {base};", source, count=1, flags=re.MULTILINE)
|
|
193
|
+
return source
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@pytest.mark.skipif(not SDK_CLASSPATH or not _javac(), reason="target SDK classpath or javac unavailable")
|
|
197
|
+
def test_every_skeleton_compiles_against_the_target_sdk(tmp_path):
|
|
198
|
+
"""The coding step copies a skeleton's imports and event signatures as verified evidence, and
|
|
199
|
+
the API pages cannot prove a skeleton right: list-plugin.md imported two event classes that do
|
|
200
|
+
not exist (`ListPackageDataEvent`, `PrepareFilterEvent`), form-plugin.md and list-plugin.md
|
|
201
|
+
imported `ShowType` from a package that does not exist, the shared form skeleton overrode
|
|
202
|
+
`afterLoadData` that only bill bases declare, and the workflow skeleton called `super` on
|
|
203
|
+
interface methods. Each compiled nowhere; only the target SDK shows it."""
|
|
204
|
+
failures = []
|
|
205
|
+
for tree in TREES:
|
|
206
|
+
seen = set()
|
|
207
|
+
for entry in _registry(tree).values():
|
|
208
|
+
base = str(entry.get("baseImport") or "")
|
|
209
|
+
if not entry.get("guide") or not base or "," in base or str(entry.get("baseClass", "")).startswith("@"):
|
|
210
|
+
continue
|
|
211
|
+
blocks = _skeletons(tree, entry["guide"])
|
|
212
|
+
if not blocks or (entry["guide"], base) in seen:
|
|
213
|
+
continue
|
|
214
|
+
seen.add((entry["guide"], base))
|
|
215
|
+
source = _instantiated(entry, blocks[0])
|
|
216
|
+
name = re.search(r"^\s*public\s+(?:(?:abstract|final)\s+)*(?:class|interface)\s+(\w+)",
|
|
217
|
+
source, re.MULTILINE).group(1)
|
|
218
|
+
work = tmp_path / f"{tree.name}-{entry['type']}"
|
|
219
|
+
(work / "classes").mkdir(parents=True)
|
|
220
|
+
(work / f"{name}.java").write_text(source, encoding="utf-8")
|
|
221
|
+
result = subprocess.run(
|
|
222
|
+
[_javac(), "-J-Duser.language=en", "-source", "8", "-target", "8", "-proc:none", "-nowarn",
|
|
223
|
+
"-encoding", "UTF-8", "-cp", SDK_CLASSPATH, "-d", str(work / "classes"), str(work / f"{name}.java")],
|
|
224
|
+
capture_output=True, text=True)
|
|
225
|
+
if result.returncode:
|
|
226
|
+
errors = re.findall(r"\.java:\d+: error: .*", result.stderr) or [result.stderr.strip()[:300]]
|
|
227
|
+
failures.append(f"{tree.name}:{entry['guide']} as {entry['type']}: " + "; ".join(errors[:3]))
|
|
228
|
+
|
|
229
|
+
assert not failures, "skeletons that do not compile against the target SDK:\n" + "\n".join(failures)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import json
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import zipfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
SCRIPT = Path(__file__).resolve().parents[1] / "inspect_platform_sdk.py"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def sdk(tmp_path):
|
|
16
|
+
if not shutil.which("javac") or not shutil.which("javap"):
|
|
17
|
+
pytest.skip("JDK tools unavailable")
|
|
18
|
+
source = tmp_path / "Target.java"
|
|
19
|
+
source.write_text('package sample; public class Target { public String getOperationKey() { return "unit.operation"; } }')
|
|
20
|
+
subprocess.run(["javac", "-d", str(tmp_path), str(source)], check=True, capture_output=True)
|
|
21
|
+
jar = tmp_path / "sdk.jar"
|
|
22
|
+
with zipfile.ZipFile(jar, "w") as archive:
|
|
23
|
+
archive.write(tmp_path / "sample/Target.class", "sample/Target.class")
|
|
24
|
+
archive.writestr("META-INF/MANIFEST.MF", "Manifest-Version: 1.0\nImplementation-Version: test-1\n")
|
|
25
|
+
archive.writestr("sample/Target.java", source.read_text())
|
|
26
|
+
return jar
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def query(jar, *args):
|
|
30
|
+
result = subprocess.run([sys.executable, str(SCRIPT), "--jar", str(jar), *args],
|
|
31
|
+
capture_output=True, text=True)
|
|
32
|
+
return result.returncode, json.loads(result.stdout)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_sdk_inspection_uses_real_class_declarations_and_selected_bytecode(sdk):
|
|
36
|
+
code, result = query(sdk, "--class", "Target", "--member", "getOperationKey")
|
|
37
|
+
assert code == 0
|
|
38
|
+
assert result["class"] == "sample.Target"
|
|
39
|
+
assert result["version"] == "test-1"
|
|
40
|
+
assert result["jarSha256"] == hashlib.sha256(sdk.read_bytes()).hexdigest()
|
|
41
|
+
assert result["members"][0]["declaration"] == "public java.lang.String getOperationKey();"
|
|
42
|
+
assert result["missingDeclaredMembers"] == []
|
|
43
|
+
code, result = query(sdk, "--class", "sample.Target", "--member", "getOperationKey", "--mode", "bytecode")
|
|
44
|
+
assert code == 0
|
|
45
|
+
assert any("unit.operation" in line for line in result["members"][0]["details"])
|
|
46
|
+
assert len(result["members"]) == 1
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_sdk_lookup_does_not_guess_a_duplicate_version_or_an_absent_method(sdk, tmp_path):
|
|
50
|
+
other = tmp_path / "duplicate.jar"
|
|
51
|
+
shutil.copyfile(sdk, other)
|
|
52
|
+
code, result = query(sdk, "--jar", str(other), "--class", "sample.Target")
|
|
53
|
+
assert code == 2 and result["errorCode"] == "SDK_CLASS_AMBIGUOUS"
|
|
54
|
+
assert len(result["candidates"]) == 2
|
|
55
|
+
code, result = query(sdk, "--class", "sample.Target", "--member", "notDeclared")
|
|
56
|
+
assert code == 0
|
|
57
|
+
assert result["members"] == [] and result["missingDeclaredMembers"] == ["notDeclared"]
|
|
58
|
+
code, result = query(sdk, "--class", "Missing")
|
|
59
|
+
assert code == 2 and result["errorCode"] == "SDK_CLASS_NOT_FOUND"
|
|
60
|
+
code, result = query(sdk, "--class", "sample.Target", "--mode", "bytecode")
|
|
61
|
+
assert code == 2 and result["errorCode"] == "SDK_MEMBER_REQUIRED"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_reference_jars_are_read_by_exact_entry_without_extracting_files(sdk):
|
|
65
|
+
code, result = query(sdk, "--list-resources", "Target")
|
|
66
|
+
assert code == 0 and result["items"][0]["resource"] == "sample/Target.java"
|
|
67
|
+
code, result = query(sdk, "--resource", "sample/Target.java", "--limit", "1")
|
|
68
|
+
assert code == 0 and "public class Target" in result["content"]
|
|
69
|
+
assert result["resourceSha256"] and result["lineCount"] == 1
|
|
70
|
+
code, result = query(sdk, "--resource", "sample/Target.class")
|
|
71
|
+
assert code == 2 and result["errorCode"] == "SDK_REFERENCE_NOT_UNIQUE"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Tests for kd_api.py —
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
- _resolve_base
|
|
6
|
-
- post / get
|
|
7
|
-
- _request_once
|
|
8
|
-
- _call_with_retry
|
|
9
|
-
- invalidate_cache
|
|
2
|
+
"""Tests for kd_api.py — business facade entry point.
|
|
3
|
+
|
|
4
|
+
Coverage:
|
|
5
|
+
- _resolve_base: base_url resolution priority / missing error
|
|
6
|
+
- post / get: URL construction, body/query building, header style, timeout passthrough
|
|
7
|
+
- _request_once: HTTP 401 / body business code 401 -> TokenExpiredError normalization; other HTTPError re-raised as-is
|
|
8
|
+
- _call_with_retry: success no refresh; TokenExpiredError force refresh retry once; consecutive two failures still re-raised
|
|
9
|
+
- invalidate_cache: delegates to kd_token_cache.invalidate(None)
|
|
10
10
|
"""
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
@@ -26,13 +26,13 @@ import kd_api
|
|
|
26
26
|
import kd_auth
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
# ───
|
|
29
|
+
# ─── Test doubles: replace _http_client.retry_call ──────────────────────────────
|
|
30
30
|
|
|
31
31
|
def _fake_retry(*behaviors):
|
|
32
|
-
"""
|
|
32
|
+
"""Construct a retry_call double.
|
|
33
33
|
|
|
34
|
-
behaviors:
|
|
35
|
-
|
|
34
|
+
behaviors: (response, exc) tuple for each call; reuse the last when exceeding length.
|
|
35
|
+
Returns (callable, calls) — calls records (fn_name, args, kwargs) for each invocation.
|
|
36
36
|
"""
|
|
37
37
|
calls = []
|
|
38
38
|
|
|
@@ -163,7 +163,7 @@ class GetTest(unittest.TestCase):
|
|
|
163
163
|
self.assertEqual(args[0], "https://x/g")
|
|
164
164
|
|
|
165
165
|
|
|
166
|
-
# ─── _request_once:401
|
|
166
|
+
# ─── _request_once:401 normalization ────────────────────────────────────────────
|
|
167
167
|
|
|
168
168
|
class RequestOnceAuthTest(unittest.TestCase):
|
|
169
169
|
def _call(self, cfg, retry_fake):
|
|
@@ -187,7 +187,7 @@ class RequestOnceAuthTest(unittest.TestCase):
|
|
|
187
187
|
self._call(cfg, retry)
|
|
188
188
|
|
|
189
189
|
def test_body_errorcode_401_raises_token_expired(self):
|
|
190
|
-
"""HTTP 200 + body errorCode=401
|
|
190
|
+
"""HTTP 200 + body errorCode=401 should also be normalized to TokenExpiredError."""
|
|
191
191
|
cfg = {"baseUrl": "https://x"}
|
|
192
192
|
retry, _ = _fake_retry(({"errorCode": "401"}, None))
|
|
193
193
|
with self.assertRaises(kd_auth.TokenExpiredError):
|
|
@@ -200,7 +200,7 @@ class RequestOnceAuthTest(unittest.TestCase):
|
|
|
200
200
|
self.assertEqual(resp, {"errorCode": "500"})
|
|
201
201
|
|
|
202
202
|
|
|
203
|
-
# ─── _call_with_retry
|
|
203
|
+
# ─── _call_with_retry:force refresh retry ──────────────────────────────────────────
|
|
204
204
|
|
|
205
205
|
class CallWithRetryTest(unittest.TestCase):
|
|
206
206
|
def test_success_no_force_refresh(self):
|
|
@@ -212,14 +212,14 @@ class CallWithRetryTest(unittest.TestCase):
|
|
|
212
212
|
resp = kd_api.post(None, "/p", cfg, {})
|
|
213
213
|
self.assertEqual(resp, {"ok": True})
|
|
214
214
|
self.assertEqual(m_get.call_count, 1)
|
|
215
|
-
#
|
|
215
|
+
# first call: get_token(cfg, base), without force
|
|
216
216
|
args, kwargs = m_get.call_args
|
|
217
217
|
self.assertEqual(args, (cfg, "https://x"))
|
|
218
218
|
self.assertFalse(kwargs.get("force"))
|
|
219
219
|
|
|
220
220
|
def test_force_refresh_on_token_expired(self):
|
|
221
221
|
cfg = {"baseUrl": "https://x"}
|
|
222
|
-
#
|
|
222
|
+
# first request raises TokenExpiredError (body 401), second succeeds
|
|
223
223
|
retry, _ = _fake_retry(
|
|
224
224
|
(None, kd_auth.TokenExpiredError("expired")),
|
|
225
225
|
({"ok": True}, None),
|
|
@@ -230,7 +230,7 @@ class CallWithRetryTest(unittest.TestCase):
|
|
|
230
230
|
resp = kd_api.post(None, "/p", cfg, {})
|
|
231
231
|
self.assertEqual(resp, {"ok": True})
|
|
232
232
|
self.assertEqual(m_get.call_count, 2)
|
|
233
|
-
#
|
|
233
|
+
# second call must have force=True
|
|
234
234
|
_, kwargs = m_get.call_args
|
|
235
235
|
self.assertTrue(kwargs.get("force"))
|
|
236
236
|
|
|
@@ -267,7 +267,7 @@ class InvalidateCacheTest(unittest.TestCase):
|
|
|
267
267
|
m_inv.assert_called_once_with(None)
|
|
268
268
|
|
|
269
269
|
def test_config_arg_currently_ignored(self):
|
|
270
|
-
"""
|
|
270
|
+
"""Current implementation ignores config, uniformly invalidate(None)."""
|
|
271
271
|
import kd_token_cache
|
|
272
272
|
with mock.patch.object(kd_token_cache, "invalidate") as m_inv:
|
|
273
273
|
kd_api.invalidate_cache({"baseUrl": "https://x"})
|
|
@@ -276,3 +276,51 @@ class InvalidateCacheTest(unittest.TestCase):
|
|
|
276
276
|
|
|
277
277
|
if __name__ == "__main__":
|
|
278
278
|
unittest.main()
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class WriteTransportBoundaryTest(unittest.TestCase):
|
|
282
|
+
def test_lost_post_response_is_not_replayed_but_get_can_retry(self):
|
|
283
|
+
import http.client
|
|
284
|
+
import socket
|
|
285
|
+
import threading
|
|
286
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
287
|
+
hits = {"POST": 0, "GET": 0}
|
|
288
|
+
|
|
289
|
+
class Handler(BaseHTTPRequestHandler):
|
|
290
|
+
def log_message(self, *args):
|
|
291
|
+
pass
|
|
292
|
+
|
|
293
|
+
def do_POST(self):
|
|
294
|
+
self.rfile.read(int(self.headers.get("Content-Length", "0")))
|
|
295
|
+
hits["POST"] += 1
|
|
296
|
+
self.connection.shutdown(socket.SHUT_RDWR)
|
|
297
|
+
self.connection.close()
|
|
298
|
+
|
|
299
|
+
def do_GET(self):
|
|
300
|
+
hits["GET"] += 1
|
|
301
|
+
if hits["GET"] == 1:
|
|
302
|
+
self.connection.shutdown(socket.SHUT_RDWR)
|
|
303
|
+
self.connection.close()
|
|
304
|
+
return
|
|
305
|
+
body = b'{"status":true,"data":[]}'
|
|
306
|
+
self.send_response(200)
|
|
307
|
+
self.send_header("Content-Length", str(len(body)))
|
|
308
|
+
self.end_headers()
|
|
309
|
+
self.wfile.write(body)
|
|
310
|
+
|
|
311
|
+
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
|
312
|
+
worker = threading.Thread(target=server.serve_forever, daemon=True)
|
|
313
|
+
worker.start()
|
|
314
|
+
try:
|
|
315
|
+
with mock.patch.object(_http_client, "_RETRY_DELAY", 0):
|
|
316
|
+
url = f"http://127.0.0.1:{server.server_port}/write"
|
|
317
|
+
with self.assertRaises(http.client.RemoteDisconnected):
|
|
318
|
+
kd_api._request_once(url, {}, "fixture", method="POST", body={}, params=None, style="kapi")
|
|
319
|
+
self.assertEqual(hits["POST"], 1)
|
|
320
|
+
result = kd_api._request_once(url, {}, "fixture", method="GET", body=None, params=None, style="kapi")
|
|
321
|
+
self.assertTrue(result["status"])
|
|
322
|
+
self.assertEqual(hits["GET"], 2)
|
|
323
|
+
finally:
|
|
324
|
+
server.shutdown()
|
|
325
|
+
server.server_close()
|
|
326
|
+
worker.join()
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import zipfile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
SCRIPTS = Path(__file__).resolve().parents[1]
|
|
10
|
+
sys.path.insert(0, str(SCRIPTS))
|
|
11
|
+
|
|
12
|
+
import kingscript_sdk_sync as sdk_sync # noqa: E402
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
PKG_FILE = (
|
|
16
|
+
"export declare type AbstractFormPlugin = $.kd.bos.form.plugin.AbstractFormPlugin;\n"
|
|
17
|
+
"export declare const AbstractFormPlugin: $.kd.bos.form.plugin.AbstractFormPlugin_C;\n"
|
|
18
|
+
"export declare type AbstractMobFormPlugin = $.kd.bos.form.plugin.AbstractMobFormPlugin;\n"
|
|
19
|
+
"export declare interface AfterBindDataArgs { }\n"
|
|
20
|
+
"export declare const FormUtil: $.kd.bos.form.plugin.FormUtil_C;\n"
|
|
21
|
+
"export declare const enum Color { Red }\n"
|
|
22
|
+
)
|
|
23
|
+
NESTED_PKG_FILE = (
|
|
24
|
+
"/// <reference types=\"../../index\" />\n"
|
|
25
|
+
"export declare type QFilter = $.kd.bos.orm.query.QFilter;\n"
|
|
26
|
+
)
|
|
27
|
+
AGGREGATE_FILE = (
|
|
28
|
+
"declare global {\n"
|
|
29
|
+
" namespace $ {\n"
|
|
30
|
+
" namespace kd.bos.form.plugin {\n"
|
|
31
|
+
" interface AbstractFormPlugin$ {\n"
|
|
32
|
+
" afterBindData?(e: any): void;\n"
|
|
33
|
+
" }\n"
|
|
34
|
+
" }\n"
|
|
35
|
+
" }\n"
|
|
36
|
+
"}\n"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _make_zip(path: Path) -> Path:
|
|
41
|
+
with zipfile.ZipFile(path, "w") as bundle:
|
|
42
|
+
bundle.writestr("@cosmic/", "")
|
|
43
|
+
bundle.writestr("@cosmic/bos-core/", "")
|
|
44
|
+
bundle.writestr("@cosmic/bos-core/kd/bos/form/plugin.d.ts", PKG_FILE)
|
|
45
|
+
bundle.writestr("@cosmic/bos-core/kd/bos/orm/query.d.ts", NESTED_PKG_FILE)
|
|
46
|
+
bundle.writestr("@cosmic/bos-core/bos-form-core.d.ts", AGGREGATE_FILE)
|
|
47
|
+
bundle.writestr("@cosmic/bos-script/java/math.d.ts",
|
|
48
|
+
"export declare type BigDecimal = $.java.math.BigDecimal;\n")
|
|
49
|
+
return path
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ─── type index build ─────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
def test_build_type_index_extracts_all_exports_per_package_file(tmp_path):
|
|
55
|
+
rows, dts_count = sdk_sync.build_type_index(_make_zip(tmp_path / "sdk.zip"))
|
|
56
|
+
by_type = {row.split("\t")[0]: row.split("\t") for row in rows}
|
|
57
|
+
# MULTILINE regression guard: every export line of a package file must be
|
|
58
|
+
# indexed, not just the first one.
|
|
59
|
+
assert by_type["AbstractFormPlugin"] == [
|
|
60
|
+
"AbstractFormPlugin", "@cosmic/bos-core", "kd/bos/form/plugin", "kd.bos.form.plugin"]
|
|
61
|
+
assert by_type["AbstractMobFormPlugin"][3] == "kd.bos.form.plugin"
|
|
62
|
+
# interface declarations and const-only value exports are importable names
|
|
63
|
+
# and must be indexed too (HttpClient-style exports on real environments).
|
|
64
|
+
assert by_type["AfterBindDataArgs"][2] == "kd/bos/form/plugin"
|
|
65
|
+
assert by_type["FormUtil"][1] == "@cosmic/bos-core"
|
|
66
|
+
# same-name type + const pair yields exactly one row (per-file dedup)
|
|
67
|
+
assert sum(1 for row in rows if row.startswith("AbstractFormPlugin\t")) == 1
|
|
68
|
+
# `export declare const enum X` must not leak the keyword as a name
|
|
69
|
+
assert "Color" not in by_type and "enum" not in by_type
|
|
70
|
+
assert by_type["QFilter"][2] == "kd/bos/orm/query"
|
|
71
|
+
assert by_type["BigDecimal"] == [
|
|
72
|
+
"BigDecimal", "@cosmic/bos-script", "java/math", "java.math"]
|
|
73
|
+
assert dts_count == 4
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_build_type_index_skips_module_root_aggregates(tmp_path):
|
|
77
|
+
rows, _ = sdk_sync.build_type_index(_make_zip(tmp_path / "sdk.zip"))
|
|
78
|
+
types = {row.split("\t")[0] for row in rows}
|
|
79
|
+
assert types == {"AbstractFormPlugin", "AbstractMobFormPlugin", "QFilter",
|
|
80
|
+
"BigDecimal", "AfterBindDataArgs", "FormUtil"}
|
|
81
|
+
# the aggregate declares interfaces, never `export declare` rows
|
|
82
|
+
assert all("AbstractFormPlugin$" not in t for t in types)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ─── zip member safety ────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
def test_safe_member_name_rejects_traversal():
|
|
88
|
+
for bad in ("../evil.d.ts", "a/../../evil.d.ts", "/abs/evil.d.ts", "C:/evil.d.ts"):
|
|
89
|
+
with pytest.raises(RuntimeError):
|
|
90
|
+
sdk_sync._safe_member_name(bad)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_safe_member_name_normalizes_segments():
|
|
94
|
+
assert sdk_sync._safe_member_name("a//b/./c.d.ts") == "a/b/c.d.ts"
|
|
95
|
+
assert sdk_sync._safe_member_name("kd/bos/form/") == "kd/bos/form"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ─── cache state / TTL ────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
def _write_cache_with_age(cache: Path, age_hours: float, types: int = 10) -> None:
|
|
101
|
+
cache.mkdir(parents=True, exist_ok=True)
|
|
102
|
+
(cache / "sdk-meta.json").write_text(json.dumps({
|
|
103
|
+
"generatedAt": int(time.time() - age_hours * 3600),
|
|
104
|
+
"envName": cache.name, "baseUrl": "https://unit.test/ierp",
|
|
105
|
+
"modules": 1, "dtsFiles": 2, "types": types,
|
|
106
|
+
}), encoding="utf-8")
|
|
107
|
+
(cache / "type-index.tsv").write_text("X\t@m/a\ta/b\ta.b\n", encoding="utf-8")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_cache_state_missing(tmp_path):
|
|
111
|
+
state = sdk_sync._cache_state(tmp_path / "none", 168)
|
|
112
|
+
assert state["state"] == "missing"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_cache_state_fresh_and_stale_boundary(tmp_path):
|
|
116
|
+
cache = tmp_path / "env"
|
|
117
|
+
_write_cache_with_age(cache, age_hours=2)
|
|
118
|
+
assert sdk_sync._cache_state(cache, 168)["state"] == "fresh"
|
|
119
|
+
assert sdk_sync._cache_state(cache, 1)["state"] == "stale"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_cache_state_survives_corrupt_meta(tmp_path):
|
|
123
|
+
cache = tmp_path / "env"
|
|
124
|
+
cache.mkdir(parents=True)
|
|
125
|
+
(cache / "sdk-meta.json").write_text("{ not json", encoding="utf-8")
|
|
126
|
+
assert sdk_sync._cache_state(cache, 168)["state"] == "missing"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ─── lookup ───────────────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
def test_lookup_found_and_miss(tmp_path, capsys):
|
|
132
|
+
cache = tmp_path / "env"
|
|
133
|
+
_write_cache_with_age(cache, age_hours=0)
|
|
134
|
+
lines = "A\t@m/x\tx/a\tx.a\nA\t@m/y\ty/a\ty.a\nB\t@m/x\tx/b\tx.b\n"
|
|
135
|
+
(cache / "type-index.tsv").write_text(lines, encoding="utf-8")
|
|
136
|
+
monkey = pytest.MonkeyPatch()
|
|
137
|
+
monkey.setattr(sdk_sync, "_cache_dir", lambda env: cache)
|
|
138
|
+
try:
|
|
139
|
+
assert sdk_sync.do_lookup("env", "A") == 0
|
|
140
|
+
found = json.loads(capsys.readouterr().out)
|
|
141
|
+
assert found["status"] == "found"
|
|
142
|
+
assert len(found["matches"]) == 2
|
|
143
|
+
assert found["matches"][0]["packageDot"] == "x.a"
|
|
144
|
+
assert sdk_sync.do_lookup("env", "B") == 0
|
|
145
|
+
capsys.readouterr() # drain the intermediate output
|
|
146
|
+
assert sdk_sync.do_lookup("env", "Missing") == 2
|
|
147
|
+
miss = json.loads(capsys.readouterr().out)
|
|
148
|
+
assert miss["status"] == "miss"
|
|
149
|
+
finally:
|
|
150
|
+
monkey.undo()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_lookup_without_cache_fails(tmp_path, capsys):
|
|
154
|
+
monkey = pytest.MonkeyPatch()
|
|
155
|
+
monkey.setattr(sdk_sync, "_cache_dir", lambda env: tmp_path / "empty")
|
|
156
|
+
try:
|
|
157
|
+
assert sdk_sync.do_lookup("env", "A") == 2
|
|
158
|
+
result = json.loads(capsys.readouterr().out)
|
|
159
|
+
assert result["errorCode"] == "KINGSCRIPT_SDK_CACHE_MISSING"
|
|
160
|
+
finally:
|
|
161
|
+
monkey.undo()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# ─── ensure ───────────────────────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
def test_ensure_skips_sync_when_fresh(tmp_path, monkeypatch, capsys):
|
|
167
|
+
cache = tmp_path / "env"
|
|
168
|
+
_write_cache_with_age(cache, age_hours=1)
|
|
169
|
+
monkeypatch.setattr(sdk_sync, "_cache_dir", lambda env: cache)
|
|
170
|
+
|
|
171
|
+
def _boom(*_args, **_kwargs):
|
|
172
|
+
raise AssertionError("sync must not run when cache is fresh")
|
|
173
|
+
|
|
174
|
+
monkeypatch.setattr(sdk_sync, "do_sync", _boom)
|
|
175
|
+
assert sdk_sync.do_ensure(server={}, env={"envName": "env"}, ttl_hours=168) == 0
|
|
176
|
+
result = json.loads(capsys.readouterr().out)
|
|
177
|
+
assert result["status"] == "ready"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_ensure_triggers_sync_when_missing(tmp_path, monkeypatch):
|
|
181
|
+
monkeypatch.setattr(sdk_sync, "_cache_dir", lambda env: tmp_path / "none")
|
|
182
|
+
calls = []
|
|
183
|
+
monkeypatch.setattr(sdk_sync, "do_sync",
|
|
184
|
+
lambda server, env: (calls.append(env), 0)[1])
|
|
185
|
+
assert sdk_sync.do_ensure(server={}, env={"envName": "env"}, ttl_hours=168) == 0
|
|
186
|
+
assert calls == [{"envName": "env"}]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# ─── cache write ──────────────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
def test_write_cache_swaps_atomically_and_reports_counts(tmp_path, monkeypatch):
|
|
192
|
+
zip_path = _make_zip(tmp_path / "sdk.zip")
|
|
193
|
+
monkeypatch.setattr(sdk_sync, "SDK_ROOT", tmp_path / "sdkroot")
|
|
194
|
+
cache = tmp_path / "sdkroot" / "env"
|
|
195
|
+
cache.parent.mkdir(parents=True)
|
|
196
|
+
_write_cache_with_age(cache, age_hours=999, types=1) # stale old cache
|
|
197
|
+
meta = sdk_sync._write_cache(cache, zip_path, {"envName": "env"},
|
|
198
|
+
{"baseUrl": "https://unit.test/ierp"}, "deadbeef")
|
|
199
|
+
assert meta["types"] == 6
|
|
200
|
+
assert meta["modules"] == 2
|
|
201
|
+
assert meta["dtsFiles"] == 4
|
|
202
|
+
assert meta["zipSha256"] == "deadbeef"
|
|
203
|
+
# old .old backup is gone, new index and dts tree are in place
|
|
204
|
+
assert not (tmp_path / "sdkroot" / "env.old").exists()
|
|
205
|
+
assert (cache / "type-index.tsv").read_text(encoding="utf-8").count("\n") == 6
|
|
206
|
+
assert (cache / "dts" / "@cosmic" / "bos-core" / "kd" / "bos" / "form" /
|
|
207
|
+
"plugin.d.ts").is_file()
|
|
208
|
+
# meta json is valid and carries identity
|
|
209
|
+
stored = json.loads((cache / "sdk-meta.json").read_text(encoding="utf-8"))
|
|
210
|
+
assert stored["baseUrl"] == "https://unit.test/ierp"
|
|
211
|
+
assert stored["envName"] == "env"
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def test_write_cache_creates_from_scratch(tmp_path, monkeypatch):
|
|
215
|
+
zip_path = _make_zip(tmp_path / "sdk.zip")
|
|
216
|
+
cache = tmp_path / "sdkroot" / "fresh-env"
|
|
217
|
+
cache.parent.mkdir(parents=True)
|
|
218
|
+
meta = sdk_sync._write_cache(cache, zip_path, {"envName": "fresh-env"},
|
|
219
|
+
{"baseUrl": "https://unit.test/ierp"}, "abc")
|
|
220
|
+
assert meta["types"] == 6
|
|
221
|
+
assert (cache / "dts" / "@cosmic" / "bos-script" / "java" / "math.d.ts").is_file()
|
|
@@ -43,6 +43,16 @@ def test_clean_plugin_passes():
|
|
|
43
43
|
assert "KS-EXPORT-CONTRACT" not in _warn_rules(result)
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
def test_kwc_controller_uses_its_native_export_binding():
|
|
47
|
+
source = "class CoffeeController {}\nlet kwcController = new CoffeeController(); export { kwcController };"
|
|
48
|
+
result = _static(source, target_type="KWC_CONTROLLER")
|
|
49
|
+
assert result["status"] is True
|
|
50
|
+
assert "KS-EXPORT-CONTRACT" not in _warn_rules(result)
|
|
51
|
+
wrong = _static(CLEAN_PLUGIN, target_type="KWC_CONTROLLER")
|
|
52
|
+
assert "KS-EXPORT-CONTRACT" in _warn_rules(wrong)
|
|
53
|
+
assert "kwcController" in next(x["message"] for x in wrong["warnings"] if x["rule"] == "KS-EXPORT-CONTRACT")
|
|
54
|
+
|
|
55
|
+
|
|
46
56
|
def test_empty_content_is_blocked():
|
|
47
57
|
result = _static(" ")
|
|
48
58
|
assert result["status"] is False
|