hap-cli 0.9.2__tar.gz → 0.9.3__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.9.2 → hap_cli-0.9.3}/PKG-INFO +1 -1
- {hap_cli-0.9.2 → hap_cli-0.9.3}/app_live_tests/smoke.py +84 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/__init__.py +1 -1
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/context.py +5 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/field_spec_adapter.py +6 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/worksheet.py +131 -12
- hap_cli-0.9.3/hap_cli/tests/test_create_worksheet_partial.py +231 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli.egg-info/PKG-INFO +1 -1
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli.egg-info/SOURCES.txt +1 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/MANIFEST.in +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/README.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/app_live_tests/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/app_live_tests/__main__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/app_live_tests/config.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/app_live_tests/fixtures/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/app_live_tests/fixtures/feedback_scenario.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/app_live_tests/harness.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/app_live_tests/state.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/README.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/README_CN.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/__main__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/_entry.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/app_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/app_creator_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/app_editor_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/attachment_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/auth_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/calendar_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/chart_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/chat_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/chatbot_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/config_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/contact_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/department_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/group_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/guide_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/icon_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/instance_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/node_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/optionset_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/page_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/post_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/record_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/region_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/role_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/update_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/upload_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/v3_registry.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/workflow_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/commands/worksheet_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/action_spec_adapter.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/charts.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/cleanup.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/compiler.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/config.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/design/design.schema.json +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/errors.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/executor.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/fields.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/hap.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/merge.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/recording/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/recording/console.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/recording/mirror.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/recording/report.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/schema.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/executor.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/seed/template.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/selftest.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/smoke.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/steps.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/store.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/validate.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/attachment.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/auth.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/calendar_mod.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/chart.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/chat.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/chatbot.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/cli_access.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/condition_dsl.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/contact.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/control_type_codes.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/department.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/apply.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/componentlower.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/editspec/component.schema.json +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/editspec/field.schema.json +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/editspec.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/errors.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/fieldlower.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/jsonschema_mini.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/models.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/ops.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/planner.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/editor/reader.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/field_normalizer.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/filter_translator.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/filter_v3.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/flow_node.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/global_meta.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/group.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/guide.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/icon_index.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/instance.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/layout_check.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/logger.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/page.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/post.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/record.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/record_defaults.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/record_form_checks.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/record_lookups.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/record_rules.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/relation_scope.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/role.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/role_perm_builder.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/rule_spec_adapter.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/session.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/token_crypto.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/update.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/upload.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/v3/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/v3/dispatcher.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/v3/render.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/v3/schema.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/view_spec_adapter.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/workflow_node_dsl.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/worksheet_form.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/worksheet_reference.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/core/worksheet_templates.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/en/approval.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/en/chart.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/en/messages.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/en/overview.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/en/post.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/en/record.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/en/setup.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/en/skills.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/en/workflow.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/en/worksheet.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/index.json +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/approval.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/chart.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/messages.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/overview.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/post.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/record.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/setup.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/skills.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/workflow.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/guide/zh-Hans/worksheet.md +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/i18n.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/locale/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/locale/messages.json +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/locale/messages.schema.json +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/conftest.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_action_mdpublic_parity.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_advanced_setting_guard.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_app_creator.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_app_editor.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_app_export_usage.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_app_light_color.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_app_section_name.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_approval_submit_fill_cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_approval_wire.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_atomic_write.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_attachment_share.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_auth_prerelease.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_batch_add_atomic.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_bug_036430.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_bug_036750.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_calendar_date_range.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_calendar_discussion_cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_chart.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_chart_filter_merge.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_chat_attachment_send.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_chat_messages_cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_chat_recall_cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_chat_send_socket.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_cli_access.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_coded_responses.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_condition_dsl.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_condition_dsl_entries.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_condition_dsl_readback.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_config_cmd.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_content_language.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_control_lookup_guard.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_core.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_dsl_existing_node_refs.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_dsl_multi_record_guard.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_editor_inline_subtable.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_engine_core_parity.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_arg_consistency.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_bidirectional.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_discussion_mentions.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_entry_module.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_field_record_ref.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_http_error_body.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_json_anywhere.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_json_file_arg.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_layout_check.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_node_get_detail.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_node_summary.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_post_cursor.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_publish_reasons.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_replicate_workflow.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_save_fields_shape.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_subgroups.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_subtable_read.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_worksheet_references.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_worksheet_visibility.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fb_worksheet_workflows.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_fields_mdpublic_parity.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_full_e2e.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_g1_chart_parity.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_g1_record_parity.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_g1_upload_parity.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_g1_worksheet_parity.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_g2_coverage_parity.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_g3_workflow_parity.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_g4_approval_parity.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_g5_low_frequency_parity.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_g6_advanced_fields.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_g6_fields_roundtrip.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_g7_misc_gaps.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_g7b_permanent_delete.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_g8_filter_operator.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_g8b_pivot_operators.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_g8c_empty_filter_and_help_truth.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_g9_appid_resolve.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_global_meta.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_guide.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_guide_sections.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_i18n.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_inner_flow_batch_add.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_instance_envelope.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_instance_history_cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_integration_approval.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_integration_approval_actions.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_integration_misc.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_integration_social.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_integration_v3.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_jsonschema_mini_copy.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_multi_profile.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_noninteractive_confirm.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_org_id_cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_page_filters_group_read.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_page_filters_group_update.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_page_save_page_id.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_post_scope_cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_record_business_rules.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_record_defaults.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_record_doc_id_hidden.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_record_field_alias.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_record_form_checks.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_record_run_action.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_record_write_fixes.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_relation_scope.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_rmw_mdpublic_parity.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_role_app_scope.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_rule_filters_and_field_warning.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_save_action_silent_drop.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_save_rule_spec.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_select_add_option.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_semantic_types_registry.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_subtable_mdpublic_parity.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_update.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_update_mode.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_upload_allow_down.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_upload_host_resolution.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_upload_mdpublic_parity.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_v3_bool_flags.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_v3_dispatcher.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_v3_response_format_json.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_v3_session.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_view_mdpublic_parity.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_wf_config_keys.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_wf_inner_scope.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_wf_json_file_arg.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_wf_node_delete_missing.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_wf_save_action_dsl_fields.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_wf_structure_listing.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_wf_trigger_outcome.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_wf_trigger_preserve.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_wo4_result_handling.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_code_node.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_condition_field_types.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_condition_slots.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_delete_cascade.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_lifecycle_wire.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_list_cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_node_wire.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_publish_inner.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_worksheet_create_section.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_worksheet_filter_read.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_worksheet_switch.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/utils/atomic_write.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/utils/confirm.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli.egg-info/requires.txt +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/__main__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/_wftest.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/charts.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/cleanup.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/compiler.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/config.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/errors.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/executor.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/fields.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/hap.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/recording/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/recording/console.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/recording/jsonl.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/recording/mirror.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/recording/report.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/schema.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/seed/__init__.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/seed/cli.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/seed/executor.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/seed/template.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/selftest.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/smoke.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/step.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/steps.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/store.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/live_tests/workflow_dsl.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/setup.cfg +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/setup.py +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/INDEX.json +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/create_optionset.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/create_role.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/delete_optionset.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/delete_record.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/delete_role.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/get_app_info.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/get_record_details.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/get_record_list.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/get_record_logs.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/get_record_relations.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/get_regions.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/get_role_details.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/get_role_list.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/knowledge_search.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
- {hap_cli-0.9.2 → hap_cli-0.9.3}/sources/v3-api-schema/update_optionset.yaml +0 -0
|
@@ -3602,6 +3602,89 @@ def case_editor_inline_subtable(app_id: str, section_id: str) -> None:
|
|
|
3602
3602
|
hap(["worksheet", "delete", parent, "-a", app_id, "-y"], check=False)
|
|
3603
3603
|
|
|
3604
3604
|
|
|
3605
|
+
def case_create_partial(app_id: str, section_id: str) -> None:
|
|
3606
|
+
"""Bug #455775: a failed `worksheet create --fields` leaves nothing."""
|
|
3607
|
+
print("\nscenario: worksheet create --fields failure (#455775)")
|
|
3608
|
+
import json as _json
|
|
3609
|
+
from hap_cli.core import app as app_mod
|
|
3610
|
+
from hap_cli.core.session import Session
|
|
3611
|
+
session = Session.load()
|
|
3612
|
+
project_id = app_mod.app_project_id(session, app_id)
|
|
3613
|
+
prefix = "QA455775"
|
|
3614
|
+
for leftover in [n for n in _worksheets(app_id) if n.startswith(prefix)]:
|
|
3615
|
+
_ensure_absent(app_id, leftover)
|
|
3616
|
+
|
|
3617
|
+
def _ours() -> list[str]:
|
|
3618
|
+
return [n for n in _worksheets(app_id) if n.startswith(prefix)]
|
|
3619
|
+
|
|
3620
|
+
def _purge(ws_id: str) -> None:
|
|
3621
|
+
try:
|
|
3622
|
+
app_mod.purge_app_item(session, app_id=app_id,
|
|
3623
|
+
project_id=project_id,
|
|
3624
|
+
section_id=section_id, item_id=ws_id,
|
|
3625
|
+
item_type=0)
|
|
3626
|
+
except Exception as exc: # noqa: BLE001 — reported, not fatal
|
|
3627
|
+
print(f" note: could not purge {ws_id}: {exc}")
|
|
3628
|
+
|
|
3629
|
+
single = {"source": "static", "value": "DEFAULT-QA"}
|
|
3630
|
+
ok, res = _try(["worksheet", "create", app_id, f"{prefix} Invalid",
|
|
3631
|
+
"--section-id", section_id, "--fields", _json.dumps([
|
|
3632
|
+
{"type": "Text", "name": "名称"},
|
|
3633
|
+
{"type": "Text", "name": "备注",
|
|
3634
|
+
"defaultValue": single}], ensure_ascii=False)])
|
|
3635
|
+
check("create-partial: a single-object default is refused", not ok,
|
|
3636
|
+
str(res)[:200])
|
|
3637
|
+
check("create-partial: the refusal names the field and the shape",
|
|
3638
|
+
"备注" in str(res) and "list of objects" in str(res),
|
|
3639
|
+
str(res)[:300])
|
|
3640
|
+
check("create-partial: nothing was created", _ours() == [], str(_ours()))
|
|
3641
|
+
|
|
3642
|
+
# Passes every offline check, refused by the server at save time:
|
|
3643
|
+
# an alias the worksheet's own record-id column already holds.
|
|
3644
|
+
ok, res = _try(["worksheet", "create", app_id, f"{prefix} PostFail",
|
|
3645
|
+
"--section-id", section_id, "--fields", _json.dumps([
|
|
3646
|
+
{"type": "Text", "name": "名称"},
|
|
3647
|
+
{"type": "Text", "name": "冲突",
|
|
3648
|
+
"extra": {"alias": "rowid"}}],
|
|
3649
|
+
ensure_ascii=False)])
|
|
3650
|
+
check("create-partial: a save rejected after the create fails", not ok,
|
|
3651
|
+
str(res)[:200])
|
|
3652
|
+
check("create-partial: the error says the create was undone",
|
|
3653
|
+
"undone" in str(res), str(res)[:300])
|
|
3654
|
+
check("create-partial: no half-built worksheet is left",
|
|
3655
|
+
_ours() == [], str(_ours()))
|
|
3656
|
+
|
|
3657
|
+
ws_id = ""
|
|
3658
|
+
try:
|
|
3659
|
+
made = hap(["worksheet", "create", app_id, f"{prefix} Array",
|
|
3660
|
+
"--section-id", section_id, "--fields", _json.dumps([
|
|
3661
|
+
{"type": "Text", "name": "名称"},
|
|
3662
|
+
{"type": "Text", "name": "备注",
|
|
3663
|
+
"defaultValue": [single]}], ensure_ascii=False)],
|
|
3664
|
+
check=False)
|
|
3665
|
+
ws_id = made.get("worksheetId", "") if isinstance(made, dict) else ""
|
|
3666
|
+
check("create-partial: the list form still creates", bool(ws_id),
|
|
3667
|
+
str(made)[:200])
|
|
3668
|
+
if not ws_id:
|
|
3669
|
+
return
|
|
3670
|
+
note = next((c for c in _fields(ws_id)
|
|
3671
|
+
if c.get("controlName") == "备注"), {})
|
|
3672
|
+
check("create-partial: its default is stored",
|
|
3673
|
+
"DEFAULT-QA" in str((note.get("advancedSetting") or {})
|
|
3674
|
+
.get("defsource")), str(note)[:200])
|
|
3675
|
+
ok, res = _try(["worksheet", "update-fields", ws_id, "--fields",
|
|
3676
|
+
_json.dumps([{"type": "Text", "name": "名称"},
|
|
3677
|
+
{"type": "Text", "name": "备注",
|
|
3678
|
+
"defaultValue": single}],
|
|
3679
|
+
ensure_ascii=False)])
|
|
3680
|
+
check("create-partial: update-fields refuses it clearly too",
|
|
3681
|
+
not ok and "defaultValue" in str(res)
|
|
3682
|
+
and "AttributeError" not in str(res), str(res)[:300])
|
|
3683
|
+
finally:
|
|
3684
|
+
if ws_id:
|
|
3685
|
+
_purge(ws_id)
|
|
3686
|
+
|
|
3687
|
+
|
|
3605
3688
|
def case_fb_subgroups(app_id: str, section_id: str) -> None:
|
|
3606
3689
|
"""WO-FB 3.1: the editor can see worksheets in second-level groups."""
|
|
3607
3690
|
print("\nscenario: sub-group visibility (WO-FB 3.1)")
|
|
@@ -4792,6 +4875,7 @@ _CASES = {
|
|
|
4792
4875
|
"fb-layout-check": case_fb_layout_check,
|
|
4793
4876
|
"fb-pair-relation": case_fb_pair_relation,
|
|
4794
4877
|
"fb-subtable-read": case_fb_subtable_read,
|
|
4878
|
+
"create-partial": case_create_partial,
|
|
4795
4879
|
"editor-inline-subtable": case_editor_inline_subtable,
|
|
4796
4880
|
"fb-subgroups": case_fb_subgroups,
|
|
4797
4881
|
"fb-button-target": case_fb_button_target,
|
|
@@ -84,6 +84,11 @@ class Context:
|
|
|
84
84
|
is_auth = isinstance(e, (AuthenticationError, SessionError))
|
|
85
85
|
if self.json_mode:
|
|
86
86
|
payload = {"error": msg, "type": type(e).__name__}
|
|
87
|
+
# An error may carry state the caller needs to act on, e.g.
|
|
88
|
+
# the id of a worksheet a failed create left behind.
|
|
89
|
+
details = getattr(e, "details", None)
|
|
90
|
+
if isinstance(details, dict):
|
|
91
|
+
payload.update(details)
|
|
87
92
|
if is_auth:
|
|
88
93
|
payload["hint"] = NOT_LOGGED_IN_HINT
|
|
89
94
|
payload["not_logged_in"] = True
|
|
@@ -156,6 +156,12 @@ def adapt_relation_config(spec: dict[str, Any]) -> None:
|
|
|
156
156
|
spec["advanced_setting"] = adv
|
|
157
157
|
|
|
158
158
|
|
|
159
|
+
# The ``source`` values build_defsource below can encode. Anything else
|
|
160
|
+
# was skipped there without a word; the field-spec check refuses it
|
|
161
|
+
# up front instead (bug #455775).
|
|
162
|
+
DEFAULT_VALUE_SOURCES = ("system", "static", "field", "relation")
|
|
163
|
+
|
|
164
|
+
|
|
159
165
|
def build_defsource(
|
|
160
166
|
default_value: list[dict[str, Any]],
|
|
161
167
|
*,
|
|
@@ -490,9 +490,24 @@ def create_worksheet_with_fields(
|
|
|
490
490
|
``{"worksheetId": "...", "saveResult": <SaveWorksheetControls
|
|
491
491
|
response>}``
|
|
492
492
|
"""
|
|
493
|
-
#
|
|
494
|
-
#
|
|
495
|
-
|
|
493
|
+
# Shape the whole field payload before the worksheet exists. Every
|
|
494
|
+
# step of it is offline here (no default columns are kept, so nothing
|
|
495
|
+
# is read back), and it is the same shaping update_fields and
|
|
496
|
+
# save_controls run below — so any spec they would refuse is refused
|
|
497
|
+
# now, instead of after AddWorkSheet left a worksheet behind with
|
|
498
|
+
# only its auto-created columns (bug #455775). The host id only
|
|
499
|
+
# feeds a two-way relation's reverse half; a stand-in builds it the
|
|
500
|
+
# same way the real one will.
|
|
501
|
+
preview = build_fields_payload(
|
|
502
|
+
fields, worksheet_id=_PREFLIGHT_WORKSHEET_ID, title_name=title_name,
|
|
503
|
+
keep_default_controls=False)
|
|
504
|
+
_normalize_defsource(_checked_values(preview))
|
|
505
|
+
# Resolved here rather than left to create_worksheet (which would do
|
|
506
|
+
# the same lookup): undoing a failed create needs the group too, and
|
|
507
|
+
# without it the removal does not reach the recycle bin (seen live
|
|
508
|
+
# 2026-10-08).
|
|
509
|
+
if not section_id:
|
|
510
|
+
section_id = _first_app_section_id(session, app_id)
|
|
496
511
|
created = create_worksheet(
|
|
497
512
|
session, app_id, name,
|
|
498
513
|
project_id=project_id, section_id=section_id,
|
|
@@ -505,18 +520,86 @@ def create_worksheet_with_fields(
|
|
|
505
520
|
raise ValueError(
|
|
506
521
|
f"AddWorkSheet did not return a worksheetId; got {created!r}"
|
|
507
522
|
)
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
523
|
+
try:
|
|
524
|
+
save_result = update_fields(
|
|
525
|
+
session, worksheet_id, fields,
|
|
526
|
+
title_name=title_name,
|
|
527
|
+
# Match `worksheet update-fields` CLI: drop the auto-created
|
|
528
|
+
# 名称/描述/附件 columns so the caller's `fields[]` is the full
|
|
529
|
+
# layout. Callers wanting to preserve them can issue an extra
|
|
530
|
+
# update-fields call with keep_default_controls=True.
|
|
531
|
+
keep_default_controls=False,
|
|
532
|
+
)
|
|
533
|
+
# The save reports a rejection in its body rather than raising.
|
|
534
|
+
if isinstance(save_result, dict) and "code" in save_result \
|
|
535
|
+
and save_result.get("code") != 1:
|
|
536
|
+
raise APIError(
|
|
537
|
+
f"The fields were not saved: "
|
|
538
|
+
f"{save_result.get('msg') or save_result.get('code')}",
|
|
539
|
+
code=save_result.get("code"))
|
|
540
|
+
except Exception as exc:
|
|
541
|
+
raise _undo_partial_create(
|
|
542
|
+
session, exc, app_id=app_id, project_id=project_id,
|
|
543
|
+
section_id=section_id, worksheet_id=worksheet_id) from exc
|
|
517
544
|
return {"worksheetId": worksheet_id, "saveResult": save_result}
|
|
518
545
|
|
|
519
546
|
|
|
547
|
+
_PREFLIGHT_WORKSHEET_ID = "0" * 24
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
class WorksheetCreateError(APIError):
|
|
551
|
+
"""A create that failed after the worksheet already existed.
|
|
552
|
+
|
|
553
|
+
``details`` is added to the CLI's JSON error output, so a caller
|
|
554
|
+
reading it can tell whether the worksheet is gone or still there.
|
|
555
|
+
"""
|
|
556
|
+
|
|
557
|
+
def __init__(self, message: str, *, worksheet_id: str,
|
|
558
|
+
rolled_back: bool, code: Optional[int] = None):
|
|
559
|
+
super().__init__(message, code=code)
|
|
560
|
+
self.worksheet_id = worksheet_id
|
|
561
|
+
self.rolled_back = rolled_back
|
|
562
|
+
self.details = {"worksheetId": worksheet_id,
|
|
563
|
+
"rolledBack": rolled_back}
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def _undo_partial_create(session: Session, exc: Exception, *, app_id: str,
|
|
567
|
+
project_id: str, section_id: str,
|
|
568
|
+
worksheet_id: str) -> WorksheetCreateError:
|
|
569
|
+
"""Remove the worksheet a failed create left behind.
|
|
570
|
+
|
|
571
|
+
It is removed the way the product removes one — to the recycle bin,
|
|
572
|
+
then out of it (``app.purge_app_item``) — since it holds nothing but
|
|
573
|
+
the auto-created columns and keeping it in the bin would only leave
|
|
574
|
+
a stray to clean up. When that fails as well, the error names the
|
|
575
|
+
worksheet so the caller can finish the job. Either way the original
|
|
576
|
+
reason leads the message.
|
|
577
|
+
"""
|
|
578
|
+
from hap_cli.core import app as app_mod
|
|
579
|
+
reason = str(exc).rstrip(".") or type(exc).__name__
|
|
580
|
+
code = getattr(exc, "code", None)
|
|
581
|
+
logger.warning(
|
|
582
|
+
"worksheet create: fields failed on new worksheet %s (%s); "
|
|
583
|
+
"removing it", worksheet_id, reason)
|
|
584
|
+
try:
|
|
585
|
+
app_mod.purge_app_item(
|
|
586
|
+
session, app_id=app_id, project_id=project_id,
|
|
587
|
+
section_id=section_id, item_id=worksheet_id, item_type=0)
|
|
588
|
+
except Exception as undo_exc: # noqa: BLE001 — reported below
|
|
589
|
+
logger.warning("worksheet create: could not remove %s: %s",
|
|
590
|
+
worksheet_id, undo_exc, exc_info=True)
|
|
591
|
+
return WorksheetCreateError(
|
|
592
|
+
f"{reason}. Worksheet {worksheet_id} was created but its "
|
|
593
|
+
f"fields were not written — delete it, or retry with "
|
|
594
|
+
f"'hap worksheet update-fields {worksheet_id}'.",
|
|
595
|
+
worksheet_id=worksheet_id, rolled_back=False, code=code)
|
|
596
|
+
logger.warning("worksheet create: removed %s", worksheet_id)
|
|
597
|
+
return WorksheetCreateError(
|
|
598
|
+
f"{reason}. The worksheet it had just created was undone; "
|
|
599
|
+
f"nothing was left in the app.",
|
|
600
|
+
worksheet_id=worksheet_id, rolled_back=True, code=code)
|
|
601
|
+
|
|
602
|
+
|
|
520
603
|
def copy_worksheet(
|
|
521
604
|
session: Session,
|
|
522
605
|
worksheet_id: str,
|
|
@@ -1026,6 +1109,7 @@ def check_field_specs(fields: Any) -> None:
|
|
|
1026
1109
|
f'"Text" / "Number" / "Date" / "Dropdown".')
|
|
1027
1110
|
name = spec.get("name")
|
|
1028
1111
|
if isinstance(name, str) and name.strip():
|
|
1112
|
+
_check_default_value(spec, f'{where} ("{name}")')
|
|
1029
1113
|
continue
|
|
1030
1114
|
alias = next((k for k in _FIELD_SPEC_NAME_ALIASES if k in spec), "")
|
|
1031
1115
|
if alias:
|
|
@@ -1041,6 +1125,41 @@ def check_field_specs(fields: Any) -> None:
|
|
|
1041
1125
|
f'name it will show under.')
|
|
1042
1126
|
|
|
1043
1127
|
|
|
1128
|
+
_DEFAULT_VALUE_EXAMPLE = '"defaultValue": [{"source": "static", "value": "..."}]'
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
def _check_default_value(spec: dict[str, Any], where: str) -> None:
|
|
1132
|
+
"""Refuse a ``defaultValue`` the field shaping cannot read.
|
|
1133
|
+
|
|
1134
|
+
It is a list of objects, each naming a ``source`` that
|
|
1135
|
+
``field_spec_adapter.build_defsource`` encodes. A single object or a
|
|
1136
|
+
plain string used to pass this check and crash the shaping halfway —
|
|
1137
|
+
on a create, after the worksheet already existed (bug #455775). An
|
|
1138
|
+
unknown ``source`` was dropped there silently.
|
|
1139
|
+
"""
|
|
1140
|
+
from hap_cli.core.field_spec_adapter import DEFAULT_VALUE_SOURCES
|
|
1141
|
+
value = spec.get("defaultValue")
|
|
1142
|
+
if value in (None, []):
|
|
1143
|
+
return
|
|
1144
|
+
sources = ", ".join(DEFAULT_VALUE_SOURCES)
|
|
1145
|
+
rule = (f"Write it as a list of objects, e.g. {_DEFAULT_VALUE_EXAMPLE}; "
|
|
1146
|
+
f'"source" is one of {sources}.')
|
|
1147
|
+
if not isinstance(value, list):
|
|
1148
|
+
kind = "a single object" if isinstance(value, dict) \
|
|
1149
|
+
else type(value).__name__
|
|
1150
|
+
raise ValueError(f'{where} has "defaultValue" as {kind}. {rule}')
|
|
1151
|
+
for n, entry in enumerate(value, start=1):
|
|
1152
|
+
if not isinstance(entry, dict):
|
|
1153
|
+
raise ValueError(
|
|
1154
|
+
f'{where}: "defaultValue" entry {n} is '
|
|
1155
|
+
f"{type(entry).__name__}, not an object. {rule}")
|
|
1156
|
+
if entry.get("source") not in DEFAULT_VALUE_SOURCES:
|
|
1157
|
+
got = entry.get("source")
|
|
1158
|
+
said = f'"source" {got!r}' if got is not None else 'no "source"'
|
|
1159
|
+
raise ValueError(
|
|
1160
|
+
f'{where}: "defaultValue" entry {n} has {said}. {rule}')
|
|
1161
|
+
|
|
1162
|
+
|
|
1044
1163
|
def build_fields_payload(
|
|
1045
1164
|
fields: list[dict[str, Any]],
|
|
1046
1165
|
*,
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Bug #455775: `worksheet create --fields` left a half-built worksheet.
|
|
2
|
+
|
|
3
|
+
A Text field with ``"defaultValue": {"source": "static", ...}`` (one
|
|
4
|
+
object, not a list) passed the pre-create check, the worksheet was
|
|
5
|
+
created, and shaping the fields then crashed with ``AttributeError`` —
|
|
6
|
+
the app kept a worksheet holding only the auto-created columns and the
|
|
7
|
+
caller never learned its id.
|
|
8
|
+
|
|
9
|
+
Two layers: every field-shaping error now surfaces before the worksheet
|
|
10
|
+
exists, and a failure after it exists undoes the create (or, when that
|
|
11
|
+
fails too, names the worksheet that was left behind).
|
|
12
|
+
"""
|
|
13
|
+
import json
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
from click.testing import CliRunner
|
|
17
|
+
|
|
18
|
+
from hap_cli.core import app as app_mod
|
|
19
|
+
from hap_cli.core import worksheet as ws_mod
|
|
20
|
+
from hap_cli.core.session import APIError, Session
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
GOOD = {"type": "Text", "name": "名称"}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def tmp_config(tmp_path, monkeypatch):
|
|
28
|
+
cfg = tmp_path / ".hap-cli"
|
|
29
|
+
monkeypatch.setattr("hap_cli.core.session.CONFIG_DIR", cfg)
|
|
30
|
+
monkeypatch.setattr("hap_cli.core.session.CONFIG_FILE",
|
|
31
|
+
cfg / "config.json")
|
|
32
|
+
monkeypatch.setattr("hap_cli.core.token_crypto._KEY_FILE", cfg / ".key")
|
|
33
|
+
return cfg
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _with_default(value):
|
|
37
|
+
return [GOOD, {"type": "Text", "name": "备注", "defaultValue": value}]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ── defaultValue shape is checked before anything is sent ────────────────
|
|
41
|
+
class TestDefaultValueShape:
|
|
42
|
+
@pytest.mark.parametrize("value", [
|
|
43
|
+
{"source": "static", "value": "DEFAULT-QA"}, # the report
|
|
44
|
+
"DEFAULT-QA",
|
|
45
|
+
["x"],
|
|
46
|
+
[{"value": "x"}], # no source
|
|
47
|
+
[{"source": "constant", "value": "x"}], # unknown source
|
|
48
|
+
7,
|
|
49
|
+
])
|
|
50
|
+
def test_refused_with_field_and_right_shape(self, value):
|
|
51
|
+
with pytest.raises(ValueError) as exc:
|
|
52
|
+
ws_mod.check_field_specs(_with_default(value))
|
|
53
|
+
msg = str(exc.value)
|
|
54
|
+
assert "Field 2" in msg and "备注" in msg
|
|
55
|
+
assert '"defaultValue": [{"source": "static"' in msg
|
|
56
|
+
for name in ("system", "static", "field", "relation"):
|
|
57
|
+
assert name in msg
|
|
58
|
+
|
|
59
|
+
@pytest.mark.parametrize("value", [
|
|
60
|
+
None, [],
|
|
61
|
+
[{"source": "static", "value": "x"}],
|
|
62
|
+
[{"source": "system", "value": "currentUser"}],
|
|
63
|
+
[{"source": "field", "field": "abc"}],
|
|
64
|
+
[{"source": "relation", "relationField": "r", "field": "f"}],
|
|
65
|
+
])
|
|
66
|
+
def test_accepted(self, value):
|
|
67
|
+
ws_mod.check_field_specs(_with_default(value))
|
|
68
|
+
|
|
69
|
+
def test_update_fields_refuses_cleanly_not_attribute_error(self):
|
|
70
|
+
with pytest.raises(ValueError, match="defaultValue"):
|
|
71
|
+
ws_mod.build_fields_payload(
|
|
72
|
+
_with_default({"source": "static", "value": "x"}),
|
|
73
|
+
keep_default_controls=False)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ── create: preflight builds the whole payload offline ───────────────────
|
|
77
|
+
class _Recorder:
|
|
78
|
+
def __init__(self, monkeypatch, *, save=None, save_raises=None,
|
|
79
|
+
purge_raises=None):
|
|
80
|
+
self.calls = []
|
|
81
|
+
rec = self
|
|
82
|
+
|
|
83
|
+
def fake_create(session, app_id, name, **kw):
|
|
84
|
+
rec.calls.append(("create", app_id, name, kw.get("section_id")))
|
|
85
|
+
return {"worksheetId": "ws_new"}
|
|
86
|
+
|
|
87
|
+
def fake_update(session, worksheet_id, fields, **kw):
|
|
88
|
+
rec.calls.append(("update", worksheet_id))
|
|
89
|
+
if save_raises:
|
|
90
|
+
raise save_raises
|
|
91
|
+
return save if save is not None else {"code": 1, "data": {}}
|
|
92
|
+
|
|
93
|
+
def fake_purge(session, **kw):
|
|
94
|
+
rec.calls.append(("purge", kw.get("item_id"), kw.get("item_type"),
|
|
95
|
+
kw.get("app_id")))
|
|
96
|
+
if purge_raises:
|
|
97
|
+
raise purge_raises
|
|
98
|
+
return {"deleted": kw.get("item_id"), "permanently": True}
|
|
99
|
+
|
|
100
|
+
monkeypatch.setattr(ws_mod, "create_worksheet", fake_create)
|
|
101
|
+
monkeypatch.setattr(ws_mod, "update_fields", fake_update)
|
|
102
|
+
monkeypatch.setattr(app_mod, "purge_app_item", fake_purge)
|
|
103
|
+
|
|
104
|
+
def kinds(self):
|
|
105
|
+
return [c[0] for c in self.calls]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _create(fields, **kw):
|
|
109
|
+
return ws_mod.create_worksheet_with_fields(
|
|
110
|
+
object(), "app1", "QA", fields, project_id="org1",
|
|
111
|
+
section_id="sec1", **kw)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TestCreatePreflight:
|
|
115
|
+
def test_bad_default_never_creates(self, monkeypatch):
|
|
116
|
+
rec = _Recorder(monkeypatch)
|
|
117
|
+
with pytest.raises(ValueError, match="defaultValue"):
|
|
118
|
+
_create(_with_default({"source": "static", "value": "x"}))
|
|
119
|
+
assert rec.calls == []
|
|
120
|
+
|
|
121
|
+
def test_any_shaping_error_surfaces_before_create(self, monkeypatch):
|
|
122
|
+
# A field-settings value the save path refuses (an object where
|
|
123
|
+
# text belongs) used to be found only after the create.
|
|
124
|
+
rec = _Recorder(monkeypatch)
|
|
125
|
+
bad = [GOOD, {"type": "Text", "name": "x",
|
|
126
|
+
"advancedSetting": {"sorttype": {"a": 1}}}]
|
|
127
|
+
with pytest.raises(ValueError):
|
|
128
|
+
_create(bad)
|
|
129
|
+
assert rec.calls == []
|
|
130
|
+
|
|
131
|
+
def test_good_spec_creates_and_saves(self, monkeypatch):
|
|
132
|
+
rec = _Recorder(monkeypatch)
|
|
133
|
+
out = _create([GOOD, {"type": "Text", "name": "备注", "defaultValue":
|
|
134
|
+
[{"source": "static", "value": "D"}]}])
|
|
135
|
+
assert out["worksheetId"] == "ws_new"
|
|
136
|
+
assert rec.kinds() == ["create", "update"]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# ── create: a failure after the worksheet exists ─────────────────────────
|
|
140
|
+
class TestCreateRollback:
|
|
141
|
+
def test_rollback_knows_the_group_when_none_was_given(self, monkeypatch):
|
|
142
|
+
rec = _Recorder(monkeypatch, save_raises=APIError("x"))
|
|
143
|
+
monkeypatch.setattr(ws_mod, "_first_app_section_id",
|
|
144
|
+
lambda session, app_id: "sec-first")
|
|
145
|
+
seen = {}
|
|
146
|
+
orig = app_mod.purge_app_item
|
|
147
|
+
|
|
148
|
+
def spy(session, **kw):
|
|
149
|
+
seen.update(kw)
|
|
150
|
+
return orig(session, **kw)
|
|
151
|
+
|
|
152
|
+
monkeypatch.setattr(app_mod, "purge_app_item", spy)
|
|
153
|
+
with pytest.raises(ws_mod.WorksheetCreateError):
|
|
154
|
+
ws_mod.create_worksheet_with_fields(
|
|
155
|
+
object(), "app1", "QA", [GOOD], project_id="org1")
|
|
156
|
+
assert seen["section_id"] == "sec-first"
|
|
157
|
+
assert rec.calls[0][3] == "sec-first"
|
|
158
|
+
|
|
159
|
+
def test_save_error_rolls_back_and_keeps_original_reason(
|
|
160
|
+
self, monkeypatch):
|
|
161
|
+
rec = _Recorder(monkeypatch, save_raises=APIError("字段保存失败"))
|
|
162
|
+
with pytest.raises(ws_mod.WorksheetCreateError) as exc:
|
|
163
|
+
_create([GOOD])
|
|
164
|
+
assert rec.kinds() == ["create", "update", "purge"]
|
|
165
|
+
assert rec.calls[2][1:] == ("ws_new", 0, "app1")
|
|
166
|
+
msg = str(exc.value)
|
|
167
|
+
assert "字段保存失败" in msg
|
|
168
|
+
assert "undone" in msg
|
|
169
|
+
assert exc.value.details == {"worksheetId": "ws_new",
|
|
170
|
+
"rolledBack": True}
|
|
171
|
+
|
|
172
|
+
def test_rejected_save_code_rolls_back(self, monkeypatch):
|
|
173
|
+
rec = _Recorder(monkeypatch, save={"code": 16, "msg": "别名重复"})
|
|
174
|
+
with pytest.raises(ws_mod.WorksheetCreateError, match="别名重复"):
|
|
175
|
+
_create([GOOD])
|
|
176
|
+
assert "purge" in rec.kinds()
|
|
177
|
+
|
|
178
|
+
def test_failed_rollback_names_the_worksheet(self, monkeypatch):
|
|
179
|
+
rec = _Recorder(monkeypatch, save_raises=RuntimeError("boom"),
|
|
180
|
+
purge_raises=APIError("no"))
|
|
181
|
+
with pytest.raises(ws_mod.WorksheetCreateError) as exc:
|
|
182
|
+
_create([GOOD])
|
|
183
|
+
msg = str(exc.value)
|
|
184
|
+
assert "boom" in msg and "ws_new" in msg
|
|
185
|
+
assert "update-fields" in msg
|
|
186
|
+
assert exc.value.details == {"worksheetId": "ws_new",
|
|
187
|
+
"rolledBack": False}
|
|
188
|
+
assert rec.kinds() == ["create", "update", "purge"]
|
|
189
|
+
|
|
190
|
+
def test_rollback_is_logged(self, monkeypatch):
|
|
191
|
+
import logging
|
|
192
|
+
seen = []
|
|
193
|
+
|
|
194
|
+
class Grab(logging.Handler):
|
|
195
|
+
def emit(self, record):
|
|
196
|
+
seen.append(record)
|
|
197
|
+
|
|
198
|
+
log = logging.getLogger("hap_cli.worksheet")
|
|
199
|
+
handler = Grab(level=logging.WARNING)
|
|
200
|
+
old_level = log.level
|
|
201
|
+
log.addHandler(handler)
|
|
202
|
+
log.setLevel(logging.WARNING)
|
|
203
|
+
try:
|
|
204
|
+
_Recorder(monkeypatch, save_raises=APIError("x"))
|
|
205
|
+
with pytest.raises(ws_mod.WorksheetCreateError):
|
|
206
|
+
_create([GOOD])
|
|
207
|
+
finally:
|
|
208
|
+
log.removeHandler(handler)
|
|
209
|
+
log.setLevel(old_level)
|
|
210
|
+
assert any("ws_new" in r.getMessage() for r in seen
|
|
211
|
+
if r.levelno == logging.WARNING)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def test_cli_json_error_carries_worksheet_id(tmp_config, monkeypatch):
|
|
215
|
+
from hap_cli._entry import cli
|
|
216
|
+
sess = Session(server_url="https://t.com", auth_token="tok")
|
|
217
|
+
sess.default_project_id = "proj1"
|
|
218
|
+
sess.save()
|
|
219
|
+
|
|
220
|
+
def boom(*a, **kw):
|
|
221
|
+
raise ws_mod.WorksheetCreateError(
|
|
222
|
+
"x. Worksheet ws_new was created but its fields were not "
|
|
223
|
+
"written", worksheet_id="ws_new", rolled_back=False)
|
|
224
|
+
|
|
225
|
+
monkeypatch.setattr(ws_mod, "create_worksheet_with_fields", boom)
|
|
226
|
+
res = CliRunner().invoke(cli, ["--json", "worksheet", "create", "app1",
|
|
227
|
+
"QA", "--fields", json.dumps([GOOD])])
|
|
228
|
+
assert res.exit_code == 1
|
|
229
|
+
payload = json.loads(res.output)
|
|
230
|
+
assert payload["worksheetId"] == "ws_new"
|
|
231
|
+
assert payload["rolledBack"] is False
|
|
@@ -216,6 +216,7 @@ hap_cli/tests/test_config_cmd.py
|
|
|
216
216
|
hap_cli/tests/test_content_language.py
|
|
217
217
|
hap_cli/tests/test_control_lookup_guard.py
|
|
218
218
|
hap_cli/tests/test_core.py
|
|
219
|
+
hap_cli/tests/test_create_worksheet_partial.py
|
|
219
220
|
hap_cli/tests/test_destructive_confirm_cli.py
|
|
220
221
|
hap_cli/tests/test_dsl_existing_node_refs.py
|
|
221
222
|
hap_cli/tests/test_dsl_multi_record_guard.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|