hap-cli 0.8.0__tar.gz → 0.8.2__tar.gz
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.
- {hap_cli-0.8.0 → hap_cli-0.8.2}/PKG-INFO +1 -1
- {hap_cli-0.8.0 → hap_cli-0.8.2}/app_live_tests/smoke.py +179 -139
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/__init__.py +1 -1
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/app_cmd.py +52 -13
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/app_editor_cmd.py +6 -2
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/chart_cmd.py +5 -6
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/config_cmd.py +19 -2
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/contact_cmd.py +1 -1
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/group_cmd.py +5 -5
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/instance_cmd.py +43 -10
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/node_cmd.py +46 -15
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/optionset_cmd.py +1 -1
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/page_cmd.py +9 -6
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/post_cmd.py +3 -3
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/record_cmd.py +162 -36
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/role_cmd.py +44 -23
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/upload_cmd.py +1 -1
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/v3_registry.py +8 -4
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/workflow_cmd.py +44 -6
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/worksheet_cmd.py +40 -23
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/action_spec_adapter.py +14 -1
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/app.py +140 -17
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/auth.py +199 -33
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/chart.py +5 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/chat.py +20 -19
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/editspec.py +48 -13
- hap_cli-0.8.2/hap_cli/core/editor/ops.py +188 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/filter_translator.py +74 -28
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/global_meta.py +25 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/group.py +4 -1
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/instance.py +54 -1
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/page.py +9 -3
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/record.py +268 -27
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/role.py +78 -44
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/session.py +14 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/v3/dispatcher.py +23 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/view_spec_adapter.py +91 -12
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/workflow_node_dsl.py +806 -43
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/worksheet.py +93 -5
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/conftest.py +52 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_app_editor.py +55 -53
- hap_cli-0.8.2/hap_cli/tests/test_app_restore.py +123 -0
- hap_cli-0.8.2/hap_cli/tests/test_bug_036430.py +167 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_chart.py +14 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_config_cmd.py +23 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_core.py +506 -45
- hap_cli-0.8.2/hap_cli/tests/test_destructive_confirm_cli.py +173 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_worksheet_extra.py +8 -4
- hap_cli-0.8.2/hap_cli/tests/test_org_approval_v3_semantic_cli.py +261 -0
- hap_cli-0.8.2/hap_cli/tests/test_record_batch_v3_semantic_cli.py +269 -0
- hap_cli-0.8.2/hap_cli/tests/test_record_v3_semantic_cli.py +94 -0
- hap_cli-0.8.2/hap_cli/tests/test_semantic_types_registry.py +92 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_v3_session.py +25 -0
- hap_cli-0.8.2/hap_cli/tests/test_workflow_v3_semantic_cli.py +651 -0
- hap_cli-0.8.2/hap_cli/tests/test_worksheet_v3_semantic_cli.py +350 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli.egg-info/PKG-INFO +1 -1
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli.egg-info/SOURCES.txt +9 -8
- hap_cli-0.8.2/skills/hap-cli-app-editor/scripts/editspec/custom-action.schema.json +75 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/skills/hap-cli-app-editor/scripts/editspec/envelope.schema.json +11 -34
- hap_cli-0.8.0/hap_cli/core/editor/ops.py +0 -543
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/application.schema.json +0 -59
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/chatbot.schema.json +0 -52
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/custom-action.schema.json +0 -55
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/custom-page.schema.json +0 -49
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/node.schema.json +0 -73
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/role.schema.json +0 -90
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/view.schema.json +0 -64
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/workflow.schema.json +0 -60
- hap_cli-0.8.0/skills/hap-cli-app-editor/scripts/editspec/worksheet.schema.json +0 -51
- {hap_cli-0.8.0 → hap_cli-0.8.2}/MANIFEST.in +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/README.md +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/app_live_tests/__init__.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/app_live_tests/__main__.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/app_live_tests/config.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/app_live_tests/harness.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/app_live_tests/state.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/README.md +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/README_CN.md +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/auth_cmd.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/calendar_cmd.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/chat_cmd.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/chatbot_cmd.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/department_cmd.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/icon_cmd.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/commands/region_cmd.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/context.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/calendar_mod.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/chatbot.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/contact.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/control_type_codes.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/department.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/__init__.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/apply.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/componentlower.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/errors.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/fieldlower.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/jsonschema_mini.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/models.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/planner.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/editor/reader.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/field_normalizer.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/field_spec_adapter.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/flow_node.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/icon_index.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/logger.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/post.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/role_perm_builder.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/token_crypto.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/upload.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/v3/__init__.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/v3/render.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/v3/schema.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/core/worksheet_templates.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/hap_cli.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/i18n.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/locale/__init__.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/locale/messages.json +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/locale/messages.schema.json +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_app_export_usage.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_auth_prerelease.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_full_e2e.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_global_meta.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_i18n.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_approval.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_approval_actions.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_misc.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_social.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_v3.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_multi_profile.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_org_id_cli.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_v3_dispatcher.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli.egg-info/requires.txt +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/__init__.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/__main__.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/_wftest.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/charts.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/cleanup.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/compiler.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/config.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/errors.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/executor.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/fields.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/hap.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/recording/__init__.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/recording/console.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/recording/jsonl.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/recording/mirror.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/recording/report.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/schema.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/seed/__init__.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/seed/cli.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/seed/executor.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/seed/template.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/selftest.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/smoke.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/step.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/steps.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/store.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/live_tests/workflow_dsl.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/setup.cfg +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/setup.py +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/skills/hap-cli-app-editor/scripts/editspec/component.schema.json +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/skills/hap-cli-app-editor/scripts/editspec/field.schema.json +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/INDEX.json +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/create_optionset.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/create_role.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/delete_optionset.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/delete_record.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/delete_role.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_app_info.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_record_details.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_record_list.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_record_logs.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_record_relations.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_regions.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_role_details.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/get_role_list.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/knowledge_search.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
- {hap_cli-0.8.0 → hap_cli-0.8.2}/sources/v3-api-schema/update_optionset.yaml +0 -0
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
"""Live smoke scenarios for the editor, run against the persistent app.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
"""Live smoke scenarios for the editor + bare CLI, run against the persistent app.
|
|
2
|
+
|
|
3
|
+
Edit-spec scenarios (field/component/custom-action) drive the real editor
|
|
4
|
+
entry point; everything else uses the bare ``hap`` commands that replaced
|
|
5
|
+
the retired edit-spec ops. Each scenario asserts the result by reading back
|
|
6
|
+
through ``hap``. Scenarios create their own worksheet and delete it at the
|
|
7
|
+
end, so the persistent app stays clean and the suite is re-runnable.
|
|
8
|
+
Run: ``python3 -m app_live_tests smoke [--case NAME]``.
|
|
7
9
|
"""
|
|
8
10
|
from __future__ import annotations
|
|
9
11
|
|
|
12
|
+
import json
|
|
10
13
|
import sys
|
|
14
|
+
import tempfile
|
|
15
|
+
from pathlib import Path
|
|
11
16
|
|
|
12
17
|
from app_live_tests import state
|
|
13
|
-
from app_live_tests.harness import editor_apply, hap
|
|
18
|
+
from app_live_tests.harness import editor, editor_apply, hap
|
|
14
19
|
|
|
15
20
|
_failures: list[str] = []
|
|
16
21
|
|
|
@@ -23,6 +28,14 @@ def check(name: str, cond: bool, detail: str = "") -> None:
|
|
|
23
28
|
print(f" FAIL {name}: {detail}")
|
|
24
29
|
|
|
25
30
|
|
|
31
|
+
def _try(args: list[str]) -> tuple[bool, object]:
|
|
32
|
+
"""Run a bare ``hap`` command; return (ok, payload-or-error-text)."""
|
|
33
|
+
try:
|
|
34
|
+
return True, hap(args)
|
|
35
|
+
except RuntimeError as e:
|
|
36
|
+
return False, str(e)
|
|
37
|
+
|
|
38
|
+
|
|
26
39
|
# ── helpers ──────────────────────────────────────────────────────────────
|
|
27
40
|
def _worksheets(app_id: str) -> dict[str, str]:
|
|
28
41
|
detail = hap(["app", "info", "-a", app_id])
|
|
@@ -48,7 +61,7 @@ def _ensure_absent(app_id: str, name: str) -> None:
|
|
|
48
61
|
"""Delete a leftover worksheet of this name (from a crashed prior run)."""
|
|
49
62
|
ws = _worksheets(app_id)
|
|
50
63
|
if name in ws:
|
|
51
|
-
hap(["worksheet", "delete", ws[name], "-a", app_id], check=False)
|
|
64
|
+
hap(["worksheet", "delete", ws[name], "-a", app_id, "-y"], check=False)
|
|
52
65
|
|
|
53
66
|
|
|
54
67
|
def _make_worksheet(app_id: str, section_id: str, name: str) -> str:
|
|
@@ -59,19 +72,18 @@ def _make_worksheet(app_id: str, section_id: str, name: str) -> str:
|
|
|
59
72
|
|
|
60
73
|
# ── scenarios ──────────────────────────────────────────────────────────────
|
|
61
74
|
def case_worksheet(app_id: str, section_id: str) -> None:
|
|
62
|
-
print("scenario: worksheet create/update/delete")
|
|
75
|
+
print("scenario: worksheet create/update/delete (bare commands)")
|
|
63
76
|
name = "LT-ws"
|
|
64
77
|
_ensure_absent(app_id, name)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
78
|
+
ok, res = _try(["worksheet", "create", app_id, name,
|
|
79
|
+
"--section-id", section_id])
|
|
80
|
+
ws_id = res.get("workSheetId") if isinstance(res, dict) else ""
|
|
81
|
+
check("worksheet create succeeds", ok and bool(ws_id), str(res))
|
|
82
|
+
ok, res = _try(["worksheet", "update", ws_id, "--desc", "live test"])
|
|
83
|
+
check("worksheet update succeeds", ok, str(res))
|
|
70
84
|
check("worksheet exists after create", name in _worksheets(app_id))
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
]})
|
|
74
|
-
check("apply delete succeeds", rc == 0, err or out)
|
|
85
|
+
ok, res = _try(["worksheet", "delete", ws_id, "-a", app_id, "-y"])
|
|
86
|
+
check("worksheet delete succeeds", ok, str(res))
|
|
75
87
|
check("worksheet gone after delete", name not in _worksheets(app_id))
|
|
76
88
|
|
|
77
89
|
|
|
@@ -113,31 +125,32 @@ def case_field(app_id: str, section_id: str) -> None:
|
|
|
113
125
|
rows = [(c["controlName"], c.get("row")) for c in _fields(ws_id)]
|
|
114
126
|
first = min(rows, key=lambda t: (t[1] if t[1] is not None else 9999))
|
|
115
127
|
check("reordered field is first", first[0] == "金额", str(rows))
|
|
116
|
-
hap(["worksheet", "delete", ws_id, "-a", app_id], check=False)
|
|
128
|
+
hap(["worksheet", "delete", ws_id, "-a", app_id, "-y"], check=False)
|
|
117
129
|
|
|
118
130
|
|
|
119
131
|
def case_view(app_id: str, section_id: str) -> None:
|
|
120
|
-
print("scenario: view create/update/delete")
|
|
132
|
+
print("scenario: view create/update/delete (bare commands)")
|
|
121
133
|
name = "LT-views"
|
|
122
134
|
ws_id = _make_worksheet(app_id, section_id, name)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
ok, res = _try(["worksheet", "view", "create", ws_id, "按状态",
|
|
136
|
+
"--view-type", "board", "-a", app_id])
|
|
137
|
+
check("view create succeeds", ok, str(res))
|
|
138
|
+
view = next((v for v in _views(ws_id) if v.get("name") == "按状态"), {})
|
|
139
|
+
view_id = view.get("viewId") or view.get("id")
|
|
140
|
+
check("view present after create", bool(view_id),
|
|
141
|
+
str({v.get("name") for v in _views(ws_id)}))
|
|
142
|
+
ok, res = _try(["worksheet", "view", "update", ws_id, view_id,
|
|
143
|
+
"--name", "看板", "-a", app_id])
|
|
144
|
+
check("view update (rename) succeeds", ok, str(res))
|
|
130
145
|
names = {v.get("name") for v in _views(ws_id)}
|
|
131
146
|
check("view renamed to 看板", "看板" in names and "按状态" not in names,
|
|
132
147
|
str(names))
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
]})
|
|
137
|
-
check("apply view delete succeeds", rc == 0, err or out)
|
|
148
|
+
ok, res = _try(["worksheet", "view", "delete", ws_id, view_id,
|
|
149
|
+
"-a", app_id, "-y"])
|
|
150
|
+
check("view delete succeeds", ok, str(res))
|
|
138
151
|
names = {v.get("name") for v in _views(ws_id)}
|
|
139
152
|
check("view gone after delete", "看板" not in names, str(names))
|
|
140
|
-
hap(["worksheet", "delete", ws_id, "-a", app_id], check=False)
|
|
153
|
+
hap(["worksheet", "delete", ws_id, "-a", app_id, "-y"], check=False)
|
|
141
154
|
|
|
142
155
|
|
|
143
156
|
def _roles(app_id: str) -> dict[str, str]:
|
|
@@ -153,32 +166,28 @@ def case_role(app_id: str, section_id: str) -> None:
|
|
|
153
166
|
existing = _roles(app_id)
|
|
154
167
|
for n in (name, renamed):
|
|
155
168
|
if n in existing:
|
|
156
|
-
hap(["app", "role", "delete", existing[n], "-a", app_id], check=False)
|
|
169
|
+
hap(["app", "role", "delete", existing[n], "-a", app_id, "-y"], check=False)
|
|
157
170
|
who = hap(["auth", "whoami"])
|
|
158
171
|
me = who.get("id") if isinstance(who, dict) else ""
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
check("
|
|
172
|
+
ok, res = _try(["app", "role", "create", "--name", name,
|
|
173
|
+
"--description", "live test role", "--type", "0",
|
|
174
|
+
"--permission-scope", "80", "-a", app_id])
|
|
175
|
+
check("role create succeeds", ok, str(res))
|
|
163
176
|
check("role exists after create", name in _roles(app_id))
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
check("apply role.update rename succeeds", rc == 0, err or out)
|
|
177
|
+
role_id = _roles(app_id).get(name, "")
|
|
178
|
+
ok, res = _try(["app", "role", "rename", app_id, role_id, "-n", renamed])
|
|
179
|
+
check("role rename succeeds", ok, str(res))
|
|
168
180
|
check("role renamed", renamed in _roles(app_id) and name not in _roles(app_id))
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
]})
|
|
180
|
-
check("apply role.delete succeeds", rc == 0, err or out)
|
|
181
|
-
check("role gone after delete", current not in _roles(app_id))
|
|
181
|
+
current_id = _roles(app_id).get(renamed) or role_id
|
|
182
|
+
ok, res = _try(["app", "role", "add-member", current_id,
|
|
183
|
+
"--user-ids", me, "-a", app_id])
|
|
184
|
+
check("role add-member succeeds", ok, str(res))
|
|
185
|
+
ok, res = _try(["app", "role", "remove-member", current_id,
|
|
186
|
+
"--user-ids", me, "-a", app_id])
|
|
187
|
+
check("role remove-member succeeds", ok, str(res))
|
|
188
|
+
ok, res = _try(["app", "role", "delete", current_id, "-a", app_id, "-y"])
|
|
189
|
+
check("role delete succeeds", ok, str(res))
|
|
190
|
+
check("role gone after delete", renamed not in _roles(app_id))
|
|
182
191
|
|
|
183
192
|
|
|
184
193
|
def _actions(ws_id: str) -> list[dict]:
|
|
@@ -205,14 +214,14 @@ def case_custom_action(app_id: str, section_id: str) -> None:
|
|
|
205
214
|
btn = next((b for b in _actions(ws_id) if b.get("name") == "标记"), {})
|
|
206
215
|
btn_ref = btn.get("btnId") or btn.get("id")
|
|
207
216
|
if btn_ref:
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
check("
|
|
217
|
+
# custom-action.delete is retired from the edit-spec; use the bare
|
|
218
|
+
# command instead.
|
|
219
|
+
ok, res = _try(["worksheet", "delete-custom-action", ws_id, btn_ref,
|
|
220
|
+
"-a", app_id, "-y"])
|
|
221
|
+
check("delete-custom-action succeeds", ok, str(res))
|
|
213
222
|
names = {b.get("name") for b in _actions(ws_id)}
|
|
214
223
|
check("custom action gone after delete", "标记" not in names, str(names))
|
|
215
|
-
hap(["worksheet", "delete", ws_id, "-a", app_id], check=False)
|
|
224
|
+
hap(["worksheet", "delete", ws_id, "-a", app_id, "-y"], check=False)
|
|
216
225
|
|
|
217
226
|
|
|
218
227
|
def _items(app_id: str) -> dict[str, str]:
|
|
@@ -233,68 +242,80 @@ def _workflow_names(app_id: str) -> set[str]:
|
|
|
233
242
|
|
|
234
243
|
|
|
235
244
|
def case_chatbot(app_id: str, section_id: str) -> None:
|
|
236
|
-
print("scenario: chatbot create/update/delete")
|
|
245
|
+
print("scenario: chatbot create/update/delete (bare commands)")
|
|
237
246
|
name = "LT-bot"
|
|
247
|
+
# NOTE: --permanent is a silent server-side no-op for chatbots (returns
|
|
248
|
+
# true but the item stays); recycle-bin delete actually removes it.
|
|
238
249
|
if name in _items(app_id):
|
|
239
|
-
hap(["
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
check("
|
|
250
|
+
hap(["app", "chatbot", "delete", _items(app_id)[name], "-a", app_id,
|
|
251
|
+
"--section-id", section_id, "-y"], check=False)
|
|
252
|
+
ok, res = _try(["app", "chatbot", "create", app_id, name,
|
|
253
|
+
"--section-id", section_id, "--prompt", "你是测试助手"])
|
|
254
|
+
check("chatbot create succeeds", ok, str(res))
|
|
244
255
|
check("chatbot present after create", name in _items(app_id))
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
256
|
+
bot_id = _items(app_id).get(name, "")
|
|
257
|
+
ok, res = _try(["app", "chatbot", "update-config", bot_id,
|
|
258
|
+
"--welcome-text", "你好"])
|
|
259
|
+
check("chatbot update-config succeeds", ok, str(res))
|
|
260
|
+
ok, res = _try(["app", "chatbot", "delete", bot_id, "-a", app_id,
|
|
261
|
+
"--section-id", section_id, "-y"])
|
|
262
|
+
check("chatbot delete succeeds", ok, str(res))
|
|
250
263
|
check("chatbot gone after delete", name not in _items(app_id))
|
|
251
264
|
|
|
252
265
|
|
|
253
266
|
def case_custom_page(app_id: str, section_id: str) -> None:
|
|
254
|
-
print("scenario: custom-page create/
|
|
267
|
+
print("scenario: custom-page create/rename/delete (bare commands)")
|
|
255
268
|
name, renamed = "LT-page", "LT-page-2"
|
|
256
269
|
items = _items(app_id)
|
|
257
270
|
for n in (name, renamed):
|
|
258
271
|
if n in items:
|
|
259
272
|
hap(["custom-page", "delete", app_id, items[n],
|
|
260
273
|
"--section-id", section_id, "--yes"], check=False)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
check("apply custom-page.create succeeds", rc == 0, err or out)
|
|
274
|
+
ok, res = _try(["custom-page", "create", app_id, name,
|
|
275
|
+
"--section-id", section_id])
|
|
276
|
+
check("custom-page create succeeds", ok, str(res))
|
|
265
277
|
check("page present after create", name in _items(app_id))
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
check("
|
|
278
|
+
page_id = _items(app_id).get(name, "")
|
|
279
|
+
ok, res = _try(["custom-page", "rename", app_id, page_id,
|
|
280
|
+
"--section-id", section_id, "--name", renamed])
|
|
281
|
+
check("custom-page rename succeeds", ok, str(res))
|
|
270
282
|
check("page renamed", renamed in _items(app_id) and name not in _items(app_id))
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
check("apply custom-page.delete succeeds", rc == 0, err or out)
|
|
283
|
+
ok, res = _try(["custom-page", "delete", app_id, page_id,
|
|
284
|
+
"--section-id", section_id, "--yes"])
|
|
285
|
+
check("custom-page delete succeeds", ok, str(res))
|
|
275
286
|
check("page gone after delete", renamed not in _items(app_id))
|
|
276
287
|
|
|
277
288
|
|
|
289
|
+
def _make_workflow(app_id: str, name: str) -> str:
|
|
290
|
+
"""Create a scheduled (type 5) workflow and return its process id.
|
|
291
|
+
|
|
292
|
+
A worksheet-triggered (type 1) workflow with no trigger bound is
|
|
293
|
+
excluded from the list by HAP, so it can't be resolved by name. A
|
|
294
|
+
scheduled workflow lists immediately.
|
|
295
|
+
"""
|
|
296
|
+
_, org_id = state.ensure_app()
|
|
297
|
+
res = hap(["workflow", "create", "-c", org_id, "-n", name,
|
|
298
|
+
"-a", app_id, "--type", "5"], check=False)
|
|
299
|
+
pid = res.get("id") if isinstance(res, dict) else None
|
|
300
|
+
if not pid:
|
|
301
|
+
data = hap(["workflow", "list", app_id], check=False)
|
|
302
|
+
if isinstance(data, dict):
|
|
303
|
+
data = data.get("list") or data.get("data") or []
|
|
304
|
+
pid = next((w.get("id") for w in data if w.get("name") == name), None)
|
|
305
|
+
return pid or ""
|
|
306
|
+
|
|
307
|
+
|
|
278
308
|
def case_workflow(app_id: str, section_id: str) -> None:
|
|
279
|
-
print("scenario: workflow create/update/delete")
|
|
309
|
+
print("scenario: workflow create/update/delete (bare commands)")
|
|
280
310
|
name, renamed = "LT-flow", "LT-flow-2"
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
# it can't be resolved by name. A scheduled workflow lists immediately.
|
|
284
|
-
rc, out, err = editor_apply({"app": app_id, "ops": [
|
|
285
|
-
{"type": "workflow.create", "name": name, "trigger_type": 5},
|
|
286
|
-
]})
|
|
287
|
-
check("apply workflow.create succeeds", rc == 0, err or out)
|
|
311
|
+
pid = _make_workflow(app_id, name)
|
|
312
|
+
check("workflow create succeeds", bool(pid))
|
|
288
313
|
check("workflow present after create", name in _workflow_names(app_id))
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
]})
|
|
292
|
-
check("apply workflow.update succeeds", rc == 0, err or out)
|
|
314
|
+
ok, res = _try(["workflow", "update", pid, "-n", renamed])
|
|
315
|
+
check("workflow update succeeds", ok, str(res))
|
|
293
316
|
check("workflow renamed", renamed in _workflow_names(app_id))
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
]})
|
|
297
|
-
check("apply workflow.delete succeeds", rc == 0, err or out)
|
|
317
|
+
ok, res = _try(["workflow", "delete", pid, "-y"])
|
|
318
|
+
check("workflow delete succeeds", ok, str(res))
|
|
298
319
|
check("workflow gone after delete", renamed not in _workflow_names(app_id))
|
|
299
320
|
|
|
300
321
|
|
|
@@ -350,35 +371,35 @@ def _node_names(pid: str) -> set[str]:
|
|
|
350
371
|
return {n.get("name") for n in m.values()}
|
|
351
372
|
|
|
352
373
|
|
|
374
|
+
def _node_map(pid: str) -> dict:
|
|
375
|
+
data = hap(["workflow", "node", "list", pid], check=False)
|
|
376
|
+
return data.get("flowNodeMap", {}) if isinstance(data, dict) else {}
|
|
377
|
+
|
|
378
|
+
|
|
353
379
|
def case_node(app_id: str, section_id: str) -> None:
|
|
354
|
-
print("scenario: workflow node add/rename/delete")
|
|
380
|
+
print("scenario: workflow node add/rename/delete (bare commands)")
|
|
355
381
|
name = "LT-nodeflow"
|
|
356
|
-
existing = _workflow_names(app_id)
|
|
357
382
|
# create a scheduled workflow to host nodes
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
rc, out, err = editor_apply({"app": app_id, "ops": [
|
|
367
|
-
{"type": "node.add", "workflow": name,
|
|
368
|
-
"node": {"name": "测试节点", "node_type": 13}},
|
|
369
|
-
]})
|
|
370
|
-
check("apply node.add succeeds", rc == 0, err or out)
|
|
383
|
+
pid = _make_workflow(app_id, name)
|
|
384
|
+
check("workflow create (for nodes) succeeds", bool(pid))
|
|
385
|
+
start_id = next((nid for nid, n in _node_map(pid).items()
|
|
386
|
+
if n.get("typeId") == 0), None)
|
|
387
|
+
check("start node resolved", bool(start_id), str(_node_map(pid)))
|
|
388
|
+
ok, res = _try(["workflow", "node", "add", pid, "--type", "13",
|
|
389
|
+
"-n", "测试节点", "--after", start_id])
|
|
390
|
+
check("node add succeeds", ok, str(res))
|
|
371
391
|
check("node present after add", "测试节点" in _node_names(pid),
|
|
372
392
|
str(_node_names(pid)))
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
393
|
+
node_id = next((nid for nid, n in _node_map(pid).items()
|
|
394
|
+
if n.get("name") == "测试节点"), "")
|
|
395
|
+
ok, res = _try(["workflow", "node", "rename", pid, node_id,
|
|
396
|
+
"-n", "改名节点"])
|
|
397
|
+
check("node rename succeeds", ok, str(res))
|
|
398
|
+
check("node renamed", "改名节点" in _node_names(pid), str(_node_names(pid)))
|
|
399
|
+
ok, res = _try(["workflow", "node", "delete", pid, node_id, "-y"])
|
|
400
|
+
check("node delete succeeds", ok, str(res))
|
|
380
401
|
names = _node_names(pid)
|
|
381
|
-
check("node
|
|
402
|
+
check("node gone after delete",
|
|
382
403
|
"改名节点" not in names and "测试节点" not in names, str(names))
|
|
383
404
|
hap(["workflow", "delete", pid, "--yes"], check=False)
|
|
384
405
|
|
|
@@ -389,29 +410,25 @@ def _sections(app_id: str) -> dict[str, str]:
|
|
|
389
410
|
|
|
390
411
|
|
|
391
412
|
def case_application(app_id: str, section_id: str) -> None:
|
|
392
|
-
print("scenario: app
|
|
413
|
+
print("scenario: app update + section add/edit/delete (bare commands)")
|
|
393
414
|
name, renamed = "LT-section", "LT-section-2"
|
|
394
415
|
secs = _sections(app_id)
|
|
395
416
|
for n in (name, renamed):
|
|
396
417
|
if n in secs:
|
|
397
418
|
hap(["app", "delete-section", app_id, secs[n], "-y"], check=False)
|
|
398
|
-
# app
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
check("apply app.update + section.add succeed", rc == 0, err or out)
|
|
419
|
+
# app update: set nav color (cosmetic, no restore needed)
|
|
420
|
+
ok, res = _try(["app", "update", app_id, "--nav-color", "#2296F3"])
|
|
421
|
+
check("app update succeeds", ok, str(res))
|
|
422
|
+
ok, res = _try(["app", "add-section", app_id, "-n", name])
|
|
423
|
+
check("add-section succeeds", ok, str(res))
|
|
404
424
|
check("section present after add", name in _sections(app_id))
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
check("apply section.update succeeds", rc == 0, err or out)
|
|
425
|
+
sec_id = _sections(app_id).get(name, "")
|
|
426
|
+
ok, res = _try(["app", "edit-section", app_id, sec_id, "-n", renamed])
|
|
427
|
+
check("edit-section succeeds", ok, str(res))
|
|
409
428
|
check("section renamed", renamed in _sections(app_id)
|
|
410
429
|
and name not in _sections(app_id))
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
]})
|
|
414
|
-
check("apply section.delete succeeds", rc == 0, err or out)
|
|
430
|
+
ok, res = _try(["app", "delete-section", app_id, sec_id, "-y"])
|
|
431
|
+
check("delete-section succeeds", ok, str(res))
|
|
415
432
|
check("section gone after delete", renamed not in _sections(app_id))
|
|
416
433
|
|
|
417
434
|
|
|
@@ -497,6 +514,28 @@ def case_app_logs(app_id: str, section_id: str) -> None:
|
|
|
497
514
|
f"foreign apps: {sorted(foreign)}")
|
|
498
515
|
|
|
499
516
|
|
|
517
|
+
def case_retired_op_rejected(app_id: str, section_id: str) -> None:
|
|
518
|
+
"""`hap app-editor validate` must reject retired ops and point at the
|
|
519
|
+
bare CLI replacement."""
|
|
520
|
+
print("scenario: retired edit-spec op rejected by validate")
|
|
521
|
+
spec = {"app": app_id, "ops": [
|
|
522
|
+
{"type": "view.update", "worksheet": "x", "view": "y", "name": "z"},
|
|
523
|
+
]}
|
|
524
|
+
with tempfile.NamedTemporaryFile(
|
|
525
|
+
"w", suffix=".edit.json", delete=False, encoding="utf-8") as fh:
|
|
526
|
+
json.dump(spec, fh, ensure_ascii=False)
|
|
527
|
+
path = fh.name
|
|
528
|
+
try:
|
|
529
|
+
rc, out, err = editor(["validate", path])
|
|
530
|
+
finally:
|
|
531
|
+
Path(path).unlink(missing_ok=True)
|
|
532
|
+
check("validate exits non-zero for retired op", rc != 0,
|
|
533
|
+
f"rc={rc} out={out!r}")
|
|
534
|
+
blob = err + out
|
|
535
|
+
check("error points at bare command",
|
|
536
|
+
"hap worksheet view update" in blob, blob.strip())
|
|
537
|
+
|
|
538
|
+
|
|
500
539
|
_CASES = {
|
|
501
540
|
"worksheet": case_worksheet,
|
|
502
541
|
"field": case_field,
|
|
@@ -509,6 +548,7 @@ _CASES = {
|
|
|
509
548
|
"workflow": case_workflow,
|
|
510
549
|
"node": case_node,
|
|
511
550
|
"application": case_application,
|
|
551
|
+
"retired-op": case_retired_op_rejected,
|
|
512
552
|
"app-usage": case_app_usage,
|
|
513
553
|
"app-export": case_app_export,
|
|
514
554
|
"app-logs": case_app_logs,
|
|
@@ -154,7 +154,7 @@ def app_unselect(ctx):
|
|
|
154
154
|
@click.option(
|
|
155
155
|
"--configuration", "configuratiuon",
|
|
156
156
|
default="",
|
|
157
|
-
help="JSON link-configuration blob
|
|
157
|
+
help="JSON link-configuration blob",
|
|
158
158
|
)
|
|
159
159
|
@click.option(
|
|
160
160
|
"--pc-display/--no-pc-display", "pc_display", default=None,
|
|
@@ -224,6 +224,7 @@ def app_create(
|
|
|
224
224
|
@click.argument("app_id")
|
|
225
225
|
@click.option("--name", "-n", default="", help="New name")
|
|
226
226
|
@click.option("--desc", "-d", default="", help="New description")
|
|
227
|
+
@click.option("--icon", default="", help="Icon name, e.g. 0_lego")
|
|
227
228
|
@click.option("--icon-color", default="", help="Icon color hex")
|
|
228
229
|
@click.option("--nav-color", default="", help="Navigation bar color hex")
|
|
229
230
|
@click.option(
|
|
@@ -238,7 +239,7 @@ def app_create(
|
|
|
238
239
|
help="Show-icon flag (3 bits, e.g. 011). Defaults by nav style.",
|
|
239
240
|
)
|
|
240
241
|
@pass_context
|
|
241
|
-
def app_update(ctx, app_id, name, desc, icon_color, nav_color,
|
|
242
|
+
def app_update(ctx, app_id, name, desc, icon, icon_color, nav_color,
|
|
242
243
|
pc_nav_style, display_icon):
|
|
243
244
|
"""Update application information (incl. PC navigation style)."""
|
|
244
245
|
try:
|
|
@@ -251,7 +252,7 @@ def app_update(ctx, app_id, name, desc, icon_color, nav_color,
|
|
|
251
252
|
# pcNavList[*].defaultDisplayIcon).
|
|
252
253
|
di = "100" if nav == 3 else "011"
|
|
253
254
|
data = app_mod.update_app(
|
|
254
|
-
session, app_id, name=name, description=desc,
|
|
255
|
+
session, app_id, name=name, description=desc, icon=icon,
|
|
255
256
|
icon_color=icon_color, nav_color=nav_color,
|
|
256
257
|
pc_nav_style=nav, display_icon=di,
|
|
257
258
|
)
|
|
@@ -318,13 +319,43 @@ def app_delete(ctx, app_id, org_id, yes):
|
|
|
318
319
|
@app.command("add-section")
|
|
319
320
|
@click.argument("app_id")
|
|
320
321
|
@click.option("--name", "-n", default="", help="Section name")
|
|
322
|
+
@click.option(
|
|
323
|
+
"--sections-json", default="",
|
|
324
|
+
help='Several sections at once, as a JSON array, e.g. '
|
|
325
|
+
'\'[{"name": "Ops", "icon": "folder"}]\' '
|
|
326
|
+
'(alternative to --name)',
|
|
327
|
+
)
|
|
328
|
+
@click.option(
|
|
329
|
+
"--parent-id", default="",
|
|
330
|
+
help="Parent section ID; omit to create at the top level",
|
|
331
|
+
)
|
|
321
332
|
@pass_context
|
|
322
|
-
def app_add_section(ctx, app_id, name):
|
|
323
|
-
"""Add
|
|
333
|
+
def app_add_section(ctx, app_id, name, sections_json, parent_id):
|
|
334
|
+
"""Add one or more sections/groups to an application."""
|
|
324
335
|
try:
|
|
336
|
+
import json as _json
|
|
337
|
+
if name and sections_json:
|
|
338
|
+
raise click.UsageError(
|
|
339
|
+
"Use either --name or --sections-json, not both.")
|
|
325
340
|
session = ctx.get_session()
|
|
326
|
-
|
|
327
|
-
|
|
341
|
+
if sections_json:
|
|
342
|
+
try:
|
|
343
|
+
parsed = _json.loads(sections_json)
|
|
344
|
+
except ValueError as e:
|
|
345
|
+
raise click.UsageError(f"--sections-json is not valid JSON: {e}")
|
|
346
|
+
try:
|
|
347
|
+
sections = app_mod.v3_sections_to_add_section_calls(parsed)
|
|
348
|
+
except ValueError as e:
|
|
349
|
+
raise click.UsageError(f"--sections-json: {e}")
|
|
350
|
+
data = app_mod.add_sections_batch(
|
|
351
|
+
session, app_id, sections, parent_id=parent_id,
|
|
352
|
+
)
|
|
353
|
+
ctx.output(data, lambda d: click.echo(f"Sections added: {d}"))
|
|
354
|
+
else:
|
|
355
|
+
data = app_mod.add_section(
|
|
356
|
+
session, app_id, name, parent_id=parent_id,
|
|
357
|
+
)
|
|
358
|
+
ctx.output(data, lambda d: click.echo(f"Section added: {d}"))
|
|
328
359
|
except Exception as e:
|
|
329
360
|
ctx.handle_error(e)
|
|
330
361
|
|
|
@@ -382,19 +413,27 @@ def app_backup(ctx, app_id, org_id, no_data):
|
|
|
382
413
|
@app.command("restore")
|
|
383
414
|
@click.argument("app_id")
|
|
384
415
|
@org_id_option()
|
|
385
|
-
@click.option("--backup-id", "-b",
|
|
386
|
-
@click.option("--file-url",
|
|
387
|
-
@click.option("--file-name",
|
|
416
|
+
@click.option("--backup-id", "-b", default="", help="Restore from an existing backup (its ID)")
|
|
417
|
+
@click.option("--file-url", default="", help="Restore from an uploaded backup file (its URL)")
|
|
418
|
+
@click.option("--file-name", default="", help="File name to show for an uploaded backup file")
|
|
388
419
|
@click.option("--no-data", is_flag=True, help="Restore without data")
|
|
389
420
|
@click.option("--as-new", is_flag=True, help="Restore as a new app")
|
|
390
421
|
@pass_context
|
|
391
422
|
def app_restore(ctx, app_id, org_id, backup_id, file_url, file_name, no_data, as_new):
|
|
392
|
-
"""Restore an application from backup.
|
|
423
|
+
"""Restore an application, either from an existing backup or an uploaded file.
|
|
424
|
+
|
|
425
|
+
Provide exactly one source: --backup-id (an existing backup) or
|
|
426
|
+
--file-url (an uploaded backup file).
|
|
427
|
+
"""
|
|
393
428
|
try:
|
|
429
|
+
if bool(backup_id) == bool(file_url):
|
|
430
|
+
raise click.UsageError(
|
|
431
|
+
"Provide exactly one of --backup-id or --file-url."
|
|
432
|
+
)
|
|
394
433
|
session = ctx.get_session()
|
|
395
434
|
data = app_mod.restore_app(
|
|
396
|
-
session, app_id, require_api_project_id(session, org_id),
|
|
397
|
-
file_url, file_name,
|
|
435
|
+
session, app_id, require_api_project_id(session, org_id),
|
|
436
|
+
backup_id=backup_id, file_url=file_url, file_name=file_name,
|
|
398
437
|
contain_data=not no_data, is_restore_new=as_new,
|
|
399
438
|
)
|
|
400
439
|
ctx.output(data, lambda d: click.echo(f"Restore started: {d}"))
|
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Thin Click layer over the editor engine in ``hap_cli.core.editor``:
|
|
4
4
|
edit-spec validation, live inspection, dry-run planning, and applying
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
the edits whose safe orchestration lives in the engine — worksheet
|
|
6
|
+
fields (full-control-set write-back), custom-page components
|
|
7
|
+
(layout read-modify-write), and custom-action create/update (high-level
|
|
8
|
+
spec translation). Every other element edit is a single API call served
|
|
9
|
+
by a plain ``hap`` command; retired op types are rejected with the
|
|
10
|
+
replacement command (see ``editspec.RETIRED_OPS``).
|
|
7
11
|
|
|
8
12
|
The edit-spec schemas are documented and shipped with the
|
|
9
13
|
``hap-cli-app-editor`` skill; this command resolves them at runtime.
|