hap-cli 0.8.16__tar.gz → 0.8.18__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.
Files changed (255) hide show
  1. {hap_cli-0.8.16 → hap_cli-0.8.18}/PKG-INFO +1 -1
  2. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/__init__.py +1 -1
  3. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +55 -0
  4. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/record.py +48 -3
  5. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/workflow_node_dsl.py +14 -7
  6. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/worksheet_templates.py +55 -0
  7. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_core.py +6 -3
  8. hap_cli-0.8.18/hap_cli/tests/test_select_rejects_relation_value.py +178 -0
  9. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_workflow_v3_semantic_cli.py +1 -1
  10. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli.egg-info/PKG-INFO +1 -1
  11. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli.egg-info/SOURCES.txt +1 -0
  12. {hap_cli-0.8.16 → hap_cli-0.8.18}/MANIFEST.in +0 -0
  13. {hap_cli-0.8.16 → hap_cli-0.8.18}/README.md +0 -0
  14. {hap_cli-0.8.16 → hap_cli-0.8.18}/app_live_tests/__init__.py +0 -0
  15. {hap_cli-0.8.16 → hap_cli-0.8.18}/app_live_tests/__main__.py +0 -0
  16. {hap_cli-0.8.16 → hap_cli-0.8.18}/app_live_tests/config.py +0 -0
  17. {hap_cli-0.8.16 → hap_cli-0.8.18}/app_live_tests/harness.py +0 -0
  18. {hap_cli-0.8.16 → hap_cli-0.8.18}/app_live_tests/smoke.py +0 -0
  19. {hap_cli-0.8.16 → hap_cli-0.8.18}/app_live_tests/state.py +0 -0
  20. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/README.md +0 -0
  21. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/README_CN.md +0 -0
  22. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/__main__.py +0 -0
  23. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/__init__.py +0 -0
  24. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/app_cmd.py +0 -0
  25. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/app_creator_cmd.py +0 -0
  26. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/app_editor_cmd.py +0 -0
  27. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/auth_cmd.py +0 -0
  28. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/calendar_cmd.py +0 -0
  29. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/chart_cmd.py +0 -0
  30. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/chat_cmd.py +0 -0
  31. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/chatbot_cmd.py +0 -0
  32. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/config_cmd.py +0 -0
  33. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/contact_cmd.py +0 -0
  34. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/department_cmd.py +0 -0
  35. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/group_cmd.py +0 -0
  36. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/icon_cmd.py +0 -0
  37. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/instance_cmd.py +0 -0
  38. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/node_cmd.py +0 -0
  39. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/optionset_cmd.py +0 -0
  40. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/page_cmd.py +0 -0
  41. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/post_cmd.py +0 -0
  42. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/record_cmd.py +0 -0
  43. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/region_cmd.py +0 -0
  44. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/role_cmd.py +0 -0
  45. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/update_cmd.py +0 -0
  46. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/upload_cmd.py +0 -0
  47. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/v3_registry.py +0 -0
  48. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/workflow_cmd.py +0 -0
  49. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/commands/worksheet_cmd.py +0 -0
  50. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/context.py +0 -0
  51. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/__init__.py +0 -0
  52. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/action_spec_adapter.py +0 -0
  53. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app.py +0 -0
  54. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/__init__.py +0 -0
  55. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
  56. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
  57. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/charts.py +0 -0
  58. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/cleanup.py +0 -0
  59. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/compiler.py +0 -0
  60. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/config.py +0 -0
  61. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/design/design.schema.json +0 -0
  62. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/errors.py +0 -0
  63. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
  64. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/executor.py +0 -0
  65. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/fields.py +0 -0
  66. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/hap.py +0 -0
  67. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/merge.py +0 -0
  68. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/recording/__init__.py +0 -0
  69. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/recording/console.py +0 -0
  70. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
  71. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/recording/mirror.py +0 -0
  72. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/recording/report.py +0 -0
  73. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/schema.py +0 -0
  74. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
  75. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/seed/__init__.py +0 -0
  76. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/seed/cli.py +0 -0
  77. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/seed/executor.py +0 -0
  78. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
  79. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
  80. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
  81. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
  82. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/seed/template.py +0 -0
  83. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/selftest.py +0 -0
  84. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/smoke.py +0 -0
  85. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/steps.py +0 -0
  86. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/store.py +0 -0
  87. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/validate.py +0 -0
  88. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
  89. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/auth.py +0 -0
  90. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/calendar_mod.py +0 -0
  91. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/chart.py +0 -0
  92. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/chat.py +0 -0
  93. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/chatbot.py +0 -0
  94. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/cli_access.py +0 -0
  95. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/contact.py +0 -0
  96. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/control_type_codes.py +0 -0
  97. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/department.py +0 -0
  98. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/editor/__init__.py +0 -0
  99. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
  100. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
  101. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/editor/apply.py +0 -0
  102. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/editor/componentlower.py +0 -0
  103. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/editor/editspec.py +0 -0
  104. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/editor/errors.py +0 -0
  105. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/editor/fieldlower.py +0 -0
  106. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/editor/jsonschema_mini.py +0 -0
  107. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/editor/models.py +0 -0
  108. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/editor/ops.py +0 -0
  109. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/editor/planner.py +0 -0
  110. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/editor/reader.py +0 -0
  111. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/field_normalizer.py +0 -0
  112. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/field_spec_adapter.py +0 -0
  113. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/filter_translator.py +0 -0
  114. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/flow_node.py +0 -0
  115. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/global_meta.py +0 -0
  116. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/group.py +0 -0
  117. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/icon_index.py +0 -0
  118. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/instance.py +0 -0
  119. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/logger.py +0 -0
  120. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/optionset.py +0 -0
  121. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/page.py +0 -0
  122. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/post.py +0 -0
  123. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/response_crypto.py +0 -0
  124. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/role.py +0 -0
  125. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/role_perm_builder.py +0 -0
  126. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/session.py +0 -0
  127. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/token_crypto.py +0 -0
  128. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/update.py +0 -0
  129. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/upload.py +0 -0
  130. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/v3/__init__.py +0 -0
  131. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/v3/dispatcher.py +0 -0
  132. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/v3/render.py +0 -0
  133. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/v3/schema.py +0 -0
  134. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/view_spec_adapter.py +0 -0
  135. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/workflow.py +0 -0
  136. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/core/worksheet.py +0 -0
  137. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/hap_cli.py +0 -0
  138. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/i18n.py +0 -0
  139. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/locale/__init__.py +0 -0
  140. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/locale/messages.json +0 -0
  141. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/locale/messages.schema.json +0 -0
  142. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/__init__.py +0 -0
  143. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/conftest.py +0 -0
  144. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_app_creator.py +0 -0
  145. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_app_editor.py +0 -0
  146. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_app_export_usage.py +0 -0
  147. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_auth_prerelease.py +0 -0
  148. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_bug_036430.py +0 -0
  149. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_bug_036750.py +0 -0
  150. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_chart.py +0 -0
  151. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_cli_access.py +0 -0
  152. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_config_cmd.py +0 -0
  153. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
  154. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_full_e2e.py +0 -0
  155. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_global_meta.py +0 -0
  156. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_i18n.py +0 -0
  157. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_instance_history_cli.py +0 -0
  158. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_integration.py +0 -0
  159. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_integration_approval.py +0 -0
  160. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_integration_approval_actions.py +0 -0
  161. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_integration_calendar.py +0 -0
  162. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_integration_destructive.py +0 -0
  163. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_integration_misc.py +0 -0
  164. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_integration_post.py +0 -0
  165. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_integration_social.py +0 -0
  166. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_integration_v3.py +0 -0
  167. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_integration_workflow.py +0 -0
  168. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
  169. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_multi_profile.py +0 -0
  170. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
  171. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_org_id_cli.py +0 -0
  172. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_org_id_docs.py +0 -0
  173. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_parameter_conventions.py +0 -0
  174. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
  175. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
  176. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_record_field_alias.py +0 -0
  177. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
  178. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_semantic_types_registry.py +0 -0
  179. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_update.py +0 -0
  180. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
  181. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_v3_dispatcher.py +0 -0
  182. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
  183. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_v3_session.py +0 -0
  184. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
  185. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_workflow_list_cli.py +0 -0
  186. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
  187. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
  188. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/utils/__init__.py +0 -0
  189. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/utils/formatting.py +0 -0
  190. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/utils/options.py +0 -0
  191. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli/utils/parameter_mapping.py +0 -0
  192. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli.egg-info/dependency_links.txt +0 -0
  193. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli.egg-info/entry_points.txt +0 -0
  194. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli.egg-info/requires.txt +0 -0
  195. {hap_cli-0.8.16 → hap_cli-0.8.18}/hap_cli.egg-info/top_level.txt +0 -0
  196. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/__init__.py +0 -0
  197. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/__main__.py +0 -0
  198. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/_wftest.py +0 -0
  199. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/charts.py +0 -0
  200. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/cleanup.py +0 -0
  201. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/compiler.py +0 -0
  202. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/config.py +0 -0
  203. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/errors.py +0 -0
  204. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/executor.py +0 -0
  205. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/fields.py +0 -0
  206. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/hap.py +0 -0
  207. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/recording/__init__.py +0 -0
  208. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/recording/console.py +0 -0
  209. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/recording/jsonl.py +0 -0
  210. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/recording/mirror.py +0 -0
  211. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/recording/report.py +0 -0
  212. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/schema.py +0 -0
  213. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/seed/__init__.py +0 -0
  214. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/seed/cli.py +0 -0
  215. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/seed/executor.py +0 -0
  216. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/seed/template.py +0 -0
  217. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/selftest.py +0 -0
  218. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/smoke.py +0 -0
  219. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/step.py +0 -0
  220. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/steps.py +0 -0
  221. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/store.py +0 -0
  222. {hap_cli-0.8.16 → hap_cli-0.8.18}/live_tests/workflow_dsl.py +0 -0
  223. {hap_cli-0.8.16 → hap_cli-0.8.18}/setup.cfg +0 -0
  224. {hap_cli-0.8.16 → hap_cli-0.8.18}/setup.py +0 -0
  225. {hap_cli-0.8.16 → hap_cli-0.8.18}/skills/cli/hap-cli-app-editor/scripts/editspec/component.schema.json +0 -0
  226. {hap_cli-0.8.16 → hap_cli-0.8.18}/skills/cli/hap-cli-app-editor/scripts/editspec/custom-action.schema.json +0 -0
  227. {hap_cli-0.8.16 → hap_cli-0.8.18}/skills/cli/hap-cli-app-editor/scripts/editspec/envelope.schema.json +0 -0
  228. {hap_cli-0.8.16 → hap_cli-0.8.18}/skills/cli/hap-cli-app-editor/scripts/editspec/field.schema.json +0 -0
  229. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/INDEX.json +0 -0
  230. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
  231. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
  232. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/create_optionset.yaml +0 -0
  233. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/create_role.yaml +0 -0
  234. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/delete_optionset.yaml +0 -0
  235. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/delete_record.yaml +0 -0
  236. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/delete_role.yaml +0 -0
  237. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
  238. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/get_app_info.yaml +0 -0
  239. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
  240. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
  241. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
  242. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/get_record_details.yaml +0 -0
  243. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
  244. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/get_record_list.yaml +0 -0
  245. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/get_record_logs.yaml +0 -0
  246. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
  247. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/get_record_relations.yaml +0 -0
  248. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
  249. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/get_regions.yaml +0 -0
  250. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/get_role_details.yaml +0 -0
  251. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/get_role_list.yaml +0 -0
  252. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/knowledge_search.yaml +0 -0
  253. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
  254. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
  255. {hap_cli-0.8.16 → hap_cli-0.8.18}/sources/v3-api-schema/update_optionset.yaml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hap-cli
3
- Version: 0.8.16
3
+ Version: 0.8.18
4
4
  Summary: CLI harness for Mingdao HAP - Enterprise no-code platform
5
5
  Author: hap-cli
6
6
  License: Apache-2.0
@@ -1,3 +1,3 @@
1
1
  """CLI harness for Mingdao HAP (hap-cli)."""
2
2
 
3
- __version__ = "0.8.16"
3
+ __version__ = "0.8.18"
@@ -486,6 +486,34 @@ def build_control(
486
486
  return ctrl
487
487
 
488
488
 
489
+ # The fields a HAP select option may legitimately carry. The base shape
490
+ # is {key, value, isDeleted, index, checked, color} (pd-openweb
491
+ # src/pages/widgetConfig/util/setting.js getDefaultOptions); ``score`` is
492
+ # the optional NPS/scoring weight (OptionList/AssignValue.jsx). There is
493
+ # no ``type`` field — an option carrying one is malformed input, not a HAP
494
+ # shape, so it is rejected rather than silently dropped.
495
+ _ALLOWED_OPTION_FIELDS = frozenset(
496
+ {"key", "value", "isDeleted", "index", "checked", "color", "score"}
497
+ )
498
+
499
+
500
+ def _option_value_is_object_shaped(v: Any) -> bool:
501
+ """True when an option key/value is a relation/member/object shape
502
+ (``[{"sid": …}]`` / ``{...}``) instead of plain text — which would
503
+ only surface server-side as a ``ControlOptions`` deserialization
504
+ failure on the batch save."""
505
+ if isinstance(v, (list, tuple, dict)):
506
+ return True
507
+ if isinstance(v, str):
508
+ s = v.strip()
509
+ if s[:1] in ("[", "{"):
510
+ try:
511
+ return isinstance(json.loads(s), (list, dict))
512
+ except (ValueError, TypeError):
513
+ return False
514
+ return False
515
+
516
+
489
517
  def _normalize_options(
490
518
  opts: list[str] | list[dict[str, Any]],
491
519
  ) -> list[dict[str, Any]]:
@@ -493,6 +521,10 @@ def _normalize_options(
493
521
 
494
522
  First item is marked as the default selection (checked=True) unless
495
523
  any caller-supplied dict already sets it.
524
+
525
+ Option ``value``/``key`` must be plain text; a relation/object shape
526
+ or an unsupported field (e.g. ``type``) is rejected up front rather
527
+ than silently mangled or dropped.
496
528
  """
497
529
  colors = [
498
530
  "#C9E6FC", "#C3F2F2", "#C2F1D2", "#FFE7B1", "#FBD2BF",
@@ -502,6 +534,27 @@ def _normalize_options(
502
534
  any_default = any(isinstance(o, dict) and o.get("checked") for o in opts)
503
535
  for i, o in enumerate(opts):
504
536
  if isinstance(o, dict):
537
+ unknown = set(o) - _ALLOWED_OPTION_FIELDS
538
+ if unknown:
539
+ raise ValueError(
540
+ f"Select option {o!r} has unsupported field(s) "
541
+ f"{sorted(unknown)}; an option only accepts "
542
+ f"{sorted(_ALLOWED_OPTION_FIELDS)}."
543
+ )
544
+ if o.get("value") in (None, ""):
545
+ raise ValueError(
546
+ f"Select option {o!r} is missing a non-empty 'value'."
547
+ )
548
+ if _option_value_is_object_shaped(o.get("value")):
549
+ raise ValueError(
550
+ f"Select option 'value' must be plain text, got a "
551
+ f"relation/object-shaped value: {o.get('value')!r}."
552
+ )
553
+ if o.get("key") is not None and _option_value_is_object_shaped(o.get("key")):
554
+ raise ValueError(
555
+ f"Select option 'key' must be a string, got a "
556
+ f"relation/object-shaped value: {o.get('key')!r}."
557
+ )
505
558
  entry = {
506
559
  "key": o.get("key") or str(uuid.uuid4()),
507
560
  "value": o["value"],
@@ -510,6 +563,8 @@ def _normalize_options(
510
563
  "checked": o.get("checked", False),
511
564
  "color": o.get("color", colors[i % len(colors)]),
512
565
  }
566
+ if "score" in o:
567
+ entry["score"] = o["score"]
513
568
  else:
514
569
  entry = {
515
570
  "key": str(uuid.uuid4()),
@@ -22,13 +22,56 @@ DISCUSSION_SOURCE_WORKSHEET_ROW = 8
22
22
  _LEGACY_SKIP_TYPES = {30, 31, 32, 33, 37, 38, 53}
23
23
 
24
24
 
25
+ def _option_field_label(ctrl: dict[str, Any]) -> str:
26
+ return str(ctrl.get("controlName") or ctrl.get("controlId") or "option field")
27
+
28
+
29
+ def _reject_relation_shaped_option(ctrl: dict[str, Any], item: Any) -> None:
30
+ """Fail loudly when a select cell is handed a relation/member/sub-table
31
+ *object* shape instead of an option value.
32
+
33
+ A single/multi-select cell legitimately carries only an option key, a
34
+ known label, or an ``add_<text>`` new-option directive — never a
35
+ ``[{"sid": rowId}]`` / ``{...}`` structure. When such a value slips
36
+ through (e.g. row data maps a relation value to the wrong child field),
37
+ the old code passed it straight to the server, which then failed
38
+ deserializing ``ControlOptions`` on the batch save. Catching it here
39
+ points the blame at the offending field instead of a generic 500.
40
+ """
41
+ looks_object = isinstance(item, (list, tuple, dict))
42
+ if not looks_object and isinstance(item, str):
43
+ stripped = item.strip()
44
+ if stripped[:1] in ("[", "{"):
45
+ try:
46
+ parsed = json.loads(stripped)
47
+ except (ValueError, TypeError):
48
+ parsed = None
49
+ looks_object = isinstance(parsed, (list, dict))
50
+ if looks_object:
51
+ raise ValueError(
52
+ f"Field '{_option_field_label(ctrl)}' is a select field and "
53
+ f"expects an option key, a label, or an 'add_<text>' new-option "
54
+ f"value, but got a relation/object-shaped value: {item!r}. "
55
+ f"This value belongs to a relation or member field, not this "
56
+ f"select field — check the field mapping."
57
+ )
58
+
59
+
25
60
  def _resolve_option_keys(ctrl: dict[str, Any], raw: Any) -> list[str]:
26
61
  """Map option labels (or already-keys) to the option `key` values
27
- the legacy AddWorksheetRow expects (it takes keys, not labels)."""
62
+ the legacy AddWorksheetRow expects (it takes keys, not labels).
63
+
64
+ Relation/member/sub-table *object* shapes (``[{"sid": …}]`` etc.) are
65
+ rejected up front — they are never valid option values and would only
66
+ surface server-side as a ``ControlOptions`` deserialization failure on
67
+ the batch save.
68
+ """
28
69
  if raw is None or raw == "":
29
70
  return []
30
71
  if isinstance(raw, (list, tuple)):
31
- items = [str(v) for v in raw]
72
+ items: list[Any] = list(raw)
73
+ elif isinstance(raw, dict):
74
+ items = [raw]
32
75
  else:
33
76
  items = [s for s in str(raw).split(",") if s != ""]
34
77
  by_label: dict[str, str] = {}
@@ -40,7 +83,9 @@ def _resolve_option_keys(ctrl: dict[str, Any], raw: Any) -> list[str]:
40
83
  by_label[str(opt.get("value", ""))] = key
41
84
  out: list[str] = []
42
85
  for it in items:
43
- out.append(it if it in valid_keys else by_label.get(it, it))
86
+ _reject_relation_shaped_option(ctrl, it)
87
+ s = str(it)
88
+ out.append(s if s in valid_keys else by_label.get(s, s))
44
89
  return out
45
90
 
46
91
 
@@ -684,17 +684,24 @@ def get_trigger_ref(session, process_id: str) -> dict[str, str]:
684
684
 
685
685
 
686
686
  # Worksheet-event trigger event codes (triggerId on the flowNodeType-0
687
- # node). Evidence: workflow-nodes-all-types 新增记录触发 capture (triggerId
688
- # "1") + pd-openweb trigger config.
687
+ # node). Authoritative values from pd-openweb TRIGGER_ID enum
688
+ # (src/pages/workflow/WorkflowSettings/enum.js) and the trigger radio
689
+ # options in Detail/Start/WorksheetContent.jsx:
690
+ # ADD='1' (仅新增记录), EDIT='2' (当新增或更新记录), DELETE='3' (删除记录),
691
+ # ONLY_EDIT='4' (仅更新记录).
692
+ # Note the non-sequential mapping: create_or_update is '2' and delete is
693
+ # '3' — do NOT "tidy" these into 1/2/3/4 order. (Earlier code had
694
+ # create_or_update='3', which the backend interprets as 删除记录 — that was
695
+ # the cause of bug: "创建或更新" workflows showing up as 记录删除时触发.)
689
696
  WORKSHEET_TRIGGER_EVENTS = {
690
- "create": "1", # 新增记录
691
- "update": "2", # 修改记录
692
- "create_or_update": "3", # 新增或修改
693
- "delete": "4", # 删除记录
697
+ "create": "1", # 仅新增记录 (TRIGGER_ID.ADD)
698
+ "update": "4", # 仅更新记录 (TRIGGER_ID.ONLY_EDIT)
699
+ "create_or_update": "2", # 当新增或更新记录 (TRIGGER_ID.EDIT)
700
+ "delete": "3", # 删除记录 (TRIGGER_ID.DELETE)
694
701
  # Design-vocabulary aliases (mcp_workflow_build_tool.json#create_process
695
702
  # trigger.config.event uses the backend database values).
696
703
  "add": "1",
697
- "add_or_update": "3",
704
+ "add_or_update": "2",
698
705
  }
699
706
 
700
707
 
@@ -471,6 +471,34 @@ def build_control(
471
471
  return ctrl
472
472
 
473
473
 
474
+ # The fields a HAP select option may legitimately carry. The base shape
475
+ # is {key, value, isDeleted, index, checked, color} (pd-openweb
476
+ # src/pages/widgetConfig/util/setting.js getDefaultOptions); ``score`` is
477
+ # the optional NPS/scoring weight (OptionList/AssignValue.jsx). There is
478
+ # no ``type`` field — an option carrying one is malformed input, not a HAP
479
+ # shape, so it is rejected rather than silently dropped.
480
+ _ALLOWED_OPTION_FIELDS = frozenset(
481
+ {"key", "value", "isDeleted", "index", "checked", "color", "score"}
482
+ )
483
+
484
+
485
+ def _option_value_is_object_shaped(v: Any) -> bool:
486
+ """True when an option key/value is a relation/member/object shape
487
+ (``[{"sid": …}]`` / ``{...}``) instead of plain text — which would
488
+ only surface server-side as a ``ControlOptions`` deserialization
489
+ failure on the batch save."""
490
+ if isinstance(v, (list, tuple, dict)):
491
+ return True
492
+ if isinstance(v, str):
493
+ s = v.strip()
494
+ if s[:1] in ("[", "{"):
495
+ try:
496
+ return isinstance(json.loads(s), (list, dict))
497
+ except (ValueError, TypeError):
498
+ return False
499
+ return False
500
+
501
+
474
502
  def _normalize_options(
475
503
  opts: list[str] | list[dict[str, Any]],
476
504
  ) -> list[dict[str, Any]]:
@@ -478,6 +506,10 @@ def _normalize_options(
478
506
 
479
507
  First item is marked as the default selection (checked=True) unless
480
508
  any caller-supplied dict already sets it.
509
+
510
+ Option ``value``/``key`` must be plain text; a relation/object shape
511
+ or an unsupported field (e.g. ``type``) is rejected up front rather
512
+ than silently mangled or dropped.
481
513
  """
482
514
  colors = [
483
515
  "#C9E6FC", "#C3F2F2", "#C2F1D2", "#FFE7B1", "#FBD2BF",
@@ -487,6 +519,27 @@ def _normalize_options(
487
519
  any_default = any(isinstance(o, dict) and o.get("checked") for o in opts)
488
520
  for i, o in enumerate(opts):
489
521
  if isinstance(o, dict):
522
+ unknown = set(o) - _ALLOWED_OPTION_FIELDS
523
+ if unknown:
524
+ raise ValueError(
525
+ f"Select option {o!r} has unsupported field(s) "
526
+ f"{sorted(unknown)}; an option only accepts "
527
+ f"{sorted(_ALLOWED_OPTION_FIELDS)}."
528
+ )
529
+ if o.get("value") in (None, ""):
530
+ raise ValueError(
531
+ f"Select option {o!r} is missing a non-empty 'value'."
532
+ )
533
+ if _option_value_is_object_shaped(o.get("value")):
534
+ raise ValueError(
535
+ f"Select option 'value' must be plain text, got a "
536
+ f"relation/object-shaped value: {o.get('value')!r}."
537
+ )
538
+ if o.get("key") is not None and _option_value_is_object_shaped(o.get("key")):
539
+ raise ValueError(
540
+ f"Select option 'key' must be a string, got a "
541
+ f"relation/object-shaped value: {o.get('key')!r}."
542
+ )
490
543
  entry = {
491
544
  "key": o.get("key") or str(uuid.uuid4()),
492
545
  "value": o["value"],
@@ -495,6 +548,8 @@ def _normalize_options(
495
548
  "checked": o.get("checked", False),
496
549
  "color": o.get("color", colors[i % len(colors)]),
497
550
  }
551
+ if "score" in o:
552
+ entry["score"] = o["score"]
498
553
  else:
499
554
  entry = {
500
555
  "key": str(uuid.uuid4()),
@@ -8820,10 +8820,13 @@ class TestLiveWireFixes:
8820
8820
 
8821
8821
  def test_worksheet_trigger_event_codes(self):
8822
8822
  from hap_cli.core import workflow_node_dsl as dsl
8823
+ # Authoritative TRIGGER_ID enum: ADD=1, EDIT(create_or_update)=2,
8824
+ # DELETE=3, ONLY_EDIT(update)=4. The mapping is intentionally
8825
+ # non-sequential — see WORKSHEET_TRIGGER_EVENTS comment.
8823
8826
  assert dsl.WORKSHEET_TRIGGER_EVENTS["create"] == "1"
8824
- assert dsl.WORKSHEET_TRIGGER_EVENTS["update"] == "2"
8825
- assert dsl.WORKSHEET_TRIGGER_EVENTS["create_or_update"] == "3"
8826
- assert dsl.WORKSHEET_TRIGGER_EVENTS["delete"] == "4"
8827
+ assert dsl.WORKSHEET_TRIGGER_EVENTS["update"] == "4"
8828
+ assert dsl.WORKSHEET_TRIGGER_EVENTS["create_or_update"] == "2"
8829
+ assert dsl.WORKSHEET_TRIGGER_EVENTS["delete"] == "3"
8827
8830
 
8828
8831
 
8829
8832
  class TestRolePermBuilder:
@@ -0,0 +1,178 @@
1
+ """A single/multi-select cell must reject relation/object-shaped values.
2
+
3
+ Traced from a live backend error on ``WorksheetService/AddWSRowsBatch``
4
+ (the batch service the main-site ``Worksheet/AddWorksheetRow`` dispatches
5
+ sub-table child rows to). A single-select child field (Type 11) had been
6
+ handed a RELATE_SHEET-shaped value ``[{"sid": "<rowId>"}]`` instead of an
7
+ option key, and the server failed deserialization:
8
+
9
+ Cannot deserialize the current JSON array (e.g. [1,2,3]) into type
10
+ 'MD.Worksheet.Core.Entities.ControlOptions' ...
11
+
12
+ Before this fix, ``_resolve_option_keys`` passed any value it could not
13
+ map to a known key/label straight through to the server (the same soft
14
+ spot as bug #036430). A select cell legitimately carries only an option
15
+ key, a known label, or an ``add_<text>`` new-option directive (evidence:
16
+ pd-openweb src/components/Form/core/utils.js, formatControlToServer
17
+ case 10/11). An object / array-of-objects value is structurally a
18
+ relation/member/sub-table shape and is always wrong here, so it is now
19
+ rejected loudly at the CLI boundary instead of poisoning the server.
20
+ """
21
+
22
+ import json
23
+
24
+ import pytest
25
+
26
+ from hap_cli.core import record as rec_mod
27
+ from hap_cli.core import worksheet_templates as wt
28
+
29
+
30
+ _SELECT_CTRL = {
31
+ "controlId": "c_status",
32
+ "controlName": "状态",
33
+ "type": 11,
34
+ "options": [
35
+ {"key": "k_pass", "value": "已通过"},
36
+ {"key": "k_reject", "value": "已驳回"},
37
+ ],
38
+ }
39
+
40
+ # A sub-table child whose option metadata is NOT carried on the child
41
+ # control (relationControls are often slim) — the structural rejection
42
+ # must still fire without an option set to validate against.
43
+ _SELECT_CTRL_NO_OPTS = {
44
+ "controlId": "cc_status",
45
+ "controlName": "子状态",
46
+ "type": 11,
47
+ }
48
+
49
+ _RELATION_VALUE = [{"sid": "3587620f-f900-4f9c-9803-43827e69ff21"}]
50
+ _RELATION_JSON = json.dumps(_RELATION_VALUE) # '[{"sid": "..."}]'
51
+
52
+
53
+ class TestResolveOptionKeysRejectsRelationShape:
54
+ def test_rejects_list_of_dicts(self):
55
+ with pytest.raises(ValueError, match="状态"):
56
+ rec_mod._resolve_option_keys(_SELECT_CTRL, _RELATION_VALUE)
57
+
58
+ def test_rejects_relation_json_string(self):
59
+ with pytest.raises(ValueError):
60
+ rec_mod._resolve_option_keys(_SELECT_CTRL, _RELATION_JSON)
61
+
62
+ def test_rejects_single_dict(self):
63
+ with pytest.raises(ValueError):
64
+ rec_mod._resolve_option_keys(
65
+ _SELECT_CTRL, {"sid": "3587620f-f900-4f9c-9803-43827e69ff21"})
66
+
67
+ def test_rejects_even_without_option_metadata(self):
68
+ # No options to validate against, but the shape is still wrong.
69
+ with pytest.raises(ValueError):
70
+ rec_mod._resolve_option_keys(_SELECT_CTRL_NO_OPTS, _RELATION_VALUE)
71
+
72
+ def test_rejects_json_object_string(self):
73
+ with pytest.raises(ValueError):
74
+ rec_mod._resolve_option_keys(_SELECT_CTRL, '{"sid": "abc"}')
75
+
76
+
77
+ class TestResolveOptionKeysStillAcceptsLegitValues:
78
+ def test_valid_key(self):
79
+ assert rec_mod._resolve_option_keys(_SELECT_CTRL, "k_pass") == ["k_pass"]
80
+
81
+ def test_label_maps_to_key(self):
82
+ assert rec_mod._resolve_option_keys(_SELECT_CTRL, "已通过") == ["k_pass"]
83
+
84
+ def test_add_new_option_directive_passes_through(self):
85
+ # `add_<text>` is how the form encodes "create this option on save".
86
+ assert rec_mod._resolve_option_keys(
87
+ _SELECT_CTRL, "add_新选项") == ["add_新选项"]
88
+
89
+ def test_unknown_plain_text_passes_through(self):
90
+ # Plain scalar text is not an object shape — left to the server
91
+ # (may be a key we cannot see, or a new option). Not our bug.
92
+ assert rec_mod._resolve_option_keys(
93
+ _SELECT_CTRL_NO_OPTS, "甲") == ["甲"]
94
+
95
+ def test_comma_list_of_keys(self):
96
+ assert rec_mod._resolve_option_keys(
97
+ _SELECT_CTRL, "k_pass,k_reject") == ["k_pass", "k_reject"]
98
+
99
+ def test_empty_is_cleared(self):
100
+ assert rec_mod._resolve_option_keys(_SELECT_CTRL, "") == []
101
+ assert rec_mod._resolve_option_keys(_SELECT_CTRL, None) == []
102
+
103
+
104
+ class TestSubTableChildSelectRejectsRelationValue:
105
+ """The reported scenario: a sub-table (34) child single-select gets a
106
+ relation-shaped value while the parent record is created — the child
107
+ row is what the backend batch-saves."""
108
+
109
+ def test_sublist_child_select_relation_value_is_rejected(self):
110
+ sublist_ctrl = {
111
+ "controlId": "c_sub",
112
+ "controlName": "明细",
113
+ "type": 34,
114
+ "relationControls": [_SELECT_CTRL_NO_OPTS],
115
+ }
116
+ rows = [{"子状态": _RELATION_VALUE}]
117
+ with pytest.raises(ValueError):
118
+ rec_mod._legacy_cell_value(sublist_ctrl, rows)
119
+
120
+
121
+ class TestOptionDefinitionRejectsBadShapes:
122
+ """The other injection point: defining a select field's *options*.
123
+
124
+ A select option is only ``{key, value, isDeleted, index, checked,
125
+ color}`` (evidence: pd-openweb widgetConfig/util/setting.js). Option
126
+ ``value`` / ``key`` must be plain text — never a relation/object shape
127
+ — and an option carries no ``type`` field. Before this fix,
128
+ ``_normalize_options`` passed any value/key straight through and
129
+ silently dropped unknown keys like ``type``.
130
+ """
131
+
132
+ def test_option_value_object_shape_rejected(self):
133
+ with pytest.raises(ValueError):
134
+ wt.build_control("DROP_DOWN", "状态",
135
+ options=[{"value": [{"sid": "abc"}]}])
136
+
137
+ def test_option_value_relation_json_string_rejected(self):
138
+ with pytest.raises(ValueError):
139
+ wt.build_control("DROP_DOWN", "状态",
140
+ options=[{"value": _RELATION_JSON}])
141
+
142
+ def test_option_key_object_shape_rejected(self):
143
+ with pytest.raises(ValueError):
144
+ wt.build_control("DROP_DOWN", "状态",
145
+ options=[{"key": [{"sid": "abc"}], "value": "甲"}])
146
+
147
+ def test_option_unknown_type_field_rejected(self):
148
+ with pytest.raises(ValueError, match="type"):
149
+ wt.build_control("DROP_DOWN", "状态",
150
+ options=[{"value": "甲", "type": 0}])
151
+
152
+ def test_option_missing_value_rejected(self):
153
+ with pytest.raises(ValueError):
154
+ wt.build_control("DROP_DOWN", "状态", options=[{"key": "k1"}])
155
+
156
+ # --- legit option definitions still build ---
157
+
158
+ def test_string_options_ok(self):
159
+ out = wt.build_control("DROP_DOWN", "状态", options=["甲", "乙"])
160
+ assert [o["value"] for o in out["options"]] == ["甲", "乙"]
161
+
162
+ def test_dict_option_with_known_fields_ok(self):
163
+ out = wt.build_control(
164
+ "DROP_DOWN", "状态",
165
+ options=[{"value": "甲", "key": "k1", "checked": True}])
166
+ assert out["options"][0]["key"] == "k1"
167
+ assert out["options"][0]["value"] == "甲"
168
+ assert out["options"][0]["checked"] is True
169
+
170
+ def test_scored_option_is_allowed_and_preserved(self):
171
+ # `score` is a legitimate NPS/scoring field — it must not be
172
+ # rejected (it is not a relation shape) and must survive to the
173
+ # wire instead of being silently dropped.
174
+ out = wt.build_control(
175
+ "DROP_DOWN", "满意度",
176
+ options=[{"value": "满意", "score": 5}])
177
+ assert out["options"][0]["value"] == "满意"
178
+ assert out["options"][0]["score"] == 5
@@ -452,7 +452,7 @@ class TestTriggerSpecLowering:
452
452
  assert args["trigger_fields"] == ["f1"]
453
453
  assert args["trigger_filter"] == {"logic": "and", "items": []}
454
454
  assert dsl.WORKSHEET_TRIGGER_EVENTS["add"] == "1"
455
- assert dsl.WORKSHEET_TRIGGER_EVENTS["add_or_update"] == "3"
455
+ assert dsl.WORKSHEET_TRIGGER_EVENTS["add_or_update"] == "2"
456
456
 
457
457
  def test_schedule(self):
458
458
  args = dsl.v3_trigger_to_batch_args({
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hap-cli
3
- Version: 0.8.16
3
+ Version: 0.8.18
4
4
  Summary: CLI harness for Mingdao HAP - Enterprise no-code platform
5
5
  Author: hap-cli
6
6
  License: Apache-2.0
@@ -177,6 +177,7 @@ hap_cli/tests/test_parameter_mapping_registry.py
177
177
  hap_cli/tests/test_record_batch_v3_semantic_cli.py
178
178
  hap_cli/tests/test_record_field_alias.py
179
179
  hap_cli/tests/test_record_v3_semantic_cli.py
180
+ hap_cli/tests/test_select_rejects_relation_value.py
180
181
  hap_cli/tests/test_semantic_types_registry.py
181
182
  hap_cli/tests/test_update.py
182
183
  hap_cli/tests/test_v3_api_schema_loader.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes