cmdx 1.1.1 → 1.5.0

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 (193) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/prompts/docs.md +4 -1
  4. data/.cursor/prompts/llms.md +20 -0
  5. data/.cursor/prompts/rspec.md +4 -1
  6. data/.cursor/prompts/yardoc.md +3 -2
  7. data/.cursor/rules/cursor-instructions.mdc +56 -1
  8. data/.irbrc +6 -0
  9. data/.rubocop.yml +29 -18
  10. data/.ruby-version +1 -1
  11. data/CHANGELOG.md +6 -128
  12. data/LLM.md +3317 -0
  13. data/README.md +68 -44
  14. data/docs/attributes/coercions.md +162 -0
  15. data/docs/attributes/defaults.md +90 -0
  16. data/docs/attributes/definitions.md +281 -0
  17. data/docs/attributes/naming.md +78 -0
  18. data/docs/attributes/validations.md +309 -0
  19. data/docs/basics/chain.md +56 -249
  20. data/docs/basics/context.md +56 -289
  21. data/docs/basics/execution.md +114 -0
  22. data/docs/basics/setup.md +37 -334
  23. data/docs/callbacks.md +89 -467
  24. data/docs/deprecation.md +91 -174
  25. data/docs/getting_started.md +212 -202
  26. data/docs/internationalization.md +11 -647
  27. data/docs/interruptions/exceptions.md +23 -198
  28. data/docs/interruptions/faults.md +71 -151
  29. data/docs/interruptions/halt.md +109 -186
  30. data/docs/logging.md +44 -256
  31. data/docs/middlewares.md +113 -426
  32. data/docs/outcomes/result.md +81 -228
  33. data/docs/outcomes/states.md +33 -221
  34. data/docs/outcomes/statuses.md +21 -311
  35. data/docs/tips_and_tricks.md +120 -70
  36. data/docs/workflows.md +99 -283
  37. data/lib/cmdx/.DS_Store +0 -0
  38. data/lib/cmdx/attribute.rb +229 -0
  39. data/lib/cmdx/attribute_registry.rb +94 -0
  40. data/lib/cmdx/attribute_value.rb +193 -0
  41. data/lib/cmdx/callback_registry.rb +69 -77
  42. data/lib/cmdx/chain.rb +56 -73
  43. data/lib/cmdx/coercion_registry.rb +52 -68
  44. data/lib/cmdx/coercions/array.rb +19 -18
  45. data/lib/cmdx/coercions/big_decimal.rb +20 -24
  46. data/lib/cmdx/coercions/boolean.rb +26 -25
  47. data/lib/cmdx/coercions/complex.rb +21 -22
  48. data/lib/cmdx/coercions/date.rb +25 -23
  49. data/lib/cmdx/coercions/date_time.rb +24 -25
  50. data/lib/cmdx/coercions/float.rb +25 -22
  51. data/lib/cmdx/coercions/hash.rb +31 -32
  52. data/lib/cmdx/coercions/integer.rb +30 -24
  53. data/lib/cmdx/coercions/rational.rb +29 -24
  54. data/lib/cmdx/coercions/string.rb +19 -22
  55. data/lib/cmdx/coercions/symbol.rb +37 -0
  56. data/lib/cmdx/coercions/time.rb +26 -25
  57. data/lib/cmdx/configuration.rb +49 -108
  58. data/lib/cmdx/context.rb +222 -44
  59. data/lib/cmdx/deprecator.rb +61 -0
  60. data/lib/cmdx/errors.rb +42 -252
  61. data/lib/cmdx/exceptions.rb +39 -0
  62. data/lib/cmdx/faults.rb +78 -39
  63. data/lib/cmdx/freezer.rb +51 -0
  64. data/lib/cmdx/identifier.rb +30 -0
  65. data/lib/cmdx/locale.rb +52 -0
  66. data/lib/cmdx/log_formatters/json.rb +21 -22
  67. data/lib/cmdx/log_formatters/key_value.rb +20 -22
  68. data/lib/cmdx/log_formatters/line.rb +15 -22
  69. data/lib/cmdx/log_formatters/logstash.rb +22 -23
  70. data/lib/cmdx/log_formatters/raw.rb +16 -22
  71. data/lib/cmdx/middleware_registry.rb +70 -74
  72. data/lib/cmdx/middlewares/correlate.rb +90 -54
  73. data/lib/cmdx/middlewares/runtime.rb +58 -0
  74. data/lib/cmdx/middlewares/timeout.rb +48 -68
  75. data/lib/cmdx/railtie.rb +12 -45
  76. data/lib/cmdx/result.rb +229 -314
  77. data/lib/cmdx/task.rb +194 -366
  78. data/lib/cmdx/utils/call.rb +49 -0
  79. data/lib/cmdx/utils/condition.rb +71 -0
  80. data/lib/cmdx/utils/format.rb +61 -0
  81. data/lib/cmdx/validator_registry.rb +63 -72
  82. data/lib/cmdx/validators/exclusion.rb +38 -67
  83. data/lib/cmdx/validators/format.rb +48 -49
  84. data/lib/cmdx/validators/inclusion.rb +43 -74
  85. data/lib/cmdx/validators/length.rb +91 -154
  86. data/lib/cmdx/validators/numeric.rb +87 -162
  87. data/lib/cmdx/validators/presence.rb +37 -50
  88. data/lib/cmdx/version.rb +1 -1
  89. data/lib/cmdx/worker.rb +178 -0
  90. data/lib/cmdx/workflow.rb +85 -81
  91. data/lib/cmdx.rb +19 -13
  92. data/lib/generators/cmdx/install_generator.rb +14 -13
  93. data/lib/generators/cmdx/task_generator.rb +25 -50
  94. data/lib/generators/cmdx/templates/install.rb +11 -46
  95. data/lib/generators/cmdx/templates/task.rb.tt +3 -2
  96. data/lib/locales/en.yml +18 -4
  97. data/src/cmdx-logo.png +0 -0
  98. metadata +32 -116
  99. data/docs/ai_prompts.md +0 -393
  100. data/docs/basics/call.md +0 -317
  101. data/docs/configuration.md +0 -344
  102. data/docs/parameters/coercions.md +0 -396
  103. data/docs/parameters/defaults.md +0 -335
  104. data/docs/parameters/definitions.md +0 -446
  105. data/docs/parameters/namespacing.md +0 -378
  106. data/docs/parameters/validations.md +0 -405
  107. data/docs/testing.md +0 -553
  108. data/lib/cmdx/callback.rb +0 -53
  109. data/lib/cmdx/chain_inspector.rb +0 -56
  110. data/lib/cmdx/chain_serializer.rb +0 -63
  111. data/lib/cmdx/coercion.rb +0 -57
  112. data/lib/cmdx/coercions/virtual.rb +0 -29
  113. data/lib/cmdx/core_ext/hash.rb +0 -83
  114. data/lib/cmdx/core_ext/module.rb +0 -98
  115. data/lib/cmdx/core_ext/object.rb +0 -125
  116. data/lib/cmdx/correlator.rb +0 -122
  117. data/lib/cmdx/error.rb +0 -60
  118. data/lib/cmdx/fault.rb +0 -140
  119. data/lib/cmdx/immutator.rb +0 -52
  120. data/lib/cmdx/lazy_struct.rb +0 -246
  121. data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
  122. data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
  123. data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
  124. data/lib/cmdx/logger.rb +0 -49
  125. data/lib/cmdx/logger_ansi.rb +0 -68
  126. data/lib/cmdx/logger_serializer.rb +0 -116
  127. data/lib/cmdx/middleware.rb +0 -70
  128. data/lib/cmdx/parameter.rb +0 -312
  129. data/lib/cmdx/parameter_evaluator.rb +0 -231
  130. data/lib/cmdx/parameter_inspector.rb +0 -66
  131. data/lib/cmdx/parameter_registry.rb +0 -106
  132. data/lib/cmdx/parameter_serializer.rb +0 -59
  133. data/lib/cmdx/result_ansi.rb +0 -71
  134. data/lib/cmdx/result_inspector.rb +0 -71
  135. data/lib/cmdx/result_logger.rb +0 -59
  136. data/lib/cmdx/result_serializer.rb +0 -104
  137. data/lib/cmdx/rspec/matchers.rb +0 -28
  138. data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
  139. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
  140. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
  141. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
  142. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
  143. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
  144. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
  145. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
  146. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
  147. data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
  148. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
  149. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
  150. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
  151. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
  152. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
  153. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
  154. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
  155. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
  156. data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
  157. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
  158. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
  159. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
  160. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
  161. data/lib/cmdx/task_deprecator.rb +0 -52
  162. data/lib/cmdx/task_processor.rb +0 -246
  163. data/lib/cmdx/task_serializer.rb +0 -57
  164. data/lib/cmdx/utils/ansi_color.rb +0 -73
  165. data/lib/cmdx/utils/log_timestamp.rb +0 -36
  166. data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
  167. data/lib/cmdx/utils/name_affix.rb +0 -52
  168. data/lib/cmdx/validator.rb +0 -57
  169. data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
  170. data/lib/generators/cmdx/workflow_generator.rb +0 -84
  171. data/lib/locales/ar.yml +0 -35
  172. data/lib/locales/cs.yml +0 -35
  173. data/lib/locales/da.yml +0 -35
  174. data/lib/locales/de.yml +0 -35
  175. data/lib/locales/el.yml +0 -35
  176. data/lib/locales/es.yml +0 -35
  177. data/lib/locales/fi.yml +0 -35
  178. data/lib/locales/fr.yml +0 -35
  179. data/lib/locales/he.yml +0 -35
  180. data/lib/locales/hi.yml +0 -35
  181. data/lib/locales/it.yml +0 -35
  182. data/lib/locales/ja.yml +0 -35
  183. data/lib/locales/ko.yml +0 -35
  184. data/lib/locales/nl.yml +0 -35
  185. data/lib/locales/no.yml +0 -35
  186. data/lib/locales/pl.yml +0 -35
  187. data/lib/locales/pt.yml +0 -35
  188. data/lib/locales/ru.yml +0 -35
  189. data/lib/locales/sv.yml +0 -35
  190. data/lib/locales/th.yml +0 -35
  191. data/lib/locales/tr.yml +0 -35
  192. data/lib/locales/vi.yml +0 -35
  193. data/lib/locales/zh.yml +0 -35
@@ -1,104 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CMDx
4
- # Result serialization module for converting result objects to hash format.
5
- #
6
- # This module provides functionality to serialize result objects into a
7
- # standardized hash representation that includes essential metadata about
8
- # the result such as task information, execution state, status, outcome,
9
- # metadata, and runtime. For failed results, it intelligently strips
10
- # redundant failure information to avoid duplication in serialized output.
11
- module ResultSerializer
12
-
13
- # Proc for stripping failure information from serialized results.
14
- # Removes caused_failure and threw_failure keys when the result doesn't
15
- # have the corresponding failure state, avoiding redundant information.
16
- STRIP_FAILURE = proc do |h, r, k|
17
- unless r.send(:"#{k}?")
18
- # Strip caused/threw failures since its the same info as the log line
19
- h[k] = r.send(k).to_h.except(:caused_failure, :threw_failure)
20
- end
21
- end.freeze
22
-
23
- module_function
24
-
25
- # Serializes a result object into a hash representation.
26
- #
27
- # Converts a result instance into a standardized hash format containing
28
- # task metadata and execution information. For failed results, applies
29
- # intelligent failure stripping to remove redundant caused_failure and
30
- # threw_failure information that would duplicate log output.
31
- #
32
- # @param result [CMDx::Result] the result object to serialize
33
- #
34
- # @return [Hash] a hash containing the result's metadata and execution information
35
- # @option return [Integer] :index the result's position index in the execution chain
36
- # @option return [String] :chain_id the unique identifier of the result's execution chain
37
- # @option return [String] :type the task type, either "Task" or "Workflow"
38
- # @option return [String] :class the full class name of the task
39
- # @option return [String] :id the unique identifier of the task instance
40
- # @option return [Array] :tags the tags associated with the task from cmd settings
41
- # @option return [Symbol] :state the execution state (:executing, :complete, :interrupted)
42
- # @option return [Symbol] :status the execution status (:success, :failed, :skipped)
43
- # @option return [Symbol] :outcome the execution outcome (:good, :bad)
44
- # @option return [Hash] :metadata additional metadata collected during execution
45
- # @option return [Float] :runtime the execution runtime in seconds
46
- # @option return [Hash] :caused_failure failure information if result caused a failure (stripped for non-failed results)
47
- # @option return [Hash] :threw_failure failure information if result threw a failure (stripped for non-failed results)
48
- #
49
- # @raise [NoMethodError] if the result doesn't respond to required methods
50
- #
51
- # @example Serialize a successful result
52
- # task = SuccessfulTask.new(data: "test")
53
- # ResultSerializer.call(result)
54
- # #=> {
55
- # # index: 0,
56
- # # chain_id: "abc123",
57
- # # type: "Task",
58
- # # class: "SuccessfulTask",
59
- # # id: "def456",
60
- # # tags: [],
61
- # # state: :complete,
62
- # # status: :success,
63
- # # outcome: :good,
64
- # # metadata: {},
65
- # # runtime: 0.045
66
- # # }
67
- #
68
- # @example Serialize a failed result with failure stripping
69
- # task = FailingTask.call
70
- # ResultSerializer.call(task.result)
71
- # #=> {
72
- # # index: 1,
73
- # # chain_id: "xyz789",
74
- # # type: "Task",
75
- # # class: "FailingTask",
76
- # # id: "ghi012",
77
- # # tags: [],
78
- # # state: :interrupted,
79
- # # status: :failed,
80
- # # outcome: :bad,
81
- # # metadata: { reason: "Database connection failed" },
82
- # # runtime: 0.012,
83
- # # caused_failure: { message: "Task failed", ... },
84
- # # threw_failure: { message: "Validation error", ... },
85
- # # }
86
- def call(result)
87
- TaskSerializer.call(result.task).tap do |hash|
88
- hash.merge!(
89
- state: result.state,
90
- status: result.status,
91
- outcome: result.outcome,
92
- metadata: result.metadata,
93
- runtime: result.runtime
94
- )
95
-
96
- if result.failed?
97
- STRIP_FAILURE.call(hash, result, :caused_failure)
98
- STRIP_FAILURE.call(hash, result, :threw_failure)
99
- end
100
- end
101
- end
102
-
103
- end
104
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Result matchers
4
- require_relative "result_matchers/be_successful_task"
5
- require_relative "result_matchers/be_failed_task"
6
- require_relative "result_matchers/be_skipped_task"
7
- require_relative "result_matchers/be_executed"
8
- require_relative "result_matchers/be_state_matchers"
9
- require_relative "result_matchers/be_status_matchers"
10
- require_relative "result_matchers/have_good_outcome"
11
- require_relative "result_matchers/have_bad_outcome"
12
- require_relative "result_matchers/have_runtime"
13
- require_relative "result_matchers/have_metadata"
14
- require_relative "result_matchers/have_empty_metadata"
15
- require_relative "result_matchers/have_context"
16
- require_relative "result_matchers/have_preserved_context"
17
- require_relative "result_matchers/have_caused_failure"
18
- require_relative "result_matchers/have_thrown_failure"
19
- require_relative "result_matchers/have_received_thrown_failure"
20
- require_relative "result_matchers/have_chain_index"
21
-
22
- # Task matchers
23
- require_relative "task_matchers/be_well_formed_task"
24
- require_relative "task_matchers/have_cmd_setting"
25
- require_relative "task_matchers/have_middleware"
26
- require_relative "task_matchers/have_callback"
27
- require_relative "task_matchers/have_parameter"
28
- require_relative "task_matchers/have_executed_callbacks"
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matcher for asserting that a task result has been executed.
4
- #
5
- # This matcher checks if a CMDx::Result object is in an executed state,
6
- # which occurs when the task has finished execution regardless of whether
7
- # it succeeded, failed, or was skipped. A result is considered executed
8
- # when it's in either "complete" or "interrupted" state.
9
- #
10
- # @return [Boolean] true if the result is executed (complete or interrupted)
11
- #
12
- # @example Basic usage with successful task
13
- # result = MyTask.call(user_id: 123)
14
- # expect(result).to be_executed
15
- #
16
- # @example Usage with failed task
17
- # result = FailingTask.call
18
- # expect(result).to be_executed
19
- #
20
- # @example Negative assertion
21
- # task = MyTask.new
22
- # expect(task.result).not_to be_executed
23
- #
24
- # @example In workflow integration tests
25
- # result = MyWorkflow.call(data: "test")
26
- # expect(result).to be_executed
27
- # expect(result.context.processed).to be(true)
28
- RSpec::Matchers.define :be_executed do
29
- match(&:executed?)
30
-
31
- failure_message do |result|
32
- "expected result to be executed, but was in #{result.state} state"
33
- end
34
-
35
- failure_message_when_negated do |result|
36
- "expected result not to be executed, but it was (state: #{result.state})"
37
- end
38
-
39
- description do
40
- "be executed"
41
- end
42
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matcher for asserting that a task result has failed with specific conditions.
4
- #
5
- # This matcher checks if a CMDx::Result object is in a failed state, which means
6
- # the task was executed but encountered an error or failure condition. A result
7
- # is considered failed when it's in both "failed" status and "interrupted" state,
8
- # and has been executed. Optionally checks for specific failure reasons and metadata.
9
- #
10
- # @param expected_reason [String, Symbol, nil] optional expected failure reason
11
- #
12
- # @return [Boolean] true if the result is failed, interrupted, executed, and matches expected criteria
13
- #
14
- # @example Basic usage with failed task
15
- # result = ValidateUserTask.call(user_id: nil)
16
- # expect(result).to be_failed_task
17
- #
18
- # @example Checking for specific failure reason
19
- # result = ProcessPaymentTask.call(amount: -100)
20
- # expect(result).to be_failed_task("invalid_amount")
21
- #
22
- # @example Using with_reason chain
23
- # result = AuthenticateUserTask.call(token: "invalid")
24
- # expect(result).to be_failed_task.with_reason(:authentication_failed)
25
- #
26
- # @example Checking failure with metadata
27
- # result = UploadFileTask.call(file: corrupted_file)
28
- # expect(result).to be_failed_task.with_metadata(file_size: 0, error_code: "CORRUPTED")
29
- #
30
- # @example Combining reason and metadata checks
31
- # result = ValidateDataTask.call(data: invalid_data)
32
- # expect(result).to be_failed_task("validation_error").with_metadata(field: "email", rule: "format")
33
- #
34
- # @example Negative assertion
35
- # result = SuccessfulTask.call(data: "valid")
36
- # expect(result).not_to be_failed_task
37
- RSpec::Matchers.define :be_failed_task do |expected_reason = nil|
38
- match do |result|
39
- result.failed? &&
40
- result.interrupted? &&
41
- result.executed? &&
42
- (expected_reason.nil? || result.metadata[:reason] == expected_reason)
43
- end
44
-
45
- chain :with_reason do |reason|
46
- @expected_reason = reason
47
- end
48
-
49
- chain :with_metadata do |metadata|
50
- @expected_metadata = metadata
51
- end
52
-
53
- match do |result|
54
- reason = @expected_reason || expected_reason
55
- metadata = @expected_metadata || {}
56
-
57
- result.failed? &&
58
- result.interrupted? &&
59
- result.executed? &&
60
- (reason.nil? || result.metadata[:reason] == reason) &&
61
- (metadata.empty? || metadata.all? { |k, v| result.metadata[k] == v })
62
- end
63
-
64
- failure_message do |result|
65
- messages = []
66
- messages << "expected result to be failed, but was #{result.status}" unless result.failed?
67
- messages << "expected result to be interrupted, but was #{result.state}" unless result.interrupted?
68
- messages << "expected result to be executed, but was not" unless result.executed?
69
-
70
- reason = @expected_reason || expected_reason
71
- messages << "expected failure reason to be '#{reason}', but was '#{result.metadata[:reason]}'" if reason && result.metadata[:reason] != reason
72
-
73
- if @expected_metadata&.any?
74
- mismatches = @expected_metadata.filter_map do |k, v|
75
- "#{k}: expected #{v}, got #{result.metadata[k]}" if result.metadata[k] != v
76
- end
77
- messages.concat(mismatches)
78
- end
79
-
80
- messages.join(", ")
81
- end
82
-
83
- failure_message_when_negated do |_result|
84
- "expected result not to be failed, but it was"
85
- end
86
-
87
- description do
88
- desc = "be a failed task"
89
- reason = @expected_reason || expected_reason
90
- desc += " with reason '#{reason}'" if reason
91
- desc += " with metadata #{@expected_metadata}" if @expected_metadata&.any?
92
- desc
93
- end
94
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matcher for asserting that a task result has been skipped with specific conditions.
4
- #
5
- # This matcher checks if a CMDx::Result object is in a skipped state, which means
6
- # the task was executed but was intentionally skipped due to some condition. A result
7
- # is considered skipped when it's in both "skipped" status and "interrupted" state,
8
- # and has been executed. Optionally checks for specific skip reasons and metadata.
9
- #
10
- # @param expected_reason [String, Symbol, nil] optional expected skip reason
11
- #
12
- # @return [Boolean] true if the result is skipped, interrupted, executed, and matches expected criteria
13
- #
14
- # @example Basic usage with skipped task
15
- # result = ProcessUserTask.call(user_id: 123)
16
- # expect(result).to be_skipped_task
17
- #
18
- # @example Checking for specific skip reason
19
- # result = SendEmailTask.call(user: inactive_user)
20
- # expect(result).to be_skipped_task("user_inactive")
21
- #
22
- # @example Using with_reason chain
23
- # result = BackupDataTask.call(force: false)
24
- # expect(result).to be_skipped_task.with_reason(:backup_not_needed)
25
- #
26
- # @example Checking skip with metadata
27
- # result = ProcessQueueTask.call(queue: empty_queue)
28
- # expect(result).to be_skipped_task.with_metadata(queue_size: 0, processed_count: 0)
29
- #
30
- # @example Combining reason and metadata checks
31
- # result = SyncDataTask.call(data: outdated_data)
32
- # expect(result).to be_skipped_task("data_unchanged").with_metadata(last_sync: timestamp, changes: 0)
33
- #
34
- # @example Negative assertion
35
- # result = ExecutedTask.call(data: "valid")
36
- # expect(result).not_to be_skipped_task
37
- RSpec::Matchers.define :be_skipped_task do |expected_reason = nil|
38
- match do |result|
39
- result.skipped? &&
40
- result.interrupted? &&
41
- result.executed? &&
42
- (expected_reason.nil? || result.metadata[:reason] == expected_reason)
43
- end
44
-
45
- chain :with_reason do |reason|
46
- @expected_reason = reason
47
- end
48
-
49
- chain :with_metadata do |metadata|
50
- @expected_metadata = metadata
51
- end
52
-
53
- match do |result|
54
- reason = @expected_reason || expected_reason
55
- metadata = @expected_metadata || {}
56
-
57
- result.skipped? &&
58
- result.interrupted? &&
59
- result.executed? &&
60
- (reason.nil? || result.metadata[:reason] == reason) &&
61
- (metadata.empty? || metadata.all? { |k, v| result.metadata[k] == v })
62
- end
63
-
64
- failure_message do |result|
65
- messages = []
66
- messages << "expected result to be skipped, but was #{result.status}" unless result.skipped?
67
- messages << "expected result to be interrupted, but was #{result.state}" unless result.interrupted?
68
- messages << "expected result to be executed, but was not" unless result.executed?
69
-
70
- reason = @expected_reason || expected_reason
71
- messages << "expected skip reason to be '#{reason}', but was '#{result.metadata[:reason]}'" if reason && result.metadata[:reason] != reason
72
-
73
- if @expected_metadata&.any?
74
- mismatches = @expected_metadata.filter_map do |k, v|
75
- "#{k}: expected #{v}, got #{result.metadata[k]}" if result.metadata[k] != v
76
- end
77
- messages.concat(mismatches)
78
- end
79
-
80
- messages.join(", ")
81
- end
82
-
83
- failure_message_when_negated do |_result|
84
- "expected result not to be skipped, but it was"
85
- end
86
-
87
- description do
88
- desc = "be a skipped task"
89
- reason = @expected_reason || expected_reason
90
- desc += " with reason '#{reason}'" if reason
91
- desc += " with metadata #{@expected_metadata}" if @expected_metadata&.any?
92
- desc
93
- end
94
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matchers for asserting task result states.
4
- #
5
- # This file dynamically generates RSpec matchers for each execution state defined
6
- # in CMDx::Result::STATES. These matchers check the current execution state of a
7
- # task result, which represents where the task is in its lifecycle from
8
- # initialization through completion or interruption.
9
- #
10
- # The following matchers are automatically generated:
11
- # - `be_initialized` - Task has been created but not yet started
12
- # - `be_executing` - Task is currently running its logic
13
- # - `be_complete` - Task has successfully finished execution
14
- # - `be_interrupted` - Task execution was halted due to failure or skip
15
- #
16
- # @return [Boolean] true if the result matches the expected state
17
- #
18
- # @example Testing initialized state
19
- # result = MyTask.new.result
20
- # expect(result).to be_initialized
21
- #
22
- # @example Testing executing state
23
- # result = MyTask.call(data: "processing")
24
- # expect(result).to be_executing # During execution
25
- #
26
- # @example Testing complete state
27
- # result = SuccessfulTask.call(data: "valid")
28
- # expect(result).to be_complete
29
- #
30
- # @example Testing interrupted state
31
- # result = FailedTask.call(data: "invalid")
32
- # expect(result).to be_interrupted
33
- #
34
- # @example Negative assertion
35
- # result = SuccessfulTask.call(data: "valid")
36
- # expect(result).not_to be_initialized
37
- #
38
- # @example Using with other matchers
39
- # result = ProcessDataTask.call(data: invalid_data)
40
- # expect(result).to be_interrupted.and be_failed
41
- CMDx::Result::STATES.each do |state|
42
- RSpec::Matchers.define :"be_#{state}" do
43
- match do |result|
44
- result.public_send(:"#{state}?")
45
- end
46
-
47
- failure_message do |result|
48
- "expected result to be #{state}, but was #{result.state}"
49
- end
50
-
51
- failure_message_when_negated do |_result|
52
- "expected result not to be #{state}, but it was"
53
- end
54
-
55
- description do
56
- "be #{state}"
57
- end
58
- end
59
- end
@@ -1,57 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matchers for asserting task result statuses.
4
- #
5
- # This file dynamically generates RSpec matchers for each execution status defined
6
- # in CMDx::Result::STATUSES. These matchers check the outcome of task logic execution,
7
- # which represents what happened when the task's business logic ran (success, skip, or failure).
8
- #
9
- # The following matchers are automatically generated:
10
- # - `be_success` - Task completed successfully without errors
11
- # - `be_skipped` - Task was intentionally skipped due to conditions
12
- # - `be_failed` - Task failed due to errors or validation issues
13
- #
14
- # @return [Boolean] true if the result matches the expected status
15
- #
16
- # @example Testing success status
17
- # result = ProcessDataTask.call(data: "valid")
18
- # expect(result).to be_success
19
- #
20
- # @example Testing skipped status
21
- # result = SendEmailTask.call(user: inactive_user)
22
- # expect(result).to be_skipped
23
- #
24
- # @example Testing failed status
25
- # result = ValidateUserTask.call(user_id: nil)
26
- # expect(result).to be_failed
27
- #
28
- # @example Negative assertion
29
- # result = SuccessfulTask.call(data: "valid")
30
- # expect(result).not_to be_failed
31
- #
32
- # @example Using with state matchers
33
- # result = ProcessPaymentTask.call(amount: -100)
34
- # expect(result).to be_failed.and be_interrupted
35
- #
36
- # @example Testing good vs bad outcomes
37
- # result = BackupTask.call(force: false)
38
- # expect(result).to be_skipped # Skipped is still a "good" outcome
39
- CMDx::Result::STATUSES.each do |status|
40
- RSpec::Matchers.define :"be_#{status}" do
41
- match do |result|
42
- result.public_send(:"#{status}?")
43
- end
44
-
45
- failure_message do |result|
46
- "expected result to be #{status}, but was #{result.status}"
47
- end
48
-
49
- failure_message_when_negated do |_result|
50
- "expected result not to be #{status}, but it was"
51
- end
52
-
53
- description do
54
- "be #{status}"
55
- end
56
- end
57
- end
@@ -1,87 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matcher for asserting that a task result has completed successfully.
4
- #
5
- # This matcher checks if a CMDx::Result object represents a fully successful task
6
- # execution, which means the task completed without errors and reached the end of
7
- # its lifecycle. A result is considered a successful task when it has "success" status,
8
- # "complete" state, and has been executed. Optionally validates expected context values.
9
- #
10
- # @param expected_context [Hash] optional hash of expected context key-value pairs
11
- #
12
- # @return [Boolean] true if the result is successful, complete, executed, and matches expected context
13
- #
14
- # @example Basic usage with successful task
15
- # result = ProcessOrderTask.call(order_id: 123)
16
- # expect(result).to be_successful_task
17
- #
18
- # @example Checking successful task with context validation
19
- # result = CalculateTotalTask.call(items: [item1, item2])
20
- # expect(result).to be_successful_task(total: 150.00, tax: 12.50)
21
- #
22
- # @example Validating multiple context attributes
23
- # result = UserRegistrationTask.call(email: "user@example.com")
24
- # expect(result).to be_successful_task(
25
- # user_id: 42,
26
- # email_sent: true,
27
- # activation_token: be_present
28
- # )
29
- #
30
- # @example Negative assertion
31
- # result = FailedValidationTask.call(data: "invalid")
32
- # expect(result).not_to be_successful_task
33
- #
34
- # @example Combining with other matchers
35
- # result = ProcessPaymentTask.call(amount: 100)
36
- # expect(result).to be_successful_task.and have_runtime
37
- #
38
- # @example Testing context without specific values
39
- # result = DataProcessingTask.call(data: dataset)
40
- # expect(result).to be_successful_task({}) # Just check success without context
41
- RSpec::Matchers.define :be_successful_task do |expected_context = {}|
42
- match do |result|
43
- result.success? &&
44
- result.complete? &&
45
- result.executed? &&
46
- (expected_context.empty? || context_matches?(result, expected_context))
47
- end
48
-
49
- failure_message do |result|
50
- messages = []
51
- messages << "expected result to be successful, but was #{result.status}" unless result.success?
52
- messages << "expected result to be complete, but was #{result.state}" unless result.complete?
53
- messages << "expected result to be executed, but was not" unless result.executed?
54
-
55
- unless expected_context.empty?
56
- mismatches = context_mismatches(result, expected_context)
57
- messages << "expected context to match #{expected_context}, but #{mismatches}" if mismatches.any?
58
- end
59
-
60
- messages.join(", ")
61
- end
62
-
63
- failure_message_when_negated do |_result|
64
- "expected result not to be successful, but it was"
65
- end
66
-
67
- description do
68
- desc = "be a successful task"
69
- desc += " with context #{expected_context}" unless expected_context.empty?
70
- desc
71
- end
72
-
73
- private
74
-
75
- def context_matches?(result, expected_context)
76
- expected_context.all? do |key, value|
77
- result.context.public_send(key) == value
78
- end
79
- end
80
-
81
- def context_mismatches(result, expected_context)
82
- expected_context.filter_map do |key, expected_value|
83
- actual_value = result.context.public_send(key)
84
- "#{key}: expected #{expected_value}, got #{actual_value}" if actual_value != expected_value
85
- end
86
- end
87
- end
@@ -1,51 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matcher for asserting that a task result has a bad outcome.
4
- #
5
- # This matcher checks if a CMDx::Result object represents a non-successful outcome,
6
- # which includes both failed and skipped results. A result has a bad outcome when
7
- # its status is anything other than "success" (i.e., either "failed" or "skipped").
8
- # This is useful for testing error handling and conditional logic paths.
9
- #
10
- # @return [Boolean] true if the result has a bad outcome (failed or skipped)
11
- #
12
- # @example Testing failed task outcome
13
- # result = ValidateDataTask.call(data: "invalid")
14
- # expect(result).to have_bad_outcome
15
- #
16
- # @example Testing skipped task outcome
17
- # result = ProcessQueueTask.call(queue: empty_queue)
18
- # expect(result).to have_bad_outcome
19
- #
20
- # @example Testing error handling paths
21
- # result = ProcessPaymentTask.call(amount: -100)
22
- # expect(result).to have_bad_outcome.and be_failed
23
- #
24
- # @example Negative assertion for successful tasks
25
- # result = SuccessfulTask.call(data: "valid")
26
- # expect(result).not_to have_bad_outcome
27
- #
28
- # @example Using in conditional test logic
29
- # result = ConditionalTask.call(condition: false)
30
- # if result.bad?
31
- # expect(result).to have_bad_outcome
32
- # end
33
- #
34
- # @example Opposite of good outcome
35
- # result = SkippedTask.call(reason: "not_needed")
36
- # expect(result).to have_bad_outcome.and not_to have_good_outcome
37
- RSpec::Matchers.define :have_bad_outcome do
38
- match(&:bad?)
39
-
40
- failure_message do |result|
41
- "expected result to have bad outcome (not success), but was #{result.status}"
42
- end
43
-
44
- failure_message_when_negated do |result|
45
- "expected result not to have bad outcome, but it did (status: #{result.status})"
46
- end
47
-
48
- description do
49
- "have bad outcome"
50
- end
51
- end
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matcher for asserting that a task result has caused its own failure.
4
- #
5
- # This matcher checks if a CMDx::Result object represents a failure that originated
6
- # within the task itself, as opposed to a failure that was thrown from or received
7
- # from another task. A result is considered to have caused failure when it's both
8
- # failed and the failure was generated by the task's own logic rather than propagated
9
- # from elsewhere in the chain.
10
- #
11
- # @return [Boolean] true if the result is failed and caused the failure itself
12
- #
13
- # @example Testing task that fails due to validation
14
- # result = ValidateUserTask.call(email: "invalid-email")
15
- # expect(result).to have_caused_failure
16
- #
17
- # @example Testing task that fails due to business logic
18
- # result = ProcessPaymentTask.call(amount: -100)
19
- # expect(result).to have_caused_failure
20
- #
21
- # @example Distinguishing from thrown failures
22
- # result = TaskThatThrowsFailure.call(data: "invalid")
23
- # expect(result).to have_caused_failure # This task caused its own failure
24
- # expect(result).not_to have_thrown_failure
25
- #
26
- # @example Testing in workflow context
27
- # workflow_result = MyWorkflow.call(data: "invalid")
28
- # failing_task = workflow_result.chain.find(&:failed?)
29
- # expect(failing_task).to have_caused_failure
30
- #
31
- # @example Negative assertion
32
- # result = SuccessfulTask.call(data: "valid")
33
- # expect(result).not_to have_caused_failure
34
- #
35
- # @example Testing error handling origin
36
- # result = DatabaseTask.call(connection: nil)
37
- # expect(result).to have_caused_failure.and be_failed
38
- RSpec::Matchers.define :have_caused_failure do
39
- match do |result|
40
- result.failed? && result.caused_failure?
41
- end
42
-
43
- failure_message do |result|
44
- if result.failed?
45
- "expected result to have caused failure, but it threw/received a failure instead"
46
- else
47
- "expected result to have caused failure, but it was not failed (status: #{result.status})"
48
- end
49
- end
50
-
51
- failure_message_when_negated do |_result|
52
- "expected result not to have caused failure, but it did"
53
- end
54
-
55
- description do
56
- "have caused failure"
57
- end
58
- end