xsoar-cli 2.4.0__tar.gz → 2.4.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 (176) hide show
  1. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/CHANGELOG.md +5 -0
  2. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/PKG-INFO +1 -1
  3. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/_version.py +2 -2
  4. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/case/README.md +47 -0
  5. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/case/commands.py +106 -0
  6. xsoar_cli-2.4.1/src/xsoar_cli/xsoar_client/cases.py +78 -0
  7. xsoar_cli-2.4.1/src/xsoar_cli/xsoar_client/constants.py +8 -0
  8. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/xsoar_client/execution.py +2 -5
  9. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/cli/conftest.py +13 -3
  10. xsoar_cli-2.4.1/tests/cli/test_case.py +123 -0
  11. xsoar_cli-2.4.1/tests/unit/test_case_commands.py +39 -0
  12. xsoar_cli-2.4.1/tests/unit/test_cases.py +353 -0
  13. xsoar_cli-2.4.0/src/xsoar_cli/xsoar_client/cases.py +0 -25
  14. xsoar_cli-2.4.0/src/xsoar_cli/xsoar_client/constants.py +0 -4
  15. xsoar_cli-2.4.0/tests/cli/test_case.py +0 -54
  16. xsoar_cli-2.4.0/tests/unit/test_cases.py +0 -175
  17. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/.github/workflows/main.yml +0 -0
  18. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/.github/workflows/pull-request-open.yml +0 -0
  19. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/.github/workflows/release.yml +0 -0
  20. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/.github/workflows/update-changelog.yml +0 -0
  21. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/.gitignore +0 -0
  22. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/CLAUDE.md +0 -0
  23. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/CONTRIBUTING.md +0 -0
  24. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/LICENSE.txt +0 -0
  25. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/README.md +0 -0
  26. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/pyproject.toml +0 -0
  27. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/__init__.py +0 -0
  28. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/cli.py +0 -0
  29. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/__init__.py +0 -0
  30. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/case/__init__.py +0 -0
  31. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/completions/README.md +0 -0
  32. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/completions/__init__.py +0 -0
  33. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/completions/commands.py +0 -0
  34. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/config/README.md +0 -0
  35. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/config/__init__.py +0 -0
  36. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/config/commands.py +0 -0
  37. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/content/README.md +0 -0
  38. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/content/__init__.py +0 -0
  39. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/content/commands.py +0 -0
  40. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/execute/README.md +0 -0
  41. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/execute/__init__.py +0 -0
  42. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/execute/commands.py +0 -0
  43. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/graph/README.md +0 -0
  44. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/graph/__init__.py +0 -0
  45. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/graph/commands.py +0 -0
  46. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/integration/README.md +0 -0
  47. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/integration/__init__.py +0 -0
  48. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/integration/commands.py +0 -0
  49. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/manifest/README.md +0 -0
  50. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/manifest/__init__.py +0 -0
  51. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/manifest/commands.py +0 -0
  52. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/pack/README.md +0 -0
  53. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/pack/__init__.py +0 -0
  54. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/pack/commands.py +0 -0
  55. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/plugins/README.md +0 -0
  56. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/plugins/__init__.py +0 -0
  57. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/plugins/commands.py +0 -0
  58. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/rbac/README.md +0 -0
  59. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/rbac/__init__.py +0 -0
  60. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/commands/rbac/commands.py +0 -0
  61. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/configuration.py +0 -0
  62. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/error_handling/__init__.py +0 -0
  63. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/error_handling/connection.py +0 -0
  64. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/error_handling/http.py +0 -0
  65. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/log.py +0 -0
  66. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/plugins/README.md +0 -0
  67. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/plugins/__init__.py +0 -0
  68. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/plugins/manager.py +0 -0
  69. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/utilities/__init__.py +0 -0
  70. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/utilities/config_file.py +0 -0
  71. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/utilities/content.py +0 -0
  72. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/utilities/download_content_handlers.py +0 -0
  73. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/utilities/manifest.py +0 -0
  74. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/utilities/output.py +0 -0
  75. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/utilities/validators.py +0 -0
  76. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/utilities/version_check.py +0 -0
  77. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/xsoar_client/__init__.py +0 -0
  78. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/xsoar_client/artifact_providers/__init__.py +0 -0
  79. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/xsoar_client/artifact_providers/azure.py +0 -0
  80. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/xsoar_client/artifact_providers/base.py +0 -0
  81. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/xsoar_client/artifact_providers/s3.py +0 -0
  82. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/xsoar_client/client.py +0 -0
  83. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/xsoar_client/content.py +0 -0
  84. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/xsoar_client/integrations.py +0 -0
  85. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/xsoar_client/packs.py +0 -0
  86. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/src/xsoar_cli/xsoar_client/rbac.py +0 -0
  87. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/__init__.py +0 -0
  88. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/cli/__init__.py +0 -0
  89. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/cli/test_base.py +0 -0
  90. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/cli/test_completions.py +0 -0
  91. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/cli/test_config.py +0 -0
  92. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/cli/test_content.py +0 -0
  93. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/cli/test_execute.py +0 -0
  94. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/cli/test_graph.py +0 -0
  95. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/cli/test_manifest.py +0 -0
  96. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/cli/test_pack.py +0 -0
  97. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/cli/test_plugins.py +0 -0
  98. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/conftest.py +0 -0
  99. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/cases/create.json +0 -0
  100. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/cases/get.json +0 -0
  101. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/content/automation_search.json +0 -0
  102. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/content/playbook_search.json +0 -0
  103. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/content/user_commands.json +0 -0
  104. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/integrations/instances.json +0 -0
  105. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/manifest/manifest_base.json +0 -0
  106. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/manifest/manifest_invalid.json +0 -0
  107. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/manifest/manifest_with_pack_not_on_server.json +0 -0
  108. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/manifest/server_base_response.json +0 -0
  109. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/manifest/server_base_response_missing_one_pack.json +0 -0
  110. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/manifest/server_base_response_with_updates.json +0 -0
  111. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/manifest/server_base_response_with_updates_and_one_extra.json +0 -0
  112. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/packs/installed.json +0 -0
  113. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/packs/installed_expired.json +0 -0
  114. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/rbac/roles.json +0 -0
  115. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/rbac/user_groups.json +0 -0
  116. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/fixtures/rbac/users.json +0 -0
  117. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Download/playbook-empty.yml +0 -0
  118. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/.pack-ignore +0 -0
  119. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/.secrets-ignore +0 -0
  120. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/Author_image.png +0 -0
  121. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/Playbooks/GenericPlaybook.yml +0 -0
  122. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/README.md +0 -0
  123. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_CommonPlaybooks/pack_metadata.json +0 -0
  124. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_CommonScripts/.pack-ignore +0 -0
  125. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_CommonScripts/.secrets-ignore +0 -0
  126. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_CommonScripts/Author_image.png +0 -0
  127. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_CommonScripts/README.md +0 -0
  128. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_CommonScripts/Scripts/GenericScript/GenericScript.py +0 -0
  129. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_CommonScripts/Scripts/GenericScript/GenericScript.yml +0 -0
  130. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_CommonScripts/Scripts/GenericScript/README.md +0 -0
  131. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_CommonScripts/pack_metadata.json +0 -0
  132. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_EDR/.pack-ignore +0 -0
  133. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_EDR/.secrets-ignore +0 -0
  134. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_EDR/Author_image.png +0 -0
  135. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_EDR/Playbooks/EDR_InitialTriage.yml +0 -0
  136. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_EDR/README.md +0 -0
  137. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/EDR_FetchFile.py +0 -0
  138. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/EDR_FetchFile.yml +0 -0
  139. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_FetchFile/README.md +0 -0
  140. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_Triage/EDR_Triage.py +0 -0
  141. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_Triage/EDR_Triage.yml +0 -0
  142. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_EDR/Scripts/EDR_Triage/README.md +0 -0
  143. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_EDR/Scripts/LegacyItem/LegacyItem.py +0 -0
  144. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_EDR/Scripts/LegacyItem/LegacyItem.yml +0 -0
  145. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_EDR/Scripts/LegacyItem/README.md +0 -0
  146. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_EDR/Scripts/LegacyItem/test_data/basescript-dummy.json +0 -0
  147. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_EDR/pack_metadata.json +0 -0
  148. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_Layouts/.pack-ignore +0 -0
  149. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_Layouts/.secrets-ignore +0 -0
  150. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_Layouts/Author_image.png +0 -0
  151. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_Layouts/Layouts/layoutscontainer-GenericLayout.json +0 -0
  152. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_Layouts/README.md +0 -0
  153. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/MyOrg_Layouts/pack_metadata.json +0 -0
  154. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/mock_content/Packs/Not_applicable/Playbooks/Empty.yml +0 -0
  155. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/__init__.py +0 -0
  156. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/conftest.py +0 -0
  157. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_artifact_azure.py +0 -0
  158. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_artifact_base.py +0 -0
  159. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_artifact_s3.py +0 -0
  160. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_client.py +0 -0
  161. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_config_file.py +0 -0
  162. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_content_domain.py +0 -0
  163. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_content_filters.py +0 -0
  164. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_content_handlers.py +0 -0
  165. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_error_handling.py +0 -0
  166. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_execution.py +0 -0
  167. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_integrations.py +0 -0
  168. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_main.py +0 -0
  169. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_manifest_utils.py +0 -0
  170. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_output.py +0 -0
  171. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_packs.py +0 -0
  172. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_plugin_manager.py +0 -0
  173. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_rbac.py +0 -0
  174. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_validators.py +0 -0
  175. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/tests/unit/test_version_check.py +0 -0
  176. {xsoar_cli-2.4.0 → xsoar_cli-2.4.1}/uv.lock +0 -0
@@ -6,12 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.4.0] - 2026-06-17
10
+
9
11
  ### Added
10
12
 
11
13
  - `content get-detached --type all` queries both scripts and playbooks and displays results for each type.
12
14
  - `content list --output-format` option (choices: `table`, `json`, `plain`, default: `table`). `table` is human-readable aligned columns, `json` is machine-readable, and `plain` is tab-separated values suitable for piping to tools like `grep`.
13
15
  - `content list --search TERM` option. Filters the listed items with a case-insensitive substring match against each item's id, name, and description. Content types and integration brands with no matching items are omitted, and a `No content matching '<term>' found.` message is shown when nothing matches.
14
16
  - `content describe --type [script|playbook|command] NAME` command. Shows a single content item in detail: description, arguments, and inputs/outputs. Commands also show the integration brand and its configured instances (name and state). Supports `--output-format [table|json]` (default: `table`). See the [content README](src/xsoar_cli/commands/content/README.md) for details.
17
+ - `case get-context CASENUMBER` command. Retrieves the investigation context tree for a case as JSON, matching `demisto.context()` at runtime. The incident record is not merged in. See the [case README](src/xsoar_cli/commands/case/README.md) for details.
18
+ - `case get-entry ENTRY_ID` command. Retrieves a single War Room entry by its full ID (e.g. `112@153483`) as JSON. The case ID is derived from the entry ID. Exits non-zero with a clear message when the entry ID is malformed or the entry is not found. See the [case README](src/xsoar_cli/commands/case/README.md) for details.
19
+ - `case get-entries CASENUMBER` command. Retrieves all War Room entries for a case as a JSON array, or an empty array when the case has no entries. See the [case README](src/xsoar_cli/commands/case/README.md) for details.
15
20
 
16
21
  ### Changed
17
22
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xsoar-cli
3
- Version: 2.4.0
3
+ Version: 2.4.1
4
4
  Summary: A command-line interface for managing Palo Alto Networks XSOAR (Cortex XSOAR)
5
5
  Project-URL: Documentation, https://github.com/tlium/xsoar-cli#readme
6
6
  Project-URL: Issues, https://github.com/tlium/xsoar-cli/issues
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.4.0'
22
- __version_tuple__ = version_tuple = (2, 4, 0)
21
+ __version__ = version = '2.4.1'
22
+ __version_tuple__ = version_tuple = (2, 4, 1)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -17,6 +17,53 @@ xsoar-cli case get 312412
17
17
  xsoar-cli case get --environment prod 312412
18
18
  ```
19
19
 
20
+ ## Get Context
21
+
22
+ Retrieve the investigation context tree for a single case. Output is the raw context tree as JSON, matching `demisto.context()` at runtime. The incident record is not merged in.
23
+
24
+ **Syntax:** `xsoar-cli case get-context [OPTIONS] CASENUMBER`
25
+
26
+ **Options:**
27
+ - `--environment TEXT` - Target environment (default: uses default environment from config)
28
+
29
+ **Examples:**
30
+ ```
31
+ xsoar-cli case get-context 153483
32
+ xsoar-cli case get-context --environment prod 153483
33
+ ```
34
+
35
+ ## Get Entry
36
+
37
+ Retrieve a single War Room entry by its full ID. Output is the raw entry as JSON. The command exits non-zero with a clear message when no entry with that ID exists in the case.
38
+
39
+ `ENTRY_ID` is the full entry ID as shown in the GUI, in the form `<n>@<case-id>` (e.g. `112@153483`). The case ID is taken from the entry ID, so it does not need to be supplied separately.
40
+
41
+ **Syntax:** `xsoar-cli case get-entry [OPTIONS] ENTRY_ID`
42
+
43
+ **Options:**
44
+ - `--environment TEXT` - Target environment (default: uses default environment from config)
45
+
46
+ **Examples:**
47
+ ```
48
+ xsoar-cli case get-entry 112@153483
49
+ xsoar-cli case get-entry --environment prod 112@153483
50
+ ```
51
+
52
+ ## Get Entries
53
+
54
+ Retrieve all War Room entries for a single case. Output is the full list of entries as a JSON array, or an empty array when the case has no entries.
55
+
56
+ **Syntax:** `xsoar-cli case get-entries [OPTIONS] CASENUMBER`
57
+
58
+ **Options:**
59
+ - `--environment TEXT` - Target environment (default: uses default environment from config)
60
+
61
+ **Examples:**
62
+ ```
63
+ xsoar-cli case get-entries 153483
64
+ xsoar-cli case get-entries --environment prod 153483
65
+ ```
66
+
20
67
  ## Clone
21
68
 
22
69
  Clone a case from one environment to another. Useful for copying production cases to development environment for testing.
@@ -25,6 +25,19 @@ def parse_string_to_dict(input_string: str | None, delimiter: str) -> dict:
25
25
  return {key.strip(): value.strip() for key, value in valid_pairs}
26
26
 
27
27
 
28
+ def parse_entry_id(entry_id: str) -> int:
29
+ """Extract the numeric case ID from a full entry ID of the form '<n>@<case-id>'.
30
+
31
+ Raises ValueError if the entry ID is not in that form.
32
+ """
33
+ parts = entry_id.split("@")
34
+ expected_parts = 2
35
+ if len(parts) != expected_parts or not parts[0].isdigit() or not parts[1].isdigit():
36
+ msg = f"invalid entry id '{entry_id}', expected the form <n>@<case-id> (e.g. 112@153483)"
37
+ raise ValueError(msg)
38
+ return int(parts[1])
39
+
40
+
28
41
  @click.group()
29
42
  def case() -> None:
30
43
  """Create, retrieve, and clone cases"""
@@ -210,6 +223,99 @@ def create( # noqa: PLR0913
210
223
  click.echo(f"Created XSOAR case {case_id}")
211
224
 
212
225
 
226
+ @click.command(name="get-context")
227
+ @click.argument("casenumber", type=int)
228
+ @click.option("--environment", default=None, help="Default environment set in config file.")
229
+ @click.pass_context
230
+ @load_config
231
+ @validate_xsoar_connectivity
232
+ def get_context(ctx: click.Context, casenumber: int, environment: str | None) -> None:
233
+ """Retrieve the investigation context tree for a single case.
234
+
235
+ CASENUMBER is the numeric case ID to look up. Output is the raw context
236
+ tree as JSON, matching demisto.context() at runtime.
237
+
238
+ Usage examples:
239
+
240
+ xsoar-cli case get-context 153483
241
+ """
242
+ config = get_xsoar_config(ctx)
243
+ xsoar_client: Client = config.get_client(environment)
244
+ try:
245
+ response = xsoar_client.cases.get_context(casenumber)
246
+ except HTTPError as ex:
247
+ handler = HTTPErrorHandler()
248
+ click.echo(f"Error: {handler.get_message(ex, context='case')}")
249
+ ctx.exit(1)
250
+ click.echo(json.dumps(response, indent=4))
251
+
252
+
253
+ @click.command(name="get-entry")
254
+ @click.argument("entry_id", type=str)
255
+ @click.option("--environment", default=None, help="Default environment set in config file.")
256
+ @click.pass_context
257
+ @load_config
258
+ @validate_xsoar_connectivity
259
+ def get_entry(ctx: click.Context, entry_id: str, environment: str | None) -> None:
260
+ """Retrieve a single War Room entry by its full ID.
261
+
262
+ ENTRY_ID is the full entry ID as shown in the GUI, in the form
263
+ <n>@<case-id> (e.g. 112@153483). The case ID is taken from the entry ID, so
264
+ it does not need to be supplied separately. Output is the raw entry as JSON.
265
+
266
+ Usage examples:
267
+
268
+ xsoar-cli case get-entry 112@153483
269
+ """
270
+ try:
271
+ casenumber = parse_entry_id(entry_id)
272
+ except ValueError as ex:
273
+ click.echo(f"Error: {ex}")
274
+ ctx.exit(1)
275
+ config = get_xsoar_config(ctx)
276
+ xsoar_client: Client = config.get_client(environment)
277
+ try:
278
+ response = xsoar_client.cases.get_entry(casenumber, entry_id)
279
+ except HTTPError as ex:
280
+ handler = HTTPErrorHandler()
281
+ click.echo(f"Error: {handler.get_message(ex, context='case')}")
282
+ ctx.exit(1)
283
+ except ValueError as ex:
284
+ click.echo(f"Error: {ex}")
285
+ ctx.exit(1)
286
+ click.echo(json.dumps(response, indent=4))
287
+
288
+
289
+ @click.command(name="get-entries")
290
+ @click.argument("casenumber", type=int)
291
+ @click.option("--environment", default=None, help="Default environment set in config file.")
292
+ @click.pass_context
293
+ @load_config
294
+ @validate_xsoar_connectivity
295
+ def get_entries(ctx: click.Context, casenumber: int, environment: str | None) -> None:
296
+ """Retrieve all War Room entries for a single case.
297
+
298
+ CASENUMBER is the numeric case ID. Output is the full list of entries as a
299
+ JSON array, or an empty array when the case has no entries.
300
+
301
+ Usage examples:
302
+
303
+ xsoar-cli case get-entries 153483
304
+ """
305
+ config = get_xsoar_config(ctx)
306
+ xsoar_client: Client = config.get_client(environment)
307
+ try:
308
+ response = xsoar_client.cases.get_entries(casenumber)
309
+ except HTTPError as ex:
310
+ handler = HTTPErrorHandler()
311
+ click.echo(f"Error: {handler.get_message(ex, context='case')}")
312
+ ctx.exit(1)
313
+ click.echo(json.dumps(response, indent=4))
314
+
315
+
213
316
  case.add_command(get)
214
317
  case.add_command(clone)
215
318
  case.add_command(create)
319
+ case.add_command(get_context)
320
+ case.add_command(get_entry)
321
+ case.add_command(get_entries)
@@ -0,0 +1,78 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from .constants import INVESTIGATION_PAGE_SIZE
6
+
7
+ if TYPE_CHECKING:
8
+ from .client import Client
9
+
10
+
11
+ class Cases:
12
+ def __init__(self, client: Client) -> None:
13
+ self.client = client
14
+
15
+ def get(self, case_id: int) -> dict:
16
+ """Fetches a case by ID."""
17
+ endpoint = f"/incident/load/{case_id}"
18
+ response = self.client.make_request(endpoint=endpoint, method="GET")
19
+ response.raise_for_status()
20
+ return response.json()
21
+
22
+ def create(self, data: dict) -> dict:
23
+ """Creates a new case."""
24
+ endpoint = self.client.resolve_endpoint(v6="/incident", v8="/xsoar/public/v1/incident")
25
+ response = self.client.make_request(endpoint=endpoint, json=data, method="POST")
26
+ response.raise_for_status()
27
+ return response.json()
28
+
29
+ def get_context(self, case_id: int) -> dict:
30
+ """Fetches the investigation context tree for a case, verbatim.
31
+
32
+ Returns the raw POST /investigation/<id>/context response, which matches
33
+ demisto.context() at runtime. The incident record is not merged in.
34
+
35
+ The body query '${.}' is a DT expression meaning "the entire context
36
+ root", so the full context tree is returned.
37
+ """
38
+ endpoint = f"/investigation/{case_id}/context"
39
+ response = self.client.make_request(endpoint=endpoint, method="POST", json={"query": "${.}"})
40
+ response.raise_for_status()
41
+ return response.json()
42
+
43
+ def get_entry(self, case_id: int, entry_id: str) -> dict:
44
+ """Fetches a single War Room entry by its full ID (e.g. '112@153483').
45
+
46
+ XSOAR has no per-entry endpoint, so the full War Room history is fetched
47
+ via POST /investigation/<id> and the entry with the matching id is
48
+ returned verbatim. Raises ValueError if no entry with that id exists.
49
+ """
50
+ for entry in self._fetch_entries(case_id):
51
+ if entry.get("id") == entry_id:
52
+ return entry
53
+ msg = f"Entry '{entry_id}' not found in case {case_id}"
54
+ raise ValueError(msg)
55
+
56
+ def get_entries(self, case_id: int) -> list[dict]:
57
+ """Fetches all War Room entries for a case, verbatim.
58
+
59
+ Returns the full entries list from POST /investigation/<id>. An empty
60
+ list is returned when the case has no entries.
61
+ """
62
+ return self._fetch_entries(case_id)
63
+
64
+ def _fetch_entries(self, case_id: int) -> list[dict]:
65
+ """Fetches the full War Room entry history for a case.
66
+
67
+ XSOAR has no per-entry endpoint, so the whole history is retrieved with
68
+ a single POST /investigation/<id> call and the entries list is returned
69
+ verbatim.
70
+ """
71
+ data, _, _ = self.client.demisto_py_instance.generic_request(
72
+ path=f"/investigation/{case_id}",
73
+ method="POST",
74
+ body={"pageSize": INVESTIGATION_PAGE_SIZE},
75
+ content_type="application/json",
76
+ response_type=object,
77
+ )
78
+ return data.get("entries") or []
@@ -0,0 +1,8 @@
1
+ """Shared constants for the xsoar_client package."""
2
+
3
+ XSOAR_OLD_VERSION = 6
4
+ HTTP_CALL_TIMEOUT = 30
5
+
6
+ # Page size requested when fetching investigation entries via POST /investigation/<id>.
7
+ # Set high so that all War Room entries are returned in a single response.
8
+ INVESTIGATION_PAGE_SIZE = 1000
@@ -4,6 +4,8 @@ import logging
4
4
  import time
5
5
  from typing import TYPE_CHECKING
6
6
 
7
+ from .constants import INVESTIGATION_PAGE_SIZE
8
+
7
9
  if TYPE_CHECKING:
8
10
  from .client import Client
9
11
 
@@ -23,11 +25,6 @@ DEFAULT_SYNC_TIMEOUT = 30
23
25
  # result entries.
24
26
  SYNC_POLL_INTERVAL = 2
25
27
 
26
- # Page size requested when fetching investigation entries during polling. The
27
- # playground accumulates history over time, so this is set high to ensure a
28
- # freshly created result entry is included in the response.
29
- INVESTIGATION_PAGE_SIZE = 1000
30
-
31
28
 
32
29
  def _format_arg_value(value: str) -> str:
33
30
  """Format a single argument value for a War Room command string.
@@ -272,9 +272,10 @@ def mock_execute_env(mock_config_file) -> Iterator[types.SimpleNamespace]: # no
272
272
  def mock_case_env(mock_config_file, make_case_response, make_case_create_response) -> Iterator[types.SimpleNamespace]: # noqa: ANN001
273
273
  """Mock environment for ``case`` commands.
274
274
 
275
- Patches ``Cases.get`` and ``Cases.create`` with default success responses.
276
- The mocks are exposed on the yielded namespace so tests can override
277
- return values or side effects as needed.
275
+ Patches ``Cases.get``, ``Cases.create``, ``Cases.get_context``,
276
+ ``Cases.get_entry``, and ``Cases.get_entries`` with default success
277
+ responses. The mocks are exposed on the yielded namespace so tests can
278
+ override return values or side effects as needed.
278
279
  """
279
280
  import types as _types
280
281
 
@@ -282,13 +283,22 @@ def mock_case_env(mock_config_file, make_case_response, make_case_create_respons
282
283
  patch("xsoar_cli.xsoar_client.client.Client.test_connectivity", return_value=True) as mock_conn,
283
284
  patch("xsoar_cli.xsoar_client.cases.Cases.get") as mock_get,
284
285
  patch("xsoar_cli.xsoar_client.cases.Cases.create") as mock_create,
286
+ patch("xsoar_cli.xsoar_client.cases.Cases.get_context") as mock_get_context,
287
+ patch("xsoar_cli.xsoar_client.cases.Cases.get_entry") as mock_get_entry,
288
+ patch("xsoar_cli.xsoar_client.cases.Cases.get_entries") as mock_get_entries,
285
289
  ):
286
290
  mock_get.return_value = make_case_response()
287
291
  mock_create.return_value = make_case_create_response()
292
+ mock_get_context.return_value = {"playbook_name": "My Playbook", "File": []}
293
+ mock_get_entry.return_value = {"id": "112@153483", "contents": "entry data"}
294
+ mock_get_entries.return_value = [{"id": "112@153483", "contents": "entry data"}]
288
295
  ns = _types.SimpleNamespace(
289
296
  config=mock_config_file,
290
297
  connectivity=mock_conn,
291
298
  get=mock_get,
292
299
  create=mock_create,
300
+ get_context=mock_get_context,
301
+ get_entry=mock_get_entry,
302
+ get_entries=mock_get_entries,
293
303
  )
294
304
  yield ns
@@ -0,0 +1,123 @@
1
+ """CLI integration tests for the ``case`` command group."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from tests.cli.conftest import InvokeHelper
9
+
10
+
11
+ class TestCaseGroup:
12
+ """The bare ``case`` command and its help output."""
13
+
14
+ def test_shows_help(self, invoke: InvokeHelper) -> None:
15
+ result = invoke(["case"])
16
+ assert result.exit_code == 0
17
+
18
+ def test_help_flag(self, invoke: InvokeHelper) -> None:
19
+ result = invoke(["case", "--help"])
20
+ assert result.exit_code == 0
21
+
22
+
23
+ class TestCaseGet:
24
+ """Tests for ``case get``."""
25
+
26
+ def test_get_case_success(self, invoke: InvokeHelper, mock_case_env) -> None:
27
+ result = invoke(["case", "get", "152230"])
28
+ assert result.exit_code == 0
29
+
30
+ def test_get_case_http_error(self, invoke: InvokeHelper, mock_case_env, make_http_error) -> None:
31
+ mock_case_env.get.side_effect = make_http_error(400, text="Bad Request")
32
+ result = invoke(["case", "get", "152230"])
33
+ assert result.exit_code == 1
34
+
35
+
36
+ class TestCaseCreate:
37
+ """Tests for ``case create``."""
38
+
39
+ def test_create_case_success(self, invoke: InvokeHelper, mock_case_env) -> None:
40
+ result = invoke(["case", "create"])
41
+ assert result.exit_code == 0
42
+
43
+
44
+ class TestCaseClone:
45
+ """Tests for ``case clone``."""
46
+
47
+ def test_clone_missing_options(self, invoke: InvokeHelper, mock_case_env) -> None:
48
+ result = invoke(["case", "clone", "152230"])
49
+ assert result.exit_code == 2
50
+
51
+ def test_clone_with_bogus_environments(self, invoke: InvokeHelper, mock_case_env) -> None:
52
+ result = invoke(["case", "clone", "--dest", "bogus", "--source", "bogus", "152230"])
53
+ assert result.exit_code == 1
54
+
55
+
56
+ class TestCaseGetContext:
57
+ """Tests for ``case get-context``."""
58
+
59
+ def test_get_context_success(self, invoke: InvokeHelper, mock_case_env) -> None:
60
+ result = invoke(["case", "get-context", "153483"])
61
+ assert result.exit_code == 0
62
+ mock_case_env.get_context.assert_called_once_with(153483)
63
+
64
+ def test_get_context_http_error(self, invoke: InvokeHelper, mock_case_env, make_http_error) -> None:
65
+ mock_case_env.get_context.side_effect = make_http_error(404, text="Not Found")
66
+ result = invoke(["case", "get-context", "153483"])
67
+ assert result.exit_code == 1
68
+
69
+
70
+ class TestCaseGetEntry:
71
+ """Tests for ``case get-entry``."""
72
+
73
+ def test_get_entry_success(self, invoke: InvokeHelper, mock_case_env) -> None:
74
+ result = invoke(["case", "get-entry", "112@153483"])
75
+ assert result.exit_code == 0
76
+ mock_case_env.get_entry.assert_called_once_with(153483, "112@153483")
77
+
78
+ def test_get_entry_not_found(self, invoke: InvokeHelper, mock_case_env) -> None:
79
+ mock_case_env.get_entry.side_effect = ValueError("Entry '999@153483' not found in case 153483")
80
+ result = invoke(["case", "get-entry", "999@153483"])
81
+ assert result.exit_code == 1
82
+ assert "not found" in result.output
83
+
84
+ def test_get_entry_http_error(self, invoke: InvokeHelper, mock_case_env, make_http_error) -> None:
85
+ mock_case_env.get_entry.side_effect = make_http_error(400, text="Bad Request")
86
+ result = invoke(["case", "get-entry", "112@153483"])
87
+ assert result.exit_code == 1
88
+
89
+ def test_get_entry_missing_entry_id_argument(self, invoke: InvokeHelper, mock_case_env) -> None:
90
+ result = invoke(["case", "get-entry"])
91
+ assert result.exit_code == 2
92
+
93
+ def test_get_entry_malformed_entry_id(self, invoke: InvokeHelper, mock_case_env) -> None:
94
+ result = invoke(["case", "get-entry", "112"])
95
+ assert result.exit_code == 1
96
+ assert "invalid entry id" in result.output
97
+ mock_case_env.get_entry.assert_not_called()
98
+
99
+ def test_get_entry_non_numeric_entry_number(self, invoke: InvokeHelper, mock_case_env) -> None:
100
+ result = invoke(["case", "get-entry", "12INVALID21@1234"])
101
+ assert result.exit_code == 1
102
+ assert "invalid entry id" in result.output
103
+ mock_case_env.get_entry.assert_not_called()
104
+
105
+
106
+ class TestCaseGetEntries:
107
+ """Tests for ``case get-entries``."""
108
+
109
+ def test_get_entries_success(self, invoke: InvokeHelper, mock_case_env) -> None:
110
+ result = invoke(["case", "get-entries", "153483"])
111
+ assert result.exit_code == 0
112
+ mock_case_env.get_entries.assert_called_once_with(153483)
113
+
114
+ def test_get_entries_empty(self, invoke: InvokeHelper, mock_case_env) -> None:
115
+ mock_case_env.get_entries.return_value = []
116
+ result = invoke(["case", "get-entries", "153483"])
117
+ assert result.exit_code == 0
118
+ assert result.output.strip() == "[]"
119
+
120
+ def test_get_entries_http_error(self, invoke: InvokeHelper, mock_case_env, make_http_error) -> None:
121
+ mock_case_env.get_entries.side_effect = make_http_error(404, text="Not Found")
122
+ result = invoke(["case", "get-entries", "153483"])
123
+ assert result.exit_code == 1
@@ -0,0 +1,39 @@
1
+ """Unit tests for helpers in ``xsoar_cli.commands.case.commands``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pytest
6
+
7
+ from xsoar_cli.commands.case.commands import parse_entry_id
8
+
9
+
10
+ class TestParseEntryId:
11
+ def test_valid_entry_id_returns_case_id(self) -> None:
12
+ assert parse_entry_id("112@153483") == 153483
13
+
14
+ def test_multi_digit_entry_number(self) -> None:
15
+ assert parse_entry_id("9@7") == 7
16
+
17
+ def test_missing_at_separator_raises(self) -> None:
18
+ with pytest.raises(ValueError, match="invalid entry id"):
19
+ parse_entry_id("112")
20
+
21
+ def test_empty_entry_number_raises(self) -> None:
22
+ with pytest.raises(ValueError, match="invalid entry id"):
23
+ parse_entry_id("@153483")
24
+
25
+ def test_non_numeric_entry_number_raises(self) -> None:
26
+ with pytest.raises(ValueError, match="invalid entry id"):
27
+ parse_entry_id("12INVALID21@1234")
28
+
29
+ def test_empty_case_id_raises(self) -> None:
30
+ with pytest.raises(ValueError, match="invalid entry id"):
31
+ parse_entry_id("112@")
32
+
33
+ def test_non_numeric_case_id_raises(self) -> None:
34
+ with pytest.raises(ValueError, match="invalid entry id"):
35
+ parse_entry_id("112@abc")
36
+
37
+ def test_too_many_parts_raises(self) -> None:
38
+ with pytest.raises(ValueError, match="invalid entry id"):
39
+ parse_entry_id("112@153483@extra")