hap-cli 0.8.26__tar.gz → 0.8.27__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.26 → hap_cli-0.8.27}/PKG-INFO +1 -1
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/__init__.py +1 -1
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/instance_cmd.py +102 -2
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/record_cmd.py +11 -1
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/record.py +22 -9
- hap_cli-0.8.27/hap_cli/guide/en/approval.md +118 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/guide/index.json +8 -0
- hap_cli-0.8.27/hap_cli/guide/zh-Hans/approval.md +105 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/locale/messages.json +22 -2
- hap_cli-0.8.27/hap_cli/tests/test_approval_submit_fill_cli.py +143 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_core.py +25 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli.egg-info/PKG-INFO +1 -1
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli.egg-info/SOURCES.txt +3 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/MANIFEST.in +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/README.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/app_live_tests/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/app_live_tests/__main__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/app_live_tests/config.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/app_live_tests/harness.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/app_live_tests/smoke.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/app_live_tests/state.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/README.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/README_CN.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/__main__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/app_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/app_creator_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/app_editor_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/attachment_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/auth_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/calendar_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/chart_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/chat_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/chatbot_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/config_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/contact_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/department_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/group_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/guide_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/icon_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/node_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/optionset_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/page_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/post_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/region_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/role_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/update_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/upload_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/v3_registry.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/workflow_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/commands/worksheet_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/context.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/action_spec_adapter.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/charts.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/cleanup.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/compiler.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/config.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/design/design.schema.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/errors.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/executor.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/fields.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/hap.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/merge.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/recording/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/recording/console.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/recording/mirror.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/recording/report.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/schema.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/executor.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/seed/template.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/selftest.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/smoke.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/steps.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/store.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/validate.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/attachment.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/auth.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/calendar_mod.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/chart.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/chat.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/chatbot.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/cli_access.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/contact.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/control_type_codes.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/department.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/apply.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/componentlower.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/editspec/component.schema.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/editspec/field.schema.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/editspec.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/errors.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/fieldlower.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/jsonschema_mini.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/models.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/ops.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/planner.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/editor/reader.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/field_normalizer.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/field_spec_adapter.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/filter_translator.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/flow_node.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/global_meta.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/group.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/guide.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/icon_index.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/instance.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/logger.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/page.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/post.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/role.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/role_perm_builder.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/session.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/token_crypto.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/update.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/upload.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/v3/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/v3/dispatcher.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/v3/render.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/v3/schema.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/view_spec_adapter.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/workflow_node_dsl.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/worksheet.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/core/worksheet_templates.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/guide/en/chart.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/guide/en/messages.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/guide/en/overview.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/guide/en/record.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/guide/en/setup.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/guide/en/skills.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/guide/zh-Hans/chart.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/guide/zh-Hans/messages.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/guide/zh-Hans/overview.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/guide/zh-Hans/record.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/guide/zh-Hans/setup.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/guide/zh-Hans/skills.md +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/hap_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/i18n.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/locale/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/locale/messages.schema.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/conftest.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_app_creator.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_app_editor.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_app_export_usage.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_attachment_share.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_auth_prerelease.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_bug_036430.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_bug_036750.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_chart.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_chat_attachment_send.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_cli_access.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_config_cmd.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_content_language.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_control_lookup_guard.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_full_e2e.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_global_meta.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_guide.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_i18n.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_instance_history_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_integration_approval.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_integration_approval_actions.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_integration_misc.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_integration_social.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_integration_v3.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_multi_profile.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_org_id_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_record_field_alias.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_record_write_fixes.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_select_add_option.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_semantic_types_registry.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_update.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_update_mode.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_upload_host_resolution.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_v3_dispatcher.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_v3_session.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_workflow_list_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_worksheet_create_section.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli.egg-info/requires.txt +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/__main__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/_wftest.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/charts.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/cleanup.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/compiler.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/config.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/errors.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/executor.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/fields.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/hap.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/recording/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/recording/console.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/recording/jsonl.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/recording/mirror.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/recording/report.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/schema.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/seed/__init__.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/seed/cli.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/seed/executor.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/seed/template.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/selftest.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/smoke.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/step.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/steps.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/store.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/live_tests/workflow_dsl.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/setup.cfg +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/setup.py +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/INDEX.json +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/create_optionset.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/create_role.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/delete_optionset.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/delete_record.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/delete_role.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/get_app_info.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/get_record_details.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/get_record_list.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/get_record_logs.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/get_record_relations.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/get_regions.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/get_role_details.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/get_role_list.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/knowledge_search.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
- {hap_cli-0.8.26 → hap_cli-0.8.27}/sources/v3-api-schema/update_optionset.yaml +0 -0
|
@@ -6,6 +6,11 @@ import click
|
|
|
6
6
|
|
|
7
7
|
from hap_cli.context import pass_context
|
|
8
8
|
from hap_cli.core import instance as inst_mod
|
|
9
|
+
from hap_cli.core import record as rec_mod
|
|
10
|
+
from hap_cli.commands.record_cmd import (
|
|
11
|
+
_fields_json_to_raw as _rec_fields_json_to_raw,
|
|
12
|
+
_parse_fields as _rec_parse_fields,
|
|
13
|
+
)
|
|
9
14
|
from hap_cli.utils.formatting import output_json, output_table, output_kv
|
|
10
15
|
from hap_cli.i18n import t
|
|
11
16
|
|
|
@@ -165,15 +170,110 @@ def instance_sign(ctx, instance_id, sign_to, before, work_id, opinion):
|
|
|
165
170
|
ctx.handle_error(e)
|
|
166
171
|
|
|
167
172
|
|
|
173
|
+
def _unwritten_fields(session, worksheet_id, row_id, controls):
|
|
174
|
+
"""Names of controls whose non-empty write did not land on the record.
|
|
175
|
+
|
|
176
|
+
A fill-in node only accepts the fields on its own form; anything else is
|
|
177
|
+
dropped silently. Only non-empty writes are checked, so deliberately
|
|
178
|
+
clearing a field is never reported.
|
|
179
|
+
"""
|
|
180
|
+
intended = [
|
|
181
|
+
c["controlId"] for c in controls
|
|
182
|
+
if str(c.get("value") or "").strip() not in ("", "[]")
|
|
183
|
+
]
|
|
184
|
+
if not intended:
|
|
185
|
+
return []
|
|
186
|
+
row = rec_mod.get_record(session, worksheet_id, row_id) or {}
|
|
187
|
+
# get_record returns the cell values as a JSON *string* under rowData.
|
|
188
|
+
values = row.get("rowData") or {}
|
|
189
|
+
if isinstance(values, str):
|
|
190
|
+
try:
|
|
191
|
+
values = _json.loads(values)
|
|
192
|
+
except ValueError:
|
|
193
|
+
return [] # unreadable read-back: don't block on a guess
|
|
194
|
+
if not isinstance(values, dict):
|
|
195
|
+
return []
|
|
196
|
+
return [cid for cid in intended
|
|
197
|
+
if str(values.get(cid) or "").strip() in ("", "[]")]
|
|
198
|
+
|
|
199
|
+
|
|
168
200
|
@instance.command("submit")
|
|
169
201
|
@click.argument("instance_id")
|
|
170
202
|
@click.option("--work-id", "-w", default="", help="Work item ID")
|
|
171
203
|
@click.option("--opinion", "-o", default="", help="Submit comment")
|
|
204
|
+
@click.option("--field", "-f", multiple=True,
|
|
205
|
+
help="Field value to fill in before submitting: FIELD=VALUE "
|
|
206
|
+
"(repeatable). FIELD is a field id, alias or name.")
|
|
207
|
+
@click.option("--fields-json", default="",
|
|
208
|
+
help="Field values to fill in before submitting, as one JSON "
|
|
209
|
+
"array: [{\"id\": \"<field id or alias>\", \"value\": ...}, ...]. "
|
|
210
|
+
"Use either this or -f, not both. Value formats per field "
|
|
211
|
+
"type are in `hap guide record`.")
|
|
212
|
+
@click.option("--dry-run", is_flag=True,
|
|
213
|
+
help="Resolve the task's record and show what would be written, "
|
|
214
|
+
"without filling anything in or submitting.")
|
|
172
215
|
@pass_context
|
|
173
|
-
def instance_submit(ctx, instance_id, work_id, opinion):
|
|
174
|
-
"""Submit a fill-in task.
|
|
216
|
+
def instance_submit(ctx, instance_id, work_id, opinion, field, fields_json, dry_run):
|
|
217
|
+
"""Submit a fill-in task, optionally filling in its form first.
|
|
218
|
+
|
|
219
|
+
The fields of a fill-in task are writable only while you hold the task,
|
|
220
|
+
so pass them here rather than editing the record separately. Working
|
|
221
|
+
through todos end to end is covered in `hap guide approval`.
|
|
222
|
+
"""
|
|
223
|
+
if field and fields_json:
|
|
224
|
+
raise click.UsageError("Pass exactly one of -f or --fields-json.")
|
|
175
225
|
try:
|
|
176
226
|
session = ctx.get_session()
|
|
227
|
+
raw = None
|
|
228
|
+
if field or fields_json:
|
|
229
|
+
raw = (_rec_fields_json_to_raw(fields_json) if fields_json
|
|
230
|
+
else _rec_parse_fields(field))
|
|
231
|
+
# Read-only resolution first: a fill-in task carries the record it
|
|
232
|
+
# writes to, which the caller should not have to look up.
|
|
233
|
+
target = None
|
|
234
|
+
if raw is not None:
|
|
235
|
+
target = inst_mod.get_work_item(session, instance_id, work_id=work_id)
|
|
236
|
+
if not (target or {}).get("worksheetId"):
|
|
237
|
+
raise click.ClickException(
|
|
238
|
+
"Could not resolve the record behind this task, so there "
|
|
239
|
+
"is nothing to fill in. Check the instance and work id.")
|
|
240
|
+
if dry_run:
|
|
241
|
+
ctx.output(
|
|
242
|
+
{"instanceId": instance_id, "workId": work_id,
|
|
243
|
+
"target": target, "fields": raw, "submitted": False},
|
|
244
|
+
lambda d: click.echo(
|
|
245
|
+
f"Would fill {len(raw or [])} field(s) on "
|
|
246
|
+
f"{(target or {}).get('worksheetId', '-')}/"
|
|
247
|
+
f"{(target or {}).get('rowId', '-')} and submit."),
|
|
248
|
+
)
|
|
249
|
+
return
|
|
250
|
+
if raw is not None:
|
|
251
|
+
ws_id, row_id = target["worksheetId"], target.get("rowId", "")
|
|
252
|
+
controls = rec_mod.encode_controls_by_id(
|
|
253
|
+
session, ws_id, raw, for_update=True)
|
|
254
|
+
# The record itself may be read-only outside the task; the
|
|
255
|
+
# instance/work pair is what grants the write.
|
|
256
|
+
rec_mod.update_record(
|
|
257
|
+
session, ws_id, row_id, controls,
|
|
258
|
+
instance_id=instance_id, work_id=work_id,
|
|
259
|
+
)
|
|
260
|
+
# A field the task's form does not contain is dropped by the
|
|
261
|
+
# server without any error, so the write must be read back
|
|
262
|
+
# before the task is submitted — otherwise the value is lost
|
|
263
|
+
# and the task is gone with it.
|
|
264
|
+
dropped = _unwritten_fields(session, ws_id, row_id, controls)
|
|
265
|
+
if dropped:
|
|
266
|
+
# Report the names the caller typed, not internal ids.
|
|
267
|
+
labels = dict(zip(
|
|
268
|
+
[c.get("controlId") for c in controls],
|
|
269
|
+
[str(r.get("controlId")) for r in raw],
|
|
270
|
+
)) if len(controls) == len(raw) else {}
|
|
271
|
+
shown = [labels.get(cid, cid) for cid in dropped]
|
|
272
|
+
raise click.ClickException(
|
|
273
|
+
"These values were not saved, so the task was NOT "
|
|
274
|
+
"submitted: " + ", ".join(shown) + ". A fill-in task "
|
|
275
|
+
"only accepts the fields on its own form; fill the rest "
|
|
276
|
+
"separately, or drop them and submit again.")
|
|
177
277
|
data = inst_mod.submit(session, instance_id, work_id, opinion)
|
|
178
278
|
ctx.output(data, lambda d: click.echo("Submitted."))
|
|
179
279
|
except Exception as e:
|
|
@@ -162,8 +162,17 @@ def _fields_json_to_raw(fields_json):
|
|
|
162
162
|
"--app-id", "-a", default="",
|
|
163
163
|
help="Application ID. Optional — the worksheet id is enough on its own; this only scopes the field lookup.",
|
|
164
164
|
)
|
|
165
|
+
@click.option("--instance-id", default="",
|
|
166
|
+
help="Workflow instance the edit belongs to. Pass together with "
|
|
167
|
+
"--work-id to fill in the form of a fill-in task, whose "
|
|
168
|
+
"fields are only writable while you hold that task. To fill "
|
|
169
|
+
"a task and submit it in one step use `approval submit -f`; "
|
|
170
|
+
"see `hap guide approval`.")
|
|
171
|
+
@click.option("--work-id", default="",
|
|
172
|
+
help="Workflow work-item the edit belongs to. Use with --instance-id.")
|
|
165
173
|
@pass_context
|
|
166
|
-
def record_update(ctx, worksheet_id, row_id, field, fields_json, no_workflow, app_id
|
|
174
|
+
def record_update(ctx, worksheet_id, row_id, field, fields_json, no_workflow, app_id,
|
|
175
|
+
instance_id, work_id):
|
|
167
176
|
"""Update a record.
|
|
168
177
|
|
|
169
178
|
Pass values either as repeated -f CONTROL_ID=VALUE pairs or as one
|
|
@@ -193,6 +202,7 @@ def record_update(ctx, worksheet_id, row_id, field, fields_json, no_workflow, ap
|
|
|
193
202
|
data = rec_mod.update_record(
|
|
194
203
|
session, worksheet_id, row_id, controls,
|
|
195
204
|
trigger_workflow=not no_workflow, app_id=app_id,
|
|
205
|
+
instance_id=instance_id, work_id=work_id,
|
|
196
206
|
)
|
|
197
207
|
ctx.output(data, lambda d: click.echo(f"Updated: {d}"))
|
|
198
208
|
except Exception as e:
|
|
@@ -965,6 +965,8 @@ def update_record(
|
|
|
965
965
|
controls: list[dict[str, Any]],
|
|
966
966
|
trigger_workflow: bool = True,
|
|
967
967
|
app_id: str = "",
|
|
968
|
+
instance_id: str = "",
|
|
969
|
+
work_id: str = "",
|
|
968
970
|
) -> dict[str, Any]:
|
|
969
971
|
"""Update an existing record.
|
|
970
972
|
|
|
@@ -973,6 +975,14 @@ def update_record(
|
|
|
973
975
|
AddWorksheetRow, UpdateWorksheetRow's schema has no ``appId``, and
|
|
974
976
|
inventing one would diverge from the frontend.
|
|
975
977
|
|
|
978
|
+
``instance_id`` / ``work_id`` identify a workflow step the caller is
|
|
979
|
+
acting inside. A fill-in node (填写节点) grants write permission on the
|
|
980
|
+
fields of its own form for the duration of that step; the record itself
|
|
981
|
+
may well be read-only otherwise, in which case a plain update answers
|
|
982
|
+
"权限不足". Passing the pair makes the write happen under the node's
|
|
983
|
+
permission, exactly as the web form does (UpdateWorksheetRow's
|
|
984
|
+
``instanceId`` / ``workId`` params).
|
|
985
|
+
|
|
976
986
|
Args:
|
|
977
987
|
session: Active session
|
|
978
988
|
worksheet_id: Worksheet ID
|
|
@@ -980,21 +990,24 @@ def update_record(
|
|
|
980
990
|
controls: List of {controlId, value} dicts
|
|
981
991
|
trigger_workflow: Whether to trigger workflows
|
|
982
992
|
app_id: Optional application ID (metadata scope only)
|
|
993
|
+
instance_id: Workflow instance ID the write belongs to
|
|
994
|
+
work_id: Workflow work-item ID the write belongs to
|
|
983
995
|
|
|
984
996
|
Returns:
|
|
985
997
|
Update result
|
|
986
998
|
"""
|
|
987
999
|
_reject_workflow_suppression(trigger_workflow)
|
|
1000
|
+
payload: dict[str, Any] = {
|
|
1001
|
+
"worksheetId": worksheet_id,
|
|
1002
|
+
"rowId": row_id,
|
|
1003
|
+
"newOldControl": controls,
|
|
1004
|
+
}
|
|
1005
|
+
if instance_id:
|
|
1006
|
+
payload["instanceId"] = instance_id
|
|
1007
|
+
if work_id:
|
|
1008
|
+
payload["workId"] = work_id
|
|
988
1009
|
return _raise_on_failed_write(
|
|
989
|
-
session.api_call(
|
|
990
|
-
"Worksheet",
|
|
991
|
-
"UpdateWorksheetRow",
|
|
992
|
-
{
|
|
993
|
-
"worksheetId": worksheet_id,
|
|
994
|
-
"rowId": row_id,
|
|
995
|
-
"newOldControl": controls,
|
|
996
|
-
},
|
|
997
|
-
),
|
|
1010
|
+
session.api_call("Worksheet", "UpdateWorksheetRow", payload),
|
|
998
1011
|
"update",
|
|
999
1012
|
)
|
|
1000
1013
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Todos and approvals: find, fill, act
|
|
2
|
+
|
|
3
|
+
`hap approval` handles workflow todos: listing them, filling in the form of a
|
|
4
|
+
fill-in task, approving or rejecting, withdrawing and re-initiating. This topic
|
|
5
|
+
covers which command each kind of todo needs, plus the traps you will hit
|
|
6
|
+
without reading it.
|
|
7
|
+
|
|
8
|
+
> 🚨 Core risk of this topic: **the fields of a fill-in task cannot be written
|
|
9
|
+
> with a plain record update**. `hap worksheet record update` answers
|
|
10
|
+
> 权限不足 (insufficient permission), and that error points you in entirely the
|
|
11
|
+
> wrong direction (it reads as "your account lacks permission"). The right way
|
|
12
|
+
> is section 3.
|
|
13
|
+
|
|
14
|
+
## 1. See what is waiting
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
hap approval todo-count # counts per kind
|
|
18
|
+
hap approval todo --type 3 # only "waiting for me to fill in"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`--type` values: `-1` all, `0` initiated by me, `3` fill in, `4` approve,
|
|
22
|
+
`5` carbon copy.
|
|
23
|
+
|
|
24
|
+
Each todo carries two IDs that every later command needs:
|
|
25
|
+
|
|
26
|
+
- `id` — the **workflow instance id** (instanceId). It is named `id`, which is
|
|
27
|
+
easy to mistake for an id of the todo itself.
|
|
28
|
+
- `workId` — the **work-item id**, identifying this step for this person.
|
|
29
|
+
|
|
30
|
+
To find which business record a todo belongs to:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
hap approval work-item <instanceId> --work-id <workId> # -> {worksheetId, rowId}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 2. Approval tasks (type 4)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
hap approval approve <instanceId> --work-id <workId> -o "Agreed"
|
|
40
|
+
hap approval reject <instanceId> --work-id <workId> -o "Does not qualify"
|
|
41
|
+
hap approval operations <instanceId> --work-id <workId> # what this step actually allows
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
When unsure which buttons a task supports, read `operations` first rather than
|
|
45
|
+
guessing.
|
|
46
|
+
|
|
47
|
+
## 3. Fill-in tasks (type 3): fill the form and submit
|
|
48
|
+
|
|
49
|
+
**Do it in one command, not two:**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
hap approval submit <instanceId> --work-id <workId> \
|
|
53
|
+
-f 'Frontend owner=谭宏' -f 'Backend owner=陈旭' -o "Assigned"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
For richer values use `--fields-json` (same shape as writing a record; per-type
|
|
57
|
+
value formats are in `hap guide record`):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
hap approval submit <instanceId> --work-id <workId> \
|
|
61
|
+
--fields-json '[{"id":"Frontend owner","value":["<member accountId>"]}]'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
To see which record would be written without touching anything:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
hap approval submit <instanceId> --work-id <workId> -f 'Amount=100' --dry-run
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Why it has to go this way
|
|
71
|
+
|
|
72
|
+
A fill-in node's fields are writable **only while you hold the task, and only
|
|
73
|
+
when the write carries the workflow identity**. So:
|
|
74
|
+
|
|
75
|
+
- A plain `hap worksheet record update <ws> <row> -f 'field=value'` returns
|
|
76
|
+
**权限不足**. This is not an account-permission problem; do not go asking an
|
|
77
|
+
admin for rights.
|
|
78
|
+
- If you must edit the record separately, carry the workflow identity:
|
|
79
|
+
`hap worksheet record update <ws> <row> --instance-id <instanceId> --work-id <workId> ...`
|
|
80
|
+
That is exactly what `approval submit -f` does internally.
|
|
81
|
+
|
|
82
|
+
### Only fields on the form can be filled
|
|
83
|
+
|
|
84
|
+
A fill-in node accepts **only the fields configured on its own form**. Anything
|
|
85
|
+
else is **discarded by the server with no error at all**. `approval submit`
|
|
86
|
+
therefore reads the write back before submitting; if a value did not land it
|
|
87
|
+
stops, names the field, and **does not submit** (the task survives, so you can
|
|
88
|
+
correct and retry). When you see that message, drop the field from the command
|
|
89
|
+
or have the flow designer add it to the node's form.
|
|
90
|
+
|
|
91
|
+
## 4. Withdraw and re-initiate
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
hap approval revoke <instanceId> # withdraw a flow you initiated
|
|
95
|
+
hap approval restart <instanceId> # re-initiate one you withdrew
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Notes:
|
|
99
|
+
|
|
100
|
+
- **Withdrawing uses the work item of the initiating step, not the approver's.**
|
|
101
|
+
Omit `--work-id` and let the CLI find that initiating work; it is more
|
|
102
|
+
reliable than filling one in by hand.
|
|
103
|
+
- `restart` requires the flow to have "allow re-initiation" enabled and
|
|
104
|
+
published, and the instance to actually be in the withdrawn state.
|
|
105
|
+
|
|
106
|
+
## 5. Other common commands
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
hap approval get <instanceId> # instance detail
|
|
110
|
+
hap approval op-history <instanceId> # processing history
|
|
111
|
+
hap approval urge <instanceId> --work-id <workId> # send a reminder
|
|
112
|
+
hap approval batch ... # bulk approve / reject
|
|
113
|
+
hap approval todo-filters ... # richer todo filtering
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The selection for a bulk operation is an **array of objects** (each with the
|
|
117
|
+
instance id and workId), not a list of id strings; see
|
|
118
|
+
`hap approval batch --help` for the exact parameters.
|
|
@@ -24,6 +24,14 @@
|
|
|
24
24
|
"zh-Hans": "顺着通知或讨论找到它所属的那条应用记录。"
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
|
+
{
|
|
28
|
+
"name": "approval",
|
|
29
|
+
"title": {"en": "Todos and approvals", "zh-Hans": "待办与审批"},
|
|
30
|
+
"summary": {
|
|
31
|
+
"en": "Find, fill in and act on workflow todos. A fill-in task's fields need the workflow identity, or the write is refused.",
|
|
32
|
+
"zh-Hans": "查看、填写、办理工作流待办。填写类待办的字段必须带工作流身份写,否则报权限不足。"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
27
35
|
{
|
|
28
36
|
"name": "chart",
|
|
29
37
|
"title": {"en": "Charts and dashboards", "zh-Hans": "统计图与看板"},
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# 待办与审批:查、填、办
|
|
2
|
+
|
|
3
|
+
`hap approval` 处理工作流待办:查看待办、填写「填写节点」的表单、审批通过/否决、
|
|
4
|
+
撤回与重新发起。本主题讲清楚每类待办该用哪个命令,以及几个「不看文档必然踩」的坑。
|
|
5
|
+
|
|
6
|
+
> 🚨 本主题核心风险:**填写类待办的字段不能用普通的记录更新去写**。直接
|
|
7
|
+
> `hap worksheet record update` 会报「权限不足」,而那个报错会把你引向完全错误的方向
|
|
8
|
+
> (以为是账号没权限)。正确做法见第 3 节。
|
|
9
|
+
|
|
10
|
+
## 1. 先看有哪些待办
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
hap approval todo-count # 各类待办数量
|
|
14
|
+
hap approval todo --type 3 # 只看「待我填写」
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`--type` 的取值:`-1` 全部、`0` 我发起的、`3` 填写、`4` 审批、`5` 抄送查看。
|
|
18
|
+
|
|
19
|
+
列表里每条待办有两个关键 ID,后续所有命令都要用:
|
|
20
|
+
|
|
21
|
+
- `id` —— **流程实例 ID**(instanceId)。字段名叫 id,容易误以为是待办自己的 id。
|
|
22
|
+
- `workId` —— **工作项 ID**,标识「这一步、这个人」的那条待办。
|
|
23
|
+
|
|
24
|
+
要知道这条待办对应哪条业务记录,用:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
hap approval work-item <实例ID> --work-id <工作项ID> # 返回 {worksheetId, rowId}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 2. 审批类待办(type 4)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
hap approval approve <实例ID> --work-id <工作项ID> -o "同意"
|
|
34
|
+
hap approval reject <实例ID> --work-id <工作项ID> -o "不符合要求"
|
|
35
|
+
hap approval operations <实例ID> --work-id <工作项ID> # 这一步到底允许哪些操作
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
拿不准某条待办支持什么按钮时,先 `operations` 看一眼,不要硬猜。
|
|
39
|
+
|
|
40
|
+
## 3. 填写类待办(type 3):填字段 + 提交
|
|
41
|
+
|
|
42
|
+
**用一条命令完成,不要分开做:**
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
hap approval submit <实例ID> --work-id <工作项ID> \
|
|
46
|
+
-f '前端负责人=谭宏' -f '后端负责人=陈旭' -o "已分配"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
复杂取值用 `--fields-json`(形态与写记录一致,各字段类型该传什么见 `hap guide record`):
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
hap approval submit <实例ID> --work-id <工作项ID> \
|
|
53
|
+
--fields-json '[{"id":"前端负责人","value":["<成员accountId>"]}]'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
不确定会写到哪条记录时,先干跑一次,它只做只读解析、不写也不提交:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
hap approval submit <实例ID> --work-id <工作项ID> -f '金额=100' --dry-run
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 为什么必须走这条路
|
|
63
|
+
|
|
64
|
+
填写节点的字段**只在你持有这条待办期间可写,而且必须带着工作流身份写**。
|
|
65
|
+
所以:
|
|
66
|
+
|
|
67
|
+
- 直接 `hap worksheet record update <表> <行> -f '字段=值'` → **权限不足**。
|
|
68
|
+
这不是账号权限问题,别去找管理员要权限。
|
|
69
|
+
- 一定要单独改记录时,带上工作流身份即可:
|
|
70
|
+
`hap worksheet record update <表> <行> --instance-id <实例ID> --work-id <工作项ID> ...`
|
|
71
|
+
`approval submit -f` 内部就是这么做的。
|
|
72
|
+
|
|
73
|
+
### 只有表单上的字段能填
|
|
74
|
+
|
|
75
|
+
一个填写节点只接受**它自己表单里配置的那些字段**。传了表单之外的字段,服务端
|
|
76
|
+
**不报错、直接丢弃**。`approval submit` 会在提交前把写入读回来核对,发现某个值没落库
|
|
77
|
+
就停下并告诉你是哪个字段、且**不会提交**(待办还在,可以改完重来)。看到这个提示时,
|
|
78
|
+
把该字段从命令里去掉,或让流程设计者把它加进节点表单。
|
|
79
|
+
|
|
80
|
+
## 4. 撤回与重新发起
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
hap approval revoke <实例ID> # 撤回自己发起的流程
|
|
84
|
+
hap approval restart <实例ID> # 重新发起已被撤回的流程
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
要点:
|
|
88
|
+
|
|
89
|
+
- **撤回用的是「最初发起节点」的工作项,不是当前审批人的**。省略 `--work-id` 让 CLI
|
|
90
|
+
自己找那条发起 work,比手填可靠。
|
|
91
|
+
- `restart` 要求该审批流程开启了「允许重新发起」并已发布,且实例确实处于撤回后的状态;
|
|
92
|
+
否则会失败。
|
|
93
|
+
|
|
94
|
+
## 5. 其它常用
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
hap approval get <实例ID> # 实例详情
|
|
98
|
+
hap approval op-history <实例ID> # 办理过程
|
|
99
|
+
hap approval urge <实例ID> --work-id <工作项ID> # 催办
|
|
100
|
+
hap approval batch ... # 批量通过/否决
|
|
101
|
+
hap approval todo-filters ... # 需要更复杂的待办筛选条件时
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
批量操作的选择项是**对象数组**(每项带实例 id 与 workId),不是 id 字符串列表;
|
|
105
|
+
具体参数以 `hap approval batch --help` 为准。
|
|
@@ -2010,8 +2010,20 @@
|
|
|
2010
2010
|
"zh-Hans": "工作项 ID"
|
|
2011
2011
|
},
|
|
2012
2012
|
"approval submit": {
|
|
2013
|
-
"en": "Submit a fill-in task.",
|
|
2014
|
-
"zh-Hans": "
|
|
2013
|
+
"en": "Submit a fill-in task, optionally filling in its form first.\n\nThe fields of a fill-in task are writable only while you hold the task,\nso pass them here rather than editing the record separately. Working\nthrough todos end to end is covered in `hap guide approval`.",
|
|
2014
|
+
"zh-Hans": "提交填写任务,可同时填写表单字段。填写节点的字段只有在你持有该待办期间才可写,所以直接在这里传,不要单独去改记录。完整的待办处理流程见 `hap guide approval`。"
|
|
2015
|
+
},
|
|
2016
|
+
"approval submit --dry-run": {
|
|
2017
|
+
"en": "Resolve the task's record and show what would be written, without filling anything in or submitting.",
|
|
2018
|
+
"zh-Hans": "只解析该待办对应的记录并显示将写入什么,不填写也不提交。"
|
|
2019
|
+
},
|
|
2020
|
+
"approval submit --field": {
|
|
2021
|
+
"en": "Field value to fill in before submitting: FIELD=VALUE (repeatable). FIELD is a field id, alias or name.",
|
|
2022
|
+
"zh-Hans": "提交前要填写的字段值:字段=值(可重复)。字段可用字段 ID、别名或名称。"
|
|
2023
|
+
},
|
|
2024
|
+
"approval submit --fields-json": {
|
|
2025
|
+
"en": "Field values to fill in before submitting, as one JSON array: [{\"id\": \"<field id or alias>\", \"value\": ...}, ...]. Use either this or -f, not both. Value formats per field type are in `hap guide record`.",
|
|
2026
|
+
"zh-Hans": "提交前要填写的字段值,一个 JSON 数组:[{\"id\":\"<字段 ID 或别名>\",\"value\":...}, ...]。与 -f 二选一。各字段类型的取值格式见 `hap guide record`。"
|
|
2015
2027
|
},
|
|
2016
2028
|
"approval submit --opinion": {
|
|
2017
2029
|
"en": "Submit comment",
|
|
@@ -4573,10 +4585,18 @@
|
|
|
4573
4585
|
"en": "All field values as one JSON array: [{\"id\": \"<field id or alias>\", \"value\": ...}, ...]. Values follow the field's kind (option fields take a list of option names or keys; member/relation fields a list of ids). Use either this or -f, not both.",
|
|
4574
4586
|
"zh-Hans": "所有字段值组成一个 JSON 数组:[{\"id\": \"<field id or alias>\", \"value\": ...}, ...]。值随字段类型而定(选项字段取选项名称或 key 列表;成员/关联字段取 ID 列表)。与 -f 二选一,不可同时使用"
|
|
4575
4587
|
},
|
|
4588
|
+
"worksheet record update --instance-id": {
|
|
4589
|
+
"en": "Workflow instance the edit belongs to. Pass together with --work-id to fill in the form of a fill-in task, whose fields are only writable while you hold that task. To fill a task and submit it in one step use `approval submit -f`; see `hap guide approval`.",
|
|
4590
|
+
"zh-Hans": "本次修改所属的工作流实例。与 --work-id 一起使用,可填写「填写节点」待办的表单——这些字段只有在你持有该待办期间才可写。若要一步完成填写并提交,用 `approval submit -f`,见 `hap guide approval`。"
|
|
4591
|
+
},
|
|
4576
4592
|
"worksheet record update --no-workflow": {
|
|
4577
4593
|
"en": "Don't trigger workflows",
|
|
4578
4594
|
"zh-Hans": "不触发工作流"
|
|
4579
4595
|
},
|
|
4596
|
+
"worksheet record update --work-id": {
|
|
4597
|
+
"en": "Workflow work-item the edit belongs to. Use with --instance-id.",
|
|
4598
|
+
"zh-Hans": "本次修改所属的工作流工作项。与 --instance-id 配合使用。"
|
|
4599
|
+
},
|
|
4580
4600
|
"worksheet rules": {
|
|
4581
4601
|
"en": "Get business rules for a worksheet.",
|
|
4582
4602
|
"zh-Hans": "获取工作表的业务规则"
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""CLI tests for filling in a task's form as part of `approval submit`.
|
|
2
|
+
|
|
3
|
+
A fill-in node's fields are writable only while the task is held, and only
|
|
4
|
+
through the workflow: a plain record update answers 权限不足, while
|
|
5
|
+
``instance/submit`` accepts a ``formData`` payload, reports success and
|
|
6
|
+
silently discards it (verified live, 2026-09-04). So the command composes
|
|
7
|
+
two proven calls — write the record under the instance/work pair, then
|
|
8
|
+
submit — and refuses to submit when the write did not land.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from unittest.mock import patch
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
from click.testing import CliRunner
|
|
16
|
+
|
|
17
|
+
from hap_cli.hap_cli import cli
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _row(**cells):
|
|
21
|
+
"""A get_record response: cell values live in rowData as a JSON string."""
|
|
22
|
+
return {"rowData": json.dumps(cells)}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestApprovalSubmitFill:
|
|
26
|
+
def test_field_and_fields_json_are_mutually_exclusive(self):
|
|
27
|
+
runner = CliRunner()
|
|
28
|
+
with patch("hap_cli.context.Context.get_session") as mock_sess:
|
|
29
|
+
result = runner.invoke(cli, [
|
|
30
|
+
"approval", "submit", "i1", "-w", "w1",
|
|
31
|
+
"-f", "a=1", "--fields-json", "[]",
|
|
32
|
+
])
|
|
33
|
+
assert result.exit_code != 0
|
|
34
|
+
assert not mock_sess.called
|
|
35
|
+
|
|
36
|
+
def test_plain_submit_still_works_without_fields(self):
|
|
37
|
+
"""No fields means no record lookup and no write — just submit."""
|
|
38
|
+
runner = CliRunner()
|
|
39
|
+
with patch("hap_cli.context.Context.get_session"), \
|
|
40
|
+
patch("hap_cli.core.instance.get_work_item") as mock_item, \
|
|
41
|
+
patch("hap_cli.core.record.update_record") as mock_upd, \
|
|
42
|
+
patch("hap_cli.core.instance.submit") as mock_submit:
|
|
43
|
+
mock_submit.return_value = True
|
|
44
|
+
result = runner.invoke(cli, ["approval", "submit", "i1", "-w", "w1"])
|
|
45
|
+
assert result.exit_code == 0, result.output
|
|
46
|
+
assert not mock_item.called
|
|
47
|
+
assert not mock_upd.called
|
|
48
|
+
assert mock_submit.called
|
|
49
|
+
|
|
50
|
+
def test_dry_run_resolves_but_never_mutates(self):
|
|
51
|
+
runner = CliRunner()
|
|
52
|
+
with patch("hap_cli.context.Context.get_session"), \
|
|
53
|
+
patch("hap_cli.core.instance.get_work_item") as mock_item, \
|
|
54
|
+
patch("hap_cli.core.record.update_record") as mock_upd, \
|
|
55
|
+
patch("hap_cli.core.instance.submit") as mock_submit:
|
|
56
|
+
mock_item.return_value = {"worksheetId": "ws1", "rowId": "r1"}
|
|
57
|
+
result = runner.invoke(cli, [
|
|
58
|
+
"approval", "submit", "i1", "-w", "w1",
|
|
59
|
+
"-f", "opinion=ok", "--dry-run",
|
|
60
|
+
])
|
|
61
|
+
assert result.exit_code == 0, result.output
|
|
62
|
+
assert mock_item.called # read-only resolution happened
|
|
63
|
+
assert not mock_upd.called # but nothing was written
|
|
64
|
+
assert not mock_submit.called # and nothing was submitted
|
|
65
|
+
|
|
66
|
+
def test_fields_are_written_under_the_workflow_before_submitting(self):
|
|
67
|
+
runner = CliRunner()
|
|
68
|
+
with patch("hap_cli.context.Context.get_session"), \
|
|
69
|
+
patch("hap_cli.core.instance.get_work_item") as mock_item, \
|
|
70
|
+
patch("hap_cli.core.record.encode_controls_by_id") as mock_enc, \
|
|
71
|
+
patch("hap_cli.core.record.update_record") as mock_upd, \
|
|
72
|
+
patch("hap_cli.core.record.get_record") as mock_get, \
|
|
73
|
+
patch("hap_cli.core.instance.submit") as mock_submit:
|
|
74
|
+
mock_item.return_value = {"worksheetId": "ws1", "rowId": "r1"}
|
|
75
|
+
mock_enc.return_value = [{"controlId": "c1", "value": "ok"}]
|
|
76
|
+
mock_get.return_value = _row(c1="ok")
|
|
77
|
+
mock_submit.return_value = True
|
|
78
|
+
result = runner.invoke(cli, [
|
|
79
|
+
"approval", "submit", "i1", "-w", "w1", "-f", "opinion=ok",
|
|
80
|
+
])
|
|
81
|
+
assert result.exit_code == 0, result.output
|
|
82
|
+
# The write must carry the workflow identity, or it is 权限不足.
|
|
83
|
+
kwargs = mock_upd.call_args[1]
|
|
84
|
+
assert kwargs["instance_id"] == "i1"
|
|
85
|
+
assert kwargs["work_id"] == "w1"
|
|
86
|
+
assert mock_submit.called
|
|
87
|
+
|
|
88
|
+
def test_silently_dropped_field_blocks_the_submit(self):
|
|
89
|
+
"""The server drops fields outside the node's form without erroring.
|
|
90
|
+
|
|
91
|
+
Submitting anyway would lose the value and consume the task, so the
|
|
92
|
+
command must stop and name the field.
|
|
93
|
+
"""
|
|
94
|
+
runner = CliRunner()
|
|
95
|
+
with patch("hap_cli.context.Context.get_session"), \
|
|
96
|
+
patch("hap_cli.core.instance.get_work_item") as mock_item, \
|
|
97
|
+
patch("hap_cli.core.record.encode_controls_by_id") as mock_enc, \
|
|
98
|
+
patch("hap_cli.core.record.update_record"), \
|
|
99
|
+
patch("hap_cli.core.record.get_record") as mock_get, \
|
|
100
|
+
patch("hap_cli.core.instance.submit") as mock_submit:
|
|
101
|
+
mock_item.return_value = {"worksheetId": "ws1", "rowId": "r1"}
|
|
102
|
+
mock_enc.return_value = [{"controlId": "cAmt", "value": "8888"}]
|
|
103
|
+
mock_get.return_value = _row(cAmt="") # write did not land
|
|
104
|
+
result = runner.invoke(cli, [
|
|
105
|
+
"approval", "submit", "i1", "-w", "w1", "-f", "金额=8888",
|
|
106
|
+
])
|
|
107
|
+
assert result.exit_code != 0
|
|
108
|
+
assert "金额" in result.output # named, not a raw control id
|
|
109
|
+
assert not mock_submit.called # task must survive
|
|
110
|
+
|
|
111
|
+
def test_write_succeeds_but_submit_fails_surfaces_the_error(self):
|
|
112
|
+
"""Partial state must be reported, not swallowed."""
|
|
113
|
+
runner = CliRunner()
|
|
114
|
+
with patch("hap_cli.context.Context.get_session"), \
|
|
115
|
+
patch("hap_cli.core.instance.get_work_item") as mock_item, \
|
|
116
|
+
patch("hap_cli.core.record.encode_controls_by_id") as mock_enc, \
|
|
117
|
+
patch("hap_cli.core.record.update_record") as mock_upd, \
|
|
118
|
+
patch("hap_cli.core.record.get_record") as mock_get, \
|
|
119
|
+
patch("hap_cli.core.instance.submit") as mock_submit:
|
|
120
|
+
mock_item.return_value = {"worksheetId": "ws1", "rowId": "r1"}
|
|
121
|
+
mock_enc.return_value = [{"controlId": "c1", "value": "ok"}]
|
|
122
|
+
mock_get.return_value = _row(c1="ok")
|
|
123
|
+
mock_submit.side_effect = RuntimeError("submit blew up")
|
|
124
|
+
result = runner.invoke(cli, [
|
|
125
|
+
"approval", "submit", "i1", "-w", "w1", "-f", "opinion=ok",
|
|
126
|
+
])
|
|
127
|
+
assert result.exit_code != 0
|
|
128
|
+
assert mock_upd.called # the write did happen
|
|
129
|
+
assert "submit blew up" in result.output
|
|
130
|
+
|
|
131
|
+
def test_unresolvable_record_fails_before_writing(self):
|
|
132
|
+
runner = CliRunner()
|
|
133
|
+
with patch("hap_cli.context.Context.get_session"), \
|
|
134
|
+
patch("hap_cli.core.instance.get_work_item") as mock_item, \
|
|
135
|
+
patch("hap_cli.core.record.update_record") as mock_upd, \
|
|
136
|
+
patch("hap_cli.core.instance.submit") as mock_submit:
|
|
137
|
+
mock_item.return_value = {}
|
|
138
|
+
result = runner.invoke(cli, [
|
|
139
|
+
"approval", "submit", "i1", "-w", "w1", "-f", "opinion=ok",
|
|
140
|
+
])
|
|
141
|
+
assert result.exit_code != 0
|
|
142
|
+
assert not mock_upd.called
|
|
143
|
+
assert not mock_submit.called
|