hap-cli 0.7.0__tar.gz → 0.7.1__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 (177) hide show
  1. {hap_cli-0.7.0 → hap_cli-0.7.1}/PKG-INFO +1 -1
  2. {hap_cli-0.7.0 → hap_cli-0.7.1}/app_live_tests/__init__.py +1 -1
  3. {hap_cli-0.7.0 → hap_cli-0.7.1}/app_live_tests/config.py +1 -1
  4. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/__init__.py +1 -1
  5. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/instance_cmd.py +10 -4
  6. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/instance.py +45 -32
  7. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/workflow_node_dsl.py +10 -2
  8. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_core.py +93 -28
  9. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_integration_approval.py +83 -0
  10. hap_cli-0.7.1/hap_cli/tests/test_integration_approval_actions.py +237 -0
  11. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_integration_destructive.py +4 -4
  12. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_worksheet_crud_cli.py +1 -1
  13. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli.egg-info/PKG-INFO +1 -1
  14. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli.egg-info/SOURCES.txt +1 -0
  15. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/seed/template.py +1 -1
  16. {hap_cli-0.7.0 → hap_cli-0.7.1}/MANIFEST.in +0 -0
  17. {hap_cli-0.7.0 → hap_cli-0.7.1}/README.md +0 -0
  18. {hap_cli-0.7.0 → hap_cli-0.7.1}/app_live_tests/__main__.py +0 -0
  19. {hap_cli-0.7.0 → hap_cli-0.7.1}/app_live_tests/harness.py +0 -0
  20. {hap_cli-0.7.0 → hap_cli-0.7.1}/app_live_tests/smoke.py +0 -0
  21. {hap_cli-0.7.0 → hap_cli-0.7.1}/app_live_tests/state.py +0 -0
  22. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/README.md +0 -0
  23. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/README_CN.md +0 -0
  24. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/__init__.py +0 -0
  25. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/app_cmd.py +0 -0
  26. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/auth_cmd.py +0 -0
  27. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/calendar_cmd.py +0 -0
  28. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/chart_cmd.py +0 -0
  29. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/chat_cmd.py +0 -0
  30. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/chatbot_cmd.py +0 -0
  31. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/config_cmd.py +0 -0
  32. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/contact_cmd.py +0 -0
  33. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/department_cmd.py +0 -0
  34. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/group_cmd.py +0 -0
  35. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/icon_cmd.py +0 -0
  36. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/node_cmd.py +0 -0
  37. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/optionset_cmd.py +0 -0
  38. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/page_cmd.py +0 -0
  39. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/post_cmd.py +0 -0
  40. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/record_cmd.py +0 -0
  41. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/region_cmd.py +0 -0
  42. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/role_cmd.py +0 -0
  43. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/upload_cmd.py +0 -0
  44. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/v3_registry.py +0 -0
  45. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/workflow_cmd.py +0 -0
  46. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/commands/worksheet_cmd.py +0 -0
  47. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/context.py +0 -0
  48. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/__init__.py +0 -0
  49. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/action_spec_adapter.py +0 -0
  50. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/app.py +0 -0
  51. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/auth.py +0 -0
  52. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/calendar_mod.py +0 -0
  53. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/chart.py +0 -0
  54. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/chat.py +0 -0
  55. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/chatbot.py +0 -0
  56. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/contact.py +0 -0
  57. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/control_type_codes.py +0 -0
  58. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/department.py +0 -0
  59. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/field_normalizer.py +0 -0
  60. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/field_spec_adapter.py +0 -0
  61. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/filter_translator.py +0 -0
  62. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/flow_node.py +0 -0
  63. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/global_meta.py +0 -0
  64. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/group.py +0 -0
  65. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/icon_index.py +0 -0
  66. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/logger.py +0 -0
  67. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/optionset.py +0 -0
  68. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/page.py +0 -0
  69. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/post.py +0 -0
  70. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/record.py +0 -0
  71. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/response_crypto.py +0 -0
  72. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/role.py +0 -0
  73. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/role_perm_builder.py +0 -0
  74. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/session.py +0 -0
  75. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/token_crypto.py +0 -0
  76. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/upload.py +0 -0
  77. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/v3/__init__.py +0 -0
  78. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/v3/dispatcher.py +0 -0
  79. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/v3/render.py +0 -0
  80. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/v3/schema.py +0 -0
  81. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/view_spec_adapter.py +0 -0
  82. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/workflow.py +0 -0
  83. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/worksheet.py +0 -0
  84. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/core/worksheet_templates.py +0 -0
  85. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/hap_cli.py +0 -0
  86. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/i18n.py +0 -0
  87. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/locale/__init__.py +0 -0
  88. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/locale/messages.json +0 -0
  89. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/locale/messages.schema.json +0 -0
  90. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/__init__.py +0 -0
  91. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/conftest.py +0 -0
  92. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_auth_prerelease.py +0 -0
  93. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_chart.py +0 -0
  94. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_config_cmd.py +0 -0
  95. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_full_e2e.py +0 -0
  96. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_global_meta.py +0 -0
  97. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_i18n.py +0 -0
  98. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_integration.py +0 -0
  99. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_integration_calendar.py +0 -0
  100. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_integration_misc.py +0 -0
  101. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_integration_post.py +0 -0
  102. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_integration_social.py +0 -0
  103. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_integration_v3.py +0 -0
  104. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_integration_workflow.py +0 -0
  105. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
  106. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_org_id_cli.py +0 -0
  107. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_org_id_docs.py +0 -0
  108. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_parameter_conventions.py +0 -0
  109. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
  110. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
  111. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_v3_dispatcher.py +0 -0
  112. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
  113. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_v3_session.py +0 -0
  114. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
  115. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/utils/__init__.py +0 -0
  116. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/utils/formatting.py +0 -0
  117. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/utils/options.py +0 -0
  118. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli/utils/parameter_mapping.py +0 -0
  119. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli.egg-info/dependency_links.txt +0 -0
  120. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli.egg-info/entry_points.txt +0 -0
  121. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli.egg-info/requires.txt +0 -0
  122. {hap_cli-0.7.0 → hap_cli-0.7.1}/hap_cli.egg-info/top_level.txt +0 -0
  123. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/__init__.py +0 -0
  124. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/__main__.py +0 -0
  125. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/_wftest.py +0 -0
  126. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/charts.py +0 -0
  127. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/cleanup.py +0 -0
  128. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/compiler.py +0 -0
  129. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/config.py +0 -0
  130. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/errors.py +0 -0
  131. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/executor.py +0 -0
  132. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/fields.py +0 -0
  133. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/hap.py +0 -0
  134. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/recording/__init__.py +0 -0
  135. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/recording/console.py +0 -0
  136. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/recording/jsonl.py +0 -0
  137. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/recording/mirror.py +0 -0
  138. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/recording/report.py +0 -0
  139. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/schema.py +0 -0
  140. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/seed/__init__.py +0 -0
  141. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/seed/cli.py +0 -0
  142. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/seed/executor.py +0 -0
  143. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/selftest.py +0 -0
  144. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/smoke.py +0 -0
  145. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/step.py +0 -0
  146. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/steps.py +0 -0
  147. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/store.py +0 -0
  148. {hap_cli-0.7.0 → hap_cli-0.7.1}/live_tests/workflow_dsl.py +0 -0
  149. {hap_cli-0.7.0 → hap_cli-0.7.1}/setup.cfg +0 -0
  150. {hap_cli-0.7.0 → hap_cli-0.7.1}/setup.py +0 -0
  151. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/INDEX.json +0 -0
  152. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
  153. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
  154. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/create_optionset.yaml +0 -0
  155. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/create_role.yaml +0 -0
  156. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/delete_optionset.yaml +0 -0
  157. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/delete_record.yaml +0 -0
  158. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/delete_role.yaml +0 -0
  159. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
  160. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/get_app_info.yaml +0 -0
  161. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
  162. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
  163. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
  164. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/get_record_details.yaml +0 -0
  165. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
  166. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/get_record_list.yaml +0 -0
  167. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/get_record_logs.yaml +0 -0
  168. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
  169. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/get_record_relations.yaml +0 -0
  170. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
  171. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/get_regions.yaml +0 -0
  172. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/get_role_details.yaml +0 -0
  173. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/get_role_list.yaml +0 -0
  174. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/knowledge_search.yaml +0 -0
  175. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
  176. {hap_cli-0.7.0 → hap_cli-0.7.1}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
  177. {hap_cli-0.7.0 → hap_cli-0.7.1}/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.7.0
3
+ Version: 0.7.1
4
4
  Summary: CLI harness for MingDAO HAP - Enterprise no-code platform
5
5
  Author: hap-cli
6
6
  License: Apache-2.0
@@ -1,4 +1,4 @@
1
- """Repeatable live smoke tests for the hap-app-editor skill.
1
+ """Repeatable live smoke tests for the hap-cli-app-editor skill.
2
2
 
3
3
  This is a top-level, stdlib-only package (no ``test_*.py`` files, so the
4
4
  default pytest run never collects it — mirror of ``live_tests/``). It
@@ -12,7 +12,7 @@ PYTHON_BIN = os.environ.get("HAP_EDITOR_PYTHON", "") # "" -> sys.executable
12
12
 
13
13
  _PKG_DIR = Path(__file__).resolve().parent
14
14
  REPO_ROOT = _PKG_DIR.parent
15
- SKILL_DIR = REPO_ROOT / "skills" / "hap-app-editor"
15
+ SKILL_DIR = REPO_ROOT / "skills" / "hap-cli-app-editor"
16
16
 
17
17
  STATE_DIR = _PKG_DIR / "state"
18
18
  STATE_FILE = STATE_DIR / "current_app.json"
@@ -1,3 +1,3 @@
1
1
  """CLI harness for MingDAO HAP (hap-cli)."""
2
2
 
3
- __version__ = "0.7.0"
3
+ __version__ = "0.7.1"
@@ -294,18 +294,24 @@ def instance_history_detail(ctx, instance_id):
294
294
 
295
295
  @instance.command("batch")
296
296
  @click.option("--action", "-a", required=True, type=int, help="4=batch approve, 5=batch reject")
297
- @click.option("--type", "type_", default=-1, type=int, help="Task type filter")
297
+ @click.option("--type", "type_", default=4, type=int, help="Task type the selection came from (4=pending approval)")
298
298
  @click.option("--process-id", default="", help="Filter by process")
299
- @click.option("--select", "-s", multiple=True, help="Selected instance IDs")
299
+ @click.option("--select", "-s", multiple=True, help="Task to act on, as TASK_ID:WORK_ID (repeatable)")
300
+ @click.option("--opinion", default="", help="Comment applied to each task")
300
301
  @pass_context
301
- def instance_batch(ctx, action, type_, process_id, select):
302
- """Batch approve or reject tasks."""
302
+ def instance_batch(ctx, action, type_, process_id, select, opinion):
303
+ """Batch approve or reject tasks.
304
+
305
+ Pass each task as TASK_ID:WORK_ID (the IDs from the pending list); repeat
306
+ --select for more than one.
307
+ """
303
308
  try:
304
309
  session = ctx.get_session()
305
310
  data = inst_mod.batch_operation(
306
311
  session, action,
307
312
  type_=type_, process_id=process_id,
308
313
  selects=list(select) if select else None,
314
+ opinion=opinion,
309
315
  )
310
316
  ctx.output(data, lambda d: click.echo(f"Batch operation completed: {d}"))
311
317
  except Exception as e:
@@ -119,7 +119,8 @@ def get_instance_detail(
119
119
  data: dict[str, Any] = {"id": instance_id}
120
120
  if work_id:
121
121
  data["workId"] = work_id
122
- return session.workflow_call("v2/instance/get", data)
122
+ # pd-openweb instanceVersion.js:30-34 (get2) pins type='GET'; POST returns 404.
123
+ return session.workflow_call("v2/instance/get", data, method="GET")
123
124
 
124
125
 
125
126
  def get_operation_detail(
@@ -140,7 +141,8 @@ def get_operation_detail(
140
141
  data: dict[str, Any] = {"id": instance_id}
141
142
  if work_id:
142
143
  data["workId"] = work_id
143
- return session.workflow_call("instance/getOperationDetail", data)
144
+ # pd-openweb instance.js:101-105 pins type='GET'; POST returns 404.
145
+ return session.workflow_call("instance/getOperationDetail", data, method="GET")
144
146
 
145
147
 
146
148
  def get_operation_history(
@@ -156,9 +158,11 @@ def get_operation_history(
156
158
  Returns:
157
159
  List of operation records
158
160
  """
161
+ # pd-openweb instance.js:114-118 pins type='GET'; POST returns 404.
159
162
  result = session.workflow_call(
160
163
  "instance/getOperationHistoryList",
161
164
  {"instanceId": instance_id},
165
+ method="GET",
162
166
  )
163
167
  return result if isinstance(result, list) else result.get("list", [])
164
168
 
@@ -408,48 +412,52 @@ def urge(
408
412
  def batch_operation(
409
413
  session: Session,
410
414
  batch_operation_type: int,
411
- instance_id: str = "",
412
- work_id: str = "",
413
- page_index: int = 1,
414
- page_size: int = 50,
415
- type_: int = -1,
416
- apk_id: str = "",
415
+ type_: int = 4,
417
416
  process_id: str = "",
418
417
  selects: Optional[list] = None,
418
+ opinion: str = "",
419
419
  ) -> dict[str, Any]:
420
- """Batch approve/reject/forward tasks.
420
+ """Batch approve/reject pending tasks.
421
421
 
422
422
  Args:
423
423
  session: Active session
424
- batch_operation_type: 4=pass, 5=reject
425
- instance_id: Instance ID (for single)
426
- work_id: Work item ID
427
- page_index: Page
428
- page_size: Size
429
- type_: Task type filter
430
- apk_id: App filter
431
- process_id: Process filter
432
- selects: Selected item IDs for batch
424
+ batch_operation_type: 4=pass (approve), 5=overrule (reject)
425
+ type_: Task-type context the selection came from (4=pending approval).
426
+ process_id: Optional process filter (echoed back to the server).
427
+ selects: The work items to act on. Each entry is either a
428
+ ``{"id": <instanceId>, "workId": <workId>, "opinion": <str>}``
429
+ dict or an ``"<instanceId>:<workId>"`` string. The wire requires
430
+ one object per item carrying ``id``/``workId``/``opinion`` and
431
+ ``opinionType: 3`` (batch). A bare id list is rejected (HTTP 400).
432
+ opinion: Default opinion applied to items that don't carry their own.
433
433
 
434
434
  Returns:
435
435
  Batch result
436
436
  """
437
+ # pd-openweb instanceVersion.js batch2 (/v2/instance/batch, POST) — the
438
+ # payload is {type, batchOperationType, selects:[{id, workId, opinion,
439
+ # opinionType:3}]} (MyProcess/index.jsx). Paging fields are NOT part of
440
+ # the batch body; sending them (and bare-id selects) is what 400s.
441
+ sel: list[dict[str, Any]] = []
442
+ for s in selects or []:
443
+ if isinstance(s, dict):
444
+ item = {
445
+ "id": s.get("id", ""),
446
+ "workId": s.get("workId", ""),
447
+ "opinion": s.get("opinion", opinion),
448
+ "opinionType": 3,
449
+ }
450
+ else:
451
+ inst_id, _, w_id = str(s).partition(":")
452
+ item = {"id": inst_id, "workId": w_id, "opinion": opinion, "opinionType": 3}
453
+ sel.append(item)
437
454
  data: dict[str, Any] = {
438
- "batchOperationType": batch_operation_type,
439
- "pageIndex": page_index,
440
- "pageSize": page_size,
441
455
  "type": type_,
456
+ "batchOperationType": batch_operation_type,
457
+ "selects": sel,
442
458
  }
443
- if instance_id:
444
- data["id"] = instance_id
445
- if work_id:
446
- data["workId"] = work_id
447
- if apk_id:
448
- data["apkId"] = apk_id
449
459
  if process_id:
450
460
  data["processId"] = process_id
451
- if selects:
452
- data["selects"] = selects
453
461
  return session.workflow_call("v2/instance/batch", data)
454
462
 
455
463
 
@@ -519,9 +527,11 @@ def get_history_detail(
519
527
  Returns:
520
528
  History detail with node execution info
521
529
  """
530
+ # pd-openweb instance.js:52-56 pins type='GET'; POST returns 404.
522
531
  return session.workflow_call(
523
532
  "instance/getHistoryDetail",
524
533
  {"instanceId": instance_id},
534
+ method="GET",
525
535
  )
526
536
 
527
537
 
@@ -538,8 +548,9 @@ def end_instance(session: Session, instance_id: str) -> dict[str, Any]:
538
548
  Returns:
539
549
  Termination result
540
550
  """
551
+ # pd-openweb instanceVersion.js:135-139 pins type='GET'; POST returns 404.
541
552
  return session.workflow_call(
542
- "v1/instance/endInstance", {"instanceId": instance_id}
553
+ "v1/instance/endInstance", {"instanceId": instance_id}, method="GET"
543
554
  )
544
555
 
545
556
 
@@ -553,8 +564,9 @@ def reset_instance(session: Session, instance_id: str) -> dict[str, Any]:
553
564
  Returns:
554
565
  Reset result
555
566
  """
567
+ # pd-openweb instanceVersion.js:228-232 pins type='GET'; POST returns 404.
556
568
  return session.workflow_call(
557
- "v1/instance/resetInstance", {"instanceId": instance_id}
569
+ "v1/instance/resetInstance", {"instanceId": instance_id}, method="GET"
558
570
  )
559
571
 
560
572
 
@@ -700,7 +712,8 @@ def get_work_item(
700
712
  data: dict[str, Any] = {"id": instance_id}
701
713
  if work_id:
702
714
  data["workId"] = work_id
703
- return session.workflow_call("v1/instance/getWorkItem", data)
715
+ # pd-openweb instanceVersion.js:215-219 pins type='GET'; POST returns 404.
716
+ return session.workflow_call("v1/instance/getWorkItem", data, method="GET")
704
717
 
705
718
 
706
719
  def get_archived_list(session: Session) -> list[dict[str, Any]]:
@@ -402,8 +402,16 @@ def translate_accounts(
402
402
  "roleName": acc.get("roleName", ""),
403
403
  "avatar": "", "count": 0})
404
404
  elif kind == "user":
405
- out.append({"type": 2, "entityId": acc.get("userId", ""),
406
- "entityName": acc.get("userName", "")})
405
+ # A specific named member. Ground truth: pd-openweb's shared
406
+ # SelectUserDropDown (WorkflowSettings/.../SelectUserDropDown,
407
+ # USER_TYPE.USER) — a fixed user is **type 1** with the accountId
408
+ # in ``roleId`` (NOT type 2, and NOT in entityId). type 2 is a
409
+ # role; an accountId placed in entityId can't bind and the server
410
+ # renders the recipient "已删除", failing publish (warningType 105).
411
+ # Same shape for approve and cc/notice — the picker is shared.
412
+ out.append({"type": 1, "entityId": "", "entityName": "",
413
+ "roleId": acc.get("userId", ""),
414
+ "roleName": acc.get("userName", ""), "avatar": ""})
407
415
  elif kind in ("owner", "triggerUser"):
408
416
  # The record owner / trigger user: a type-6 ref keyed off a
409
417
  # node (default the trigger) with the special "uaid" roleId.
@@ -1184,11 +1184,16 @@ class TestInstance:
1184
1184
  result = inst_mod.get_todo_list(mock_session, type_=4)
1185
1185
  assert result["count"] == 1
1186
1186
 
1187
+ @patch("hap_cli.core.session.requests.get")
1187
1188
  @patch("hap_cli.core.session.requests.post")
1188
- def test_get_instance_detail(self, mock_post, mock_session):
1189
- mock_post.return_value = _mock_response({"id": "inst1", "status": 1})
1189
+ def test_get_instance_detail(self, mock_post, mock_get, mock_session):
1190
+ """v2/instance/get is GET (instanceVersion.js:30-34)."""
1191
+ mock_get.return_value = _mock_response({"id": "inst1", "status": 1})
1190
1192
  result = inst_mod.get_instance_detail(mock_session, "inst1")
1191
1193
  assert result["status"] == 1
1194
+ assert mock_get.called
1195
+ assert not mock_post.called
1196
+ assert mock_get.call_args[0][0].endswith("/v2/instance/get")
1192
1197
 
1193
1198
  @patch("hap_cli.core.session.requests.post")
1194
1199
  def test_approve(self, mock_post, mock_session):
@@ -1243,19 +1248,8 @@ class TestInstance:
1243
1248
  body = mock_post.call_args[1]["json"]
1244
1249
  assert body["operationType"] == 18
1245
1250
 
1246
- @patch("hap_cli.core.session.requests.post")
1247
- def test_end_instance(self, mock_post, mock_session):
1248
- mock_post.return_value = _mock_response(True)
1249
- inst_mod.end_instance(mock_session, "inst1")
1250
- body = mock_post.call_args[1]["json"]
1251
- assert body["instanceId"] == "inst1"
1252
-
1253
- @patch("hap_cli.core.session.requests.post")
1254
- def test_reset_instance(self, mock_post, mock_session):
1255
- mock_post.return_value = _mock_response(True)
1256
- inst_mod.reset_instance(mock_session, "inst1")
1257
- body = mock_post.call_args[1]["json"]
1258
- assert body["instanceId"] == "inst1"
1251
+ # end_instance / reset_instance GET-verb guards live in
1252
+ # test_end_instance_is_get / test_reset_instance_is_get below.
1259
1253
 
1260
1254
  @patch("hap_cli.core.session.requests.get")
1261
1255
  @patch("hap_cli.core.session.requests.post")
@@ -1271,27 +1265,67 @@ class TestInstance:
1271
1265
  assert not mock_post.called
1272
1266
  assert mock_get.call_args[0][0].endswith("/instance/getHistoryList")
1273
1267
 
1268
+ @patch("hap_cli.core.session.requests.get")
1274
1269
  @patch("hap_cli.core.session.requests.post")
1275
- def test_get_history_detail(self, mock_post, mock_session):
1276
- mock_post.return_value = _mock_response({"instanceId": "inst1", "nodes": []})
1270
+ def test_get_history_detail(self, mock_post, mock_get, mock_session):
1271
+ """instance/getHistoryDetail is GET (instance.js:52-56)."""
1272
+ mock_get.return_value = _mock_response({"instanceId": "inst1", "nodes": []})
1277
1273
  result = inst_mod.get_history_detail(mock_session, "inst1")
1278
1274
  assert result["instanceId"] == "inst1"
1275
+ assert mock_get.called
1276
+ assert not mock_post.called
1277
+ assert mock_get.call_args[0][0].endswith("/instance/getHistoryDetail")
1279
1278
 
1279
+ @patch("hap_cli.core.session.requests.get")
1280
1280
  @patch("hap_cli.core.session.requests.post")
1281
- def test_get_operation_history(self, mock_post, mock_session):
1282
- mock_post.return_value = _mock_response([{"action": "pass", "time": "2024-01-01"}])
1281
+ def test_get_operation_history(self, mock_post, mock_get, mock_session):
1282
+ """instance/getOperationHistoryList is GET (instance.js:114-118)."""
1283
+ mock_get.return_value = _mock_response([{"action": "pass", "time": "2024-01-01"}])
1283
1284
  result = inst_mod.get_operation_history(mock_session, "inst1")
1284
1285
  assert len(result) == 1
1286
+ assert mock_get.called
1287
+ assert not mock_post.called
1288
+ assert mock_get.call_args[0][0].endswith("/instance/getOperationHistoryList")
1285
1289
 
1286
1290
  @patch("hap_cli.core.session.requests.post")
1287
1291
  def test_batch_operation(self, mock_post, mock_session):
1288
- mock_post.return_value = _mock_response({"success": 5})
1289
- result = inst_mod.batch_operation(
1290
- mock_session, 4, process_id="wf1", selects=["s1", "s2"],
1292
+ # v2/instance/batch wants {type, batchOperationType, selects:[{id,
1293
+ # workId, opinion, opinionType:3}]}. Bare-id selects / paging fields
1294
+ # are rejected (HTTP 400). "id:workId" strings normalise to objects.
1295
+ mock_post.return_value = _mock_response({"success": [], "fail": []})
1296
+ inst_mod.batch_operation(
1297
+ mock_session, 4, process_id="wf1",
1298
+ selects=["i1:w1", {"id": "i2", "workId": "w2", "opinion": "ok"}],
1291
1299
  )
1292
1300
  body = mock_post.call_args[1]["json"]
1301
+ assert mock_post.call_args[0][0].endswith("/v2/instance/batch")
1302
+ assert body["type"] == 4
1293
1303
  assert body["batchOperationType"] == 4
1294
- assert body["selects"] == ["s1", "s2"]
1304
+ assert "pageIndex" not in body and "pageSize" not in body
1305
+ assert body["selects"] == [
1306
+ {"id": "i1", "workId": "w1", "opinion": "", "opinionType": 3},
1307
+ {"id": "i2", "workId": "w2", "opinion": "ok", "opinionType": 3},
1308
+ ]
1309
+
1310
+ @patch("hap_cli.core.session.requests.get")
1311
+ @patch("hap_cli.core.session.requests.post")
1312
+ def test_end_instance_is_get(self, mock_post, mock_get, mock_session):
1313
+ """v1/instance/endInstance is GET (instanceVersion.js:135-139)."""
1314
+ mock_get.return_value = _mock_response({"status": 4})
1315
+ inst_mod.end_instance(mock_session, "i1")
1316
+ assert mock_get.called and not mock_post.called
1317
+ assert mock_get.call_args[0][0].endswith("/v1/instance/endInstance")
1318
+ assert mock_get.call_args[1]["params"] == {"instanceId": "i1"}
1319
+
1320
+ @patch("hap_cli.core.session.requests.get")
1321
+ @patch("hap_cli.core.session.requests.post")
1322
+ def test_reset_instance_is_get(self, mock_post, mock_get, mock_session):
1323
+ """v1/instance/resetInstance is GET (instanceVersion.js:228-232)."""
1324
+ mock_get.return_value = _mock_response({"status": 1})
1325
+ inst_mod.reset_instance(mock_session, "i1")
1326
+ assert mock_get.called and not mock_post.called
1327
+ assert mock_get.call_args[0][0].endswith("/v1/instance/resetInstance")
1328
+ assert mock_get.call_args[1]["params"] == {"instanceId": "i1"}
1295
1329
 
1296
1330
 
1297
1331
  # ── Role Tests ───────────────────────────────────────────────────────────
@@ -5610,14 +5644,29 @@ class TestApprovalBatchLifecycle:
5610
5644
  class TestApprovalWorkItemAndArchive:
5611
5645
  """Wave 8 — single work-item + archived-service lookups."""
5612
5646
 
5647
+ @patch("hap_cli.core.session.requests.get")
5613
5648
  @patch("hap_cli.core.session.requests.post")
5614
- def test_get_work_item_sends_id_and_workId(self, mock_post, mock_session):
5615
- mock_post.return_value = _mock_response({"id": "i1", "workId": "w1"})
5649
+ def test_get_work_item_sends_id_and_workId(self, mock_post, mock_get, mock_session):
5650
+ """v1/instance/getWorkItem is GET (instanceVersion.js:215-219)."""
5651
+ mock_get.return_value = _mock_response({"id": "i1", "workId": "w1"})
5616
5652
  inst_mod.get_work_item(mock_session, "i1", work_id="w1")
5617
- url = mock_post.call_args[0][0]
5618
- body = mock_post.call_args[1]["json"]
5653
+ assert mock_get.called
5654
+ assert not mock_post.called
5655
+ url = mock_get.call_args[0][0]
5619
5656
  assert url.endswith("/v1/instance/getWorkItem")
5620
- assert body == {"id": "i1", "workId": "w1"}
5657
+ assert mock_get.call_args[1]["params"] == {"id": "i1", "workId": "w1"}
5658
+
5659
+ @patch("hap_cli.core.session.requests.get")
5660
+ @patch("hap_cli.core.session.requests.post")
5661
+ def test_get_operation_detail_is_get(self, mock_post, mock_get, mock_session):
5662
+ """instance/getOperationDetail is GET (instance.js:101-105)."""
5663
+ mock_get.return_value = _mock_response({"workItems": [], "nextUserRange": []})
5664
+ inst_mod.get_operation_detail(mock_session, "i1", work_id="w1")
5665
+ assert mock_get.called
5666
+ assert not mock_post.called
5667
+ url = mock_get.call_args[0][0]
5668
+ assert url.endswith("/instance/getOperationDetail")
5669
+ assert mock_get.call_args[1]["params"] == {"id": "i1", "workId": "w1"}
5621
5670
 
5622
5671
  @patch("hap_cli.core.session.requests.get")
5623
5672
  @patch("hap_cli.core.session.requests.post")
@@ -6931,6 +6980,22 @@ class TestWorkflowNodeDslPrimitives:
6931
6980
  assert out[0]["type"] == 2 and out[0]["entityId"] == "app1"
6932
6981
  assert out[0]["roleId"] == "r1"
6933
6982
 
6983
+ def test_user_account_is_type1_with_account_in_roleid(self):
6984
+ # A specific named member: pd-openweb's shared SelectUserDropDown
6985
+ # (USER_TYPE.USER) encodes a fixed user as type 1 with the accountId
6986
+ # in roleId — NOT type 2, and NOT the accountId in entityId. type 2 is
6987
+ # a role; a misplaced accountId can't bind and publish fails 105.
6988
+ from hap_cli.core import workflow_node_dsl as dsl
6989
+ for for_approve in (False, True):
6990
+ out = dsl.translate_accounts(
6991
+ [{"kind": "user", "userId": "acc-123", "userName": "田然"}],
6992
+ for_approve=for_approve,
6993
+ )
6994
+ assert out[0]["type"] == 1, out[0]
6995
+ assert out[0]["roleId"] == "acc-123", out[0]
6996
+ assert out[0]["entityId"] == "", out[0]
6997
+ assert out[0]["roleName"] == "田然"
6998
+
6934
6999
  def test_operator_to_condition_id_matches_enum(self):
6935
7000
  from hap_cli.core import workflow_node_dsl as dsl
6936
7001
  # Spot-check against pd-openweb CONDITION_TYPE.
@@ -44,6 +44,23 @@ def _sample_process_id(integration_session, app_id):
44
44
  return items[0].get("id") or items[0].get("processId")
45
45
 
46
46
 
47
+ @pytest.fixture(scope="module")
48
+ def _sample_todo(integration_session):
49
+ """A real (instance_id, work_id) pair drawn from the pending-task list.
50
+
51
+ The per-instance read endpoints (``get`` / ``getOperationDetail`` /
52
+ ``getWorkItem`` …) only resolve for a live work item the current user can
53
+ see, so we pull one straight from ``getTodoList``. Skip the whole class
54
+ when the inbox is empty — there is nothing to read.
55
+ """
56
+ result = inst.get_todo_list(integration_session, page_size=5)
57
+ items = (result or {}).get("data") or []
58
+ if not items:
59
+ pytest.skip("no pending approval tasks in inbox — cannot test per-instance reads")
60
+ item = items[0]
61
+ return item.get("id"), item.get("workId", "")
62
+
63
+
47
64
  @pytest.fixture(scope="module")
48
65
  def _dummy_account_id(integration_session, project_id):
49
66
  """Pick any real accountId from the project directory for the delegation
@@ -128,6 +145,72 @@ class TestApproval:
128
145
  assert isinstance(result["count"], int)
129
146
 
130
147
 
148
+ # ── Per-instance read-side tests ─────────────────────────────────────────────
149
+
150
+
151
+ class TestInstanceRead:
152
+ """GET-vs-POST regression guards for the per-instance read endpoints.
153
+
154
+ All five 404'd in production because the wrappers issued POST while
155
+ pd-openweb pins ``base.ajaxOptions.type = 'GET'`` on each:
156
+
157
+ - ``v2/instance/get`` (instanceVersion.js:30-34)
158
+ - ``instance/getOperationDetail`` (instance.js:101-105)
159
+ - ``instance/getOperationHistoryList``(instance.js:114-118)
160
+ - ``v1/instance/getWorkItem`` (instanceVersion.js:215-219)
161
+ - ``instance/getHistoryDetail`` (instance.js:52-56)
162
+
163
+ Each test exercises the wrapper end-to-end against a live work item so a
164
+ verb regression surfaces immediately as a 404.
165
+ """
166
+
167
+ def test_01_get_detail(self, integration_session, _sample_todo):
168
+ """v2/instance/get must be GET; returns the instance flow detail."""
169
+ instance_id, work_id = _sample_todo
170
+ result = inst.get_instance_detail(
171
+ integration_session, instance_id, work_id=work_id
172
+ )
173
+ assert_dict_like(result)
174
+ # Flow detail always carries the works array + a title.
175
+ assert "works" in result or "currentWork" in result, (
176
+ f"unexpected instance detail shape: {list(result)!r}"
177
+ )
178
+
179
+ def test_02_operations(self, integration_session, _sample_todo):
180
+ """instance/getOperationDetail must be GET; returns available ops."""
181
+ instance_id, work_id = _sample_todo
182
+ result = inst.get_operation_detail(
183
+ integration_session, instance_id, work_id=work_id
184
+ )
185
+ assert_dict_like(result)
186
+
187
+ def test_03_operation_history(self, integration_session, _sample_todo):
188
+ """instance/getOperationHistoryList must be GET; returns a list."""
189
+ instance_id, _ = _sample_todo
190
+ result = inst.get_operation_history(integration_session, instance_id)
191
+ assert_list_like(result, item_type=dict)
192
+
193
+ def test_04_work_item(self, integration_session, _sample_todo):
194
+ """v1/instance/getWorkItem must be GET; returns the work-item entity."""
195
+ instance_id, work_id = _sample_todo
196
+ result = inst.get_work_item(
197
+ integration_session, instance_id, work_id=work_id
198
+ )
199
+ # Endpoint returns a dict entity (or null for already-completed work).
200
+ assert result is None or isinstance(result, dict), (
201
+ f"work-item should be dict/None, got {type(result).__name__}"
202
+ )
203
+
204
+ def test_05_history_detail(self, integration_session, _sample_todo):
205
+ """instance/getHistoryDetail must be GET; returns execution detail."""
206
+ instance_id, _ = _sample_todo
207
+ result = inst.get_history_detail(integration_session, instance_id)
208
+ assert_dict_like(result)
209
+ assert "works" in result or "status" in result, (
210
+ f"unexpected history detail shape: {list(result)!r}"
211
+ )
212
+
213
+
131
214
  # ── Delegation CRUD ────────────────────────────────────────────────────────
132
215
 
133
216