cmdx 1.1.2 → 1.5.1

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 (192) 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 +55 -1
  8. data/.irbrc +6 -0
  9. data/.rubocop.yml +29 -18
  10. data/CHANGELOG.md +11 -132
  11. data/LLM.md +3317 -0
  12. data/README.md +68 -44
  13. data/docs/attributes/coercions.md +162 -0
  14. data/docs/attributes/defaults.md +90 -0
  15. data/docs/attributes/definitions.md +281 -0
  16. data/docs/attributes/naming.md +78 -0
  17. data/docs/attributes/validations.md +309 -0
  18. data/docs/basics/chain.md +56 -249
  19. data/docs/basics/context.md +56 -289
  20. data/docs/basics/execution.md +114 -0
  21. data/docs/basics/setup.md +37 -334
  22. data/docs/callbacks.md +89 -467
  23. data/docs/deprecation.md +91 -174
  24. data/docs/getting_started.md +212 -202
  25. data/docs/internationalization.md +11 -647
  26. data/docs/interruptions/exceptions.md +23 -198
  27. data/docs/interruptions/faults.md +71 -151
  28. data/docs/interruptions/halt.md +109 -186
  29. data/docs/logging.md +44 -256
  30. data/docs/middlewares.md +113 -426
  31. data/docs/outcomes/result.md +81 -228
  32. data/docs/outcomes/states.md +33 -221
  33. data/docs/outcomes/statuses.md +21 -311
  34. data/docs/tips_and_tricks.md +120 -70
  35. data/docs/workflows.md +99 -283
  36. data/lib/cmdx/.DS_Store +0 -0
  37. data/lib/cmdx/attribute.rb +229 -0
  38. data/lib/cmdx/attribute_registry.rb +94 -0
  39. data/lib/cmdx/attribute_value.rb +193 -0
  40. data/lib/cmdx/callback_registry.rb +69 -77
  41. data/lib/cmdx/chain.rb +56 -73
  42. data/lib/cmdx/coercion_registry.rb +52 -68
  43. data/lib/cmdx/coercions/array.rb +19 -18
  44. data/lib/cmdx/coercions/big_decimal.rb +20 -24
  45. data/lib/cmdx/coercions/boolean.rb +26 -25
  46. data/lib/cmdx/coercions/complex.rb +21 -22
  47. data/lib/cmdx/coercions/date.rb +25 -23
  48. data/lib/cmdx/coercions/date_time.rb +24 -25
  49. data/lib/cmdx/coercions/float.rb +25 -22
  50. data/lib/cmdx/coercions/hash.rb +31 -32
  51. data/lib/cmdx/coercions/integer.rb +30 -24
  52. data/lib/cmdx/coercions/rational.rb +29 -24
  53. data/lib/cmdx/coercions/string.rb +19 -22
  54. data/lib/cmdx/coercions/symbol.rb +37 -0
  55. data/lib/cmdx/coercions/time.rb +26 -25
  56. data/lib/cmdx/configuration.rb +49 -108
  57. data/lib/cmdx/context.rb +222 -44
  58. data/lib/cmdx/deprecator.rb +61 -0
  59. data/lib/cmdx/errors.rb +42 -252
  60. data/lib/cmdx/exceptions.rb +39 -0
  61. data/lib/cmdx/faults.rb +78 -39
  62. data/lib/cmdx/freezer.rb +51 -0
  63. data/lib/cmdx/identifier.rb +30 -0
  64. data/lib/cmdx/locale.rb +52 -0
  65. data/lib/cmdx/log_formatters/json.rb +21 -22
  66. data/lib/cmdx/log_formatters/key_value.rb +20 -22
  67. data/lib/cmdx/log_formatters/line.rb +15 -22
  68. data/lib/cmdx/log_formatters/logstash.rb +22 -23
  69. data/lib/cmdx/log_formatters/raw.rb +16 -22
  70. data/lib/cmdx/middleware_registry.rb +70 -74
  71. data/lib/cmdx/middlewares/correlate.rb +90 -54
  72. data/lib/cmdx/middlewares/runtime.rb +58 -0
  73. data/lib/cmdx/middlewares/timeout.rb +48 -68
  74. data/lib/cmdx/railtie.rb +12 -45
  75. data/lib/cmdx/result.rb +229 -314
  76. data/lib/cmdx/task.rb +194 -366
  77. data/lib/cmdx/utils/call.rb +49 -0
  78. data/lib/cmdx/utils/condition.rb +71 -0
  79. data/lib/cmdx/utils/format.rb +61 -0
  80. data/lib/cmdx/validator_registry.rb +63 -72
  81. data/lib/cmdx/validators/exclusion.rb +38 -67
  82. data/lib/cmdx/validators/format.rb +48 -49
  83. data/lib/cmdx/validators/inclusion.rb +43 -74
  84. data/lib/cmdx/validators/length.rb +101 -162
  85. data/lib/cmdx/validators/numeric.rb +95 -170
  86. data/lib/cmdx/validators/presence.rb +37 -50
  87. data/lib/cmdx/version.rb +1 -1
  88. data/lib/cmdx/worker.rb +178 -0
  89. data/lib/cmdx/workflow.rb +85 -81
  90. data/lib/cmdx.rb +19 -13
  91. data/lib/generators/cmdx/install_generator.rb +14 -13
  92. data/lib/generators/cmdx/task_generator.rb +25 -50
  93. data/lib/generators/cmdx/templates/install.rb +11 -46
  94. data/lib/generators/cmdx/templates/task.rb.tt +3 -2
  95. data/lib/locales/en.yml +18 -4
  96. data/src/cmdx-logo.png +0 -0
  97. metadata +32 -116
  98. data/docs/ai_prompts.md +0 -393
  99. data/docs/basics/call.md +0 -317
  100. data/docs/configuration.md +0 -344
  101. data/docs/parameters/coercions.md +0 -396
  102. data/docs/parameters/defaults.md +0 -335
  103. data/docs/parameters/definitions.md +0 -446
  104. data/docs/parameters/namespacing.md +0 -378
  105. data/docs/parameters/validations.md +0 -405
  106. data/docs/testing.md +0 -553
  107. data/lib/cmdx/callback.rb +0 -53
  108. data/lib/cmdx/chain_inspector.rb +0 -56
  109. data/lib/cmdx/chain_serializer.rb +0 -63
  110. data/lib/cmdx/coercion.rb +0 -57
  111. data/lib/cmdx/coercions/virtual.rb +0 -29
  112. data/lib/cmdx/core_ext/hash.rb +0 -83
  113. data/lib/cmdx/core_ext/module.rb +0 -98
  114. data/lib/cmdx/core_ext/object.rb +0 -125
  115. data/lib/cmdx/correlator.rb +0 -122
  116. data/lib/cmdx/error.rb +0 -67
  117. data/lib/cmdx/fault.rb +0 -140
  118. data/lib/cmdx/immutator.rb +0 -52
  119. data/lib/cmdx/lazy_struct.rb +0 -246
  120. data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
  121. data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
  122. data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
  123. data/lib/cmdx/logger.rb +0 -49
  124. data/lib/cmdx/logger_ansi.rb +0 -68
  125. data/lib/cmdx/logger_serializer.rb +0 -116
  126. data/lib/cmdx/middleware.rb +0 -70
  127. data/lib/cmdx/parameter.rb +0 -312
  128. data/lib/cmdx/parameter_evaluator.rb +0 -231
  129. data/lib/cmdx/parameter_inspector.rb +0 -66
  130. data/lib/cmdx/parameter_registry.rb +0 -106
  131. data/lib/cmdx/parameter_serializer.rb +0 -59
  132. data/lib/cmdx/result_ansi.rb +0 -71
  133. data/lib/cmdx/result_inspector.rb +0 -71
  134. data/lib/cmdx/result_logger.rb +0 -59
  135. data/lib/cmdx/result_serializer.rb +0 -104
  136. data/lib/cmdx/rspec/matchers.rb +0 -28
  137. data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
  138. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
  139. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
  140. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
  141. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
  142. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
  143. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
  144. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
  145. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
  146. data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
  147. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
  148. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
  149. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
  150. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
  151. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
  152. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
  153. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
  154. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
  155. data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
  156. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
  157. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
  158. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
  159. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
  160. data/lib/cmdx/task_deprecator.rb +0 -58
  161. data/lib/cmdx/task_processor.rb +0 -246
  162. data/lib/cmdx/task_serializer.rb +0 -57
  163. data/lib/cmdx/utils/ansi_color.rb +0 -73
  164. data/lib/cmdx/utils/log_timestamp.rb +0 -36
  165. data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
  166. data/lib/cmdx/utils/name_affix.rb +0 -52
  167. data/lib/cmdx/validator.rb +0 -57
  168. data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
  169. data/lib/generators/cmdx/workflow_generator.rb +0 -84
  170. data/lib/locales/ar.yml +0 -35
  171. data/lib/locales/cs.yml +0 -35
  172. data/lib/locales/da.yml +0 -35
  173. data/lib/locales/de.yml +0 -35
  174. data/lib/locales/el.yml +0 -35
  175. data/lib/locales/es.yml +0 -35
  176. data/lib/locales/fi.yml +0 -35
  177. data/lib/locales/fr.yml +0 -35
  178. data/lib/locales/he.yml +0 -35
  179. data/lib/locales/hi.yml +0 -35
  180. data/lib/locales/it.yml +0 -35
  181. data/lib/locales/ja.yml +0 -35
  182. data/lib/locales/ko.yml +0 -35
  183. data/lib/locales/nl.yml +0 -35
  184. data/lib/locales/no.yml +0 -35
  185. data/lib/locales/pl.yml +0 -35
  186. data/lib/locales/pt.yml +0 -35
  187. data/lib/locales/ru.yml +0 -35
  188. data/lib/locales/sv.yml +0 -35
  189. data/lib/locales/th.yml +0 -35
  190. data/lib/locales/tr.yml +0 -35
  191. data/lib/locales/vi.yml +0 -35
  192. data/lib/locales/zh.yml +0 -35
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matcher for asserting that a task result has a specific chain index.
4
- #
5
- # This matcher checks if a CMDx::Result object is positioned at the expected index
6
- # within its execution chain. The chain index represents the zero-based position of
7
- # the task in the workflow execution order, which is useful for testing workflow
8
- # structure, execution order, and identifying specific tasks within complex chains.
9
- #
10
- # @param expected_index [Integer] the expected zero-based index position in the chain
11
- #
12
- # @return [Boolean] true if the result's chain index matches the expected index
13
- #
14
- # @example Testing first task in workflow
15
- # workflow_result = MyWorkflow.call(data: "test")
16
- # first_task = workflow_result.chain.first
17
- # expect(first_task).to have_chain_index(0)
18
- #
19
- # @example Testing specific task position
20
- # workflow_result = ProcessingWorkflow.call(items: [1, 2, 3])
21
- # validation_task = workflow_result.chain[2]
22
- # expect(validation_task).to have_chain_index(2)
23
- #
24
- # @example Testing failed task position
25
- # workflow_result = FailingWorkflow.call(data: "invalid")
26
- # failed_task = workflow_result.chain.find(&:failed?)
27
- # expect(failed_task).to have_chain_index(1)
28
- #
29
- # @example Testing last task in chain
30
- # workflow_result = CompletedWorkflow.call(data: "valid")
31
- # last_task = workflow_result.chain.last
32
- # expect(last_task).to have_chain_index(workflow_result.chain.length - 1)
33
- #
34
- # @example Negative assertion
35
- # workflow_result = MyWorkflow.call(data: "test")
36
- # middle_task = workflow_result.chain[1]
37
- # expect(middle_task).not_to have_chain_index(0)
38
- #
39
- # @example Testing workflow interruption point
40
- # workflow_result = InterruptedWorkflow.call(data: "invalid")
41
- # interrupting_task = workflow_result.chain.find(&:interrupted?)
42
- # expect(interrupting_task).to have_chain_index(3)
43
- RSpec::Matchers.define :have_chain_index do |expected_index|
44
- match do |result|
45
- result.index == expected_index
46
- end
47
-
48
- failure_message do |result|
49
- "expected result to have chain index #{expected_index}, but was #{result.index}"
50
- end
51
-
52
- failure_message_when_negated do |_result|
53
- "expected result not to have chain index #{expected_index}, but it did"
54
- end
55
-
56
- description do
57
- "have chain index #{expected_index}"
58
- end
59
- end
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matcher for asserting that a task result has specific context side effects.
4
- #
5
- # This matcher checks if a CMDx::Result object's context contains expected values
6
- # or side effects that were set during task execution. Tasks often modify the context
7
- # to store computed values, intermediate results, or other data that needs to be
8
- # passed between tasks in a workflow. This matcher supports both direct value
9
- # comparisons and RSpec matchers for flexible assertions.
10
- #
11
- # @param expected_effects [Hash] hash of expected context key-value pairs or matchers
12
- #
13
- # @return [Boolean] true if the context has all expected side effects
14
- #
15
- # @example Testing simple context values
16
- # result = CalculateTask.call(a: 10, b: 20)
17
- # expect(result).to have_context(sum: 30, product: 200)
18
- #
19
- # @example Using RSpec matchers for flexible assertions
20
- # result = ProcessUserTask.call(user_id: 123)
21
- # expect(result).to have_context(
22
- # user: be_a(User),
23
- # created_at: be_a(Time),
24
- # email: match(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
25
- # )
26
- #
27
- # @example Testing computed values
28
- # result = AnalyzeDataTask.call(data: dataset)
29
- # expect(result).to have_context(
30
- # average: be_within(0.1).of(15.5),
31
- # count: be > 100,
32
- # processed: be_truthy
33
- # )
34
- #
35
- # @example Testing workflow context passing
36
- # workflow_result = DataProcessingWorkflow.call(input: "raw_data")
37
- # expect(workflow_result).to have_context(
38
- # raw_data: "raw_data",
39
- # processed_data: be_present,
40
- # validation_errors: be_empty
41
- # )
42
- #
43
- # @example Negative assertion
44
- # result = SimpleTask.call(data: "test")
45
- # expect(result).not_to have_context(unexpected_key: "value")
46
- #
47
- # @example Testing side effects in failed tasks
48
- # result = ValidateTask.call(data: "invalid")
49
- # expect(result).to have_context(
50
- # validation_errors: include("Data is invalid"),
51
- # attempted_at: be_a(Time)
52
- # )
53
- RSpec::Matchers.define :have_context do |expected_effects|
54
- match do |result|
55
- expected_effects.all? do |key, expected_value|
56
- actual_value = result.context.public_send(key)
57
- if expected_value.respond_to?(:matches?)
58
- expected_value.matches?(actual_value)
59
- else
60
- actual_value == expected_value
61
- end
62
- end
63
- end
64
-
65
- failure_message do |result|
66
- mismatches = expected_effects.filter_map do |key, expected_value|
67
- actual_value = result.context.public_send(key)
68
- match_result = if expected_value.respond_to?(:matches?)
69
- expected_value.matches?(actual_value)
70
- else
71
- actual_value == expected_value
72
- end
73
-
74
- "#{key}: expected #{expected_value}, got #{actual_value}" unless match_result
75
- end
76
- "expected context to have side effects #{expected_effects}, but #{mismatches.join(', ')}"
77
- end
78
-
79
- failure_message_when_negated do |_result|
80
- "expected context not to have side effects #{expected_effects}, but it did"
81
- end
82
-
83
- description do
84
- "have side effects #{expected_effects}"
85
- end
86
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matcher for asserting that a task result has no metadata.
4
- #
5
- # This matcher checks if a CMDx::Result object's metadata hash is empty.
6
- # Metadata is typically used to store additional information about task execution
7
- # such as failure reasons, timing details, error contexts, or other diagnostic data.
8
- # Testing for empty metadata is useful when verifying that successful tasks execute
9
- # cleanly without generating unnecessary metadata, or when ensuring default states.
10
- #
11
- # @return [Boolean] true if the result's metadata hash is empty
12
- #
13
- # @example Testing successful task with no metadata
14
- # result = SimpleTask.call(data: "valid")
15
- # expect(result).to have_empty_metadata
16
- #
17
- # @example Testing clean task execution
18
- # result = CalculateTask.call(a: 10, b: 20)
19
- # expect(result).to be_success.and have_empty_metadata
20
- #
21
- # @example Testing default result state
22
- # result = MyTask.new.result
23
- # expect(result).to have_empty_metadata
24
- #
25
- # @example Negative assertion - expecting metadata to be present
26
- # result = ValidationTask.call(data: "invalid")
27
- # expect(result).not_to have_empty_metadata
28
- #
29
- # @example Comparing with tasks that set metadata
30
- # successful_result = CleanTask.call(data: "valid")
31
- # failed_result = FailingTask.call(data: "invalid")
32
- # expect(successful_result).to have_empty_metadata
33
- # expect(failed_result).not_to have_empty_metadata
34
- #
35
- # @example Testing metadata cleanup
36
- # result = ResetTask.call(clear_metadata: true)
37
- # expect(result).to have_empty_metadata
38
- RSpec::Matchers.define :have_empty_metadata do
39
- match do |result|
40
- result.metadata.empty?
41
- end
42
-
43
- failure_message do |result|
44
- "expected metadata to be empty, but was #{result.metadata}"
45
- end
46
-
47
- failure_message_when_negated do |_result|
48
- "expected metadata not to be empty, but it was"
49
- end
50
-
51
- description do
52
- "have empty metadata"
53
- end
54
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matcher for asserting that a task result has a good outcome.
4
- #
5
- # This matcher checks if a CMDx::Result object represents a successful completion,
6
- # which includes both successful and skipped results. A result has a good outcome when
7
- # its status is either "success" or "skipped" (i.e., anything other than "failed").
8
- # This is useful for testing that tasks complete without errors, even if they were
9
- # skipped due to conditions, as skipped tasks are still considered successful outcomes.
10
- #
11
- # @return [Boolean] true if the result has a good outcome (success or skipped)
12
- #
13
- # @example Testing successful task outcome
14
- # result = ProcessDataTask.call(data: "valid")
15
- # expect(result).to have_good_outcome
16
- #
17
- # @example Testing skipped task outcome (still good)
18
- # result = BackupTask.call(force: false)
19
- # expect(result).to have_good_outcome # Skipped is still good
20
- #
21
- # @example Testing non-error completion paths
22
- # result = ConditionalTask.call(condition: false)
23
- # expect(result).to have_good_outcome # Either success or skip is good
24
- #
25
- # @example Negative assertion for failed tasks
26
- # result = ValidationTask.call(data: "invalid")
27
- # expect(result).not_to have_good_outcome
28
- #
29
- # @example Distinguishing from bad outcomes
30
- # successful_result = CleanTask.call(data: "valid")
31
- # failed_result = BrokenTask.call(data: "invalid")
32
- # expect(successful_result).to have_good_outcome
33
- # expect(failed_result).to have_bad_outcome
34
- #
35
- # @example Testing workflow completion
36
- # workflow_result = ProcessingWorkflow.call(data: "test")
37
- # expect(workflow_result).to have_good_outcome.and be_complete
38
- RSpec::Matchers.define :have_good_outcome do
39
- match(&:good?)
40
-
41
- failure_message do |result|
42
- "expected result to have good outcome (success or skipped), but was #{result.status}"
43
- end
44
-
45
- failure_message_when_negated do |result|
46
- "expected result not to have good outcome, but it did (status: #{result.status})"
47
- end
48
-
49
- description do
50
- "have good outcome"
51
- end
52
- end
@@ -1,114 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matcher for asserting that a task result has specific metadata.
4
- #
5
- # This matcher checks if a CMDx::Result object's metadata hash contains expected
6
- # key-value pairs. Metadata is typically used to store additional information about
7
- # task execution such as failure reasons, timing details, error contexts, or other
8
- # diagnostic data. The matcher supports both direct value comparisons and RSpec
9
- # matchers for flexible assertions, and can be chained with `including` for
10
- # additional metadata expectations.
11
- #
12
- # @param expected_metadata [Hash] optional hash of expected metadata key-value pairs
13
- #
14
- # @return [Boolean] true if the result's metadata contains all expected pairs
15
- #
16
- # @example Testing basic metadata
17
- # result = FailedTask.call(data: "invalid")
18
- # expect(result).to have_metadata(reason: "validation_failed", code: 422)
19
- #
20
- # @example Using RSpec matchers for flexible assertions
21
- # result = ProcessingTask.call(data: "test")
22
- # expect(result).to have_metadata(
23
- # started_at: be_a(Time),
24
- # duration: be > 0,
25
- # user_id: be_present
26
- # )
27
- #
28
- # @example Using the including chain for additional metadata
29
- # result = ValidationTask.call(data: "invalid")
30
- # expect(result).to have_metadata(reason: "validation_failed")
31
- # .including(field: "email", rule: "format")
32
- #
33
- # @example Testing failure metadata
34
- # result = DatabaseTask.call(connection: nil)
35
- # expect(result).to have_metadata(
36
- # error_class: "ConnectionError",
37
- # error_message: include("connection failed"),
38
- # retry_count: 3
39
- # )
40
- #
41
- # @example Testing skip metadata
42
- # result = BackupTask.call(force: false)
43
- # expect(result).to have_metadata(
44
- # reason: "backup_not_needed",
45
- # last_backup: be_a(Time),
46
- # next_backup: be_a(Time)
47
- # )
48
- #
49
- # @example Negative assertion
50
- # result = CleanTask.call(data: "valid")
51
- # expect(result).not_to have_metadata(error_code: anything)
52
- #
53
- # @example Complex metadata validation
54
- # result = WorkflowTask.call(data: "complex")
55
- # expect(result).to have_metadata(
56
- # steps_completed: be >= 5,
57
- # total_steps: 10,
58
- # performance_data: be_a(Hash)
59
- # ).including(
60
- # memory_usage: be_within(10).of(100),
61
- # cpu_time: be_positive
62
- # )
63
- RSpec::Matchers.define :have_metadata do |expected_metadata = {}|
64
- match do |result|
65
- expected_metadata.all? do |key, value|
66
- actual_value = result.metadata[key]
67
- if value.respond_to?(:matches?)
68
- value.matches?(actual_value)
69
- else
70
- actual_value == value
71
- end
72
- end
73
- end
74
-
75
- chain :including do |metadata|
76
- @additional_metadata = metadata
77
- end
78
-
79
- match do |result|
80
- all_metadata = expected_metadata.merge(@additional_metadata || {})
81
- all_metadata.all? do |key, value|
82
- actual_value = result.metadata[key]
83
- if value.respond_to?(:matches?)
84
- value.matches?(actual_value)
85
- else
86
- actual_value == value
87
- end
88
- end
89
- end
90
-
91
- failure_message do |result|
92
- all_metadata = expected_metadata.merge(@additional_metadata || {})
93
- mismatches = all_metadata.filter_map do |key, expected_value|
94
- actual_value = result.metadata[key]
95
- match_result = if expected_value.respond_to?(:matches?)
96
- expected_value.matches?(actual_value)
97
- else
98
- actual_value == expected_value
99
- end
100
- "#{key}: expected #{expected_value}, got #{actual_value}" unless match_result
101
- end
102
- "expected metadata to include #{all_metadata}, but #{mismatches.join(', ')}"
103
- end
104
-
105
- failure_message_when_negated do |_result|
106
- all_metadata = expected_metadata.merge(@additional_metadata || {})
107
- "expected metadata not to include #{all_metadata}, but it did"
108
- end
109
-
110
- description do
111
- all_metadata = expected_metadata.merge(@additional_metadata || {})
112
- "have metadata #{all_metadata}"
113
- end
114
- end
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matcher for asserting that a task result has preserved specific context values.
4
- #
5
- # This matcher checks if a CMDx::Result object's context contains values that were
6
- # preserved from the original input or previous task execution. Unlike `have_context`
7
- # which tests for side effects and new values, this matcher specifically verifies
8
- # that certain context attributes retained their expected values throughout task
9
- # execution, ensuring data integrity and proper context passing between tasks.
10
- #
11
- # @param preserved_attributes [Hash] hash of expected preserved context key-value pairs
12
- #
13
- # @return [Boolean] true if the context has preserved all expected attributes
14
- #
15
- # @example Testing basic context preservation
16
- # result = ProcessDataTask.call(user_id: 123, data: "input")
17
- # expect(result).to have_preserved_context(user_id: 123, data: "input")
18
- #
19
- # @example Testing workflow context preservation
20
- # workflow_result = UserWorkflow.call(user_id: 456, email: "user@example.com")
21
- # expect(workflow_result).to have_preserved_context(
22
- # user_id: 456,
23
- # email: "user@example.com"
24
- # )
25
- #
26
- # @example Testing preservation through multiple tasks
27
- # result = MultiStepTask.call(original_data: "important", temp_data: "process")
28
- # expect(result).to have_preserved_context(original_data: "important")
29
- #
30
- # @example Testing that critical data survives failures
31
- # result = FailingTask.call(user_id: 789, critical_flag: true)
32
- # expect(result).to have_preserved_context(
33
- # user_id: 789,
34
- # critical_flag: true
35
- # )
36
- #
37
- # @example Negative assertion for modified context
38
- # result = TransformTask.call(data: "original")
39
- # expect(result).not_to have_preserved_context(data: "original")
40
- #
41
- # @example Testing partial preservation
42
- # result = SelectiveTask.call(keep_this: "value", change_this: "old")
43
- # expect(result).to have_preserved_context(keep_this: "value")
44
- RSpec::Matchers.define :have_preserved_context do |preserved_attributes|
45
- match do |result|
46
- preserved_attributes.all? do |key, expected_value|
47
- result.context.public_send(key) == expected_value
48
- end
49
- end
50
-
51
- failure_message do |result|
52
- mismatches = preserved_attributes.filter_map do |key, expected_value|
53
- actual_value = result.context.public_send(key)
54
- "#{key}: expected #{expected_value}, got #{actual_value}" if actual_value != expected_value
55
- end
56
- "expected context to preserve #{preserved_attributes}, but #{mismatches.join(', ')}"
57
- end
58
-
59
- failure_message_when_negated do |_result|
60
- "expected context not to preserve #{preserved_attributes}, but it did"
61
- end
62
-
63
- description do
64
- "preserve context #{preserved_attributes}"
65
- end
66
- end
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matcher for asserting that a task result has received a thrown failure.
4
- #
5
- # This matcher checks if a CMDx::Result object represents a failure that was
6
- # thrown from another task and received by this task. This is distinct from
7
- # failures that were caused by the task itself or thrown by the task to others.
8
- # A result has received a thrown failure when it's both failed and the failure
9
- # was propagated from elsewhere in the chain, making this useful for testing
10
- # error propagation and workflow failure handling.
11
- #
12
- # @return [Boolean] true if the result is failed and received a thrown failure
13
- #
14
- # @example Testing error propagation in workflows
15
- # workflow_result = ProcessingWorkflow.call(data: "invalid")
16
- # receiving_task = workflow_result.chain.find { |r| r.thrown_failure? }
17
- # expect(receiving_task).to have_received_thrown_failure
18
- #
19
- # @example Testing downstream task failure handling
20
- # result = CleanupTask.call(previous_task_failed: true)
21
- # expect(result).to have_received_thrown_failure
22
- #
23
- # @example Distinguishing failure types in chain
24
- # workflow_result = MultiStepWorkflow.call(data: "problematic")
25
- # original_failure = workflow_result.chain.find(&:caused_failure?)
26
- # received_failure = workflow_result.chain.find(&:thrown_failure?)
27
- # expect(original_failure).to have_caused_failure
28
- # expect(received_failure).to have_received_thrown_failure
29
- #
30
- # @example Testing error handling middleware
31
- # result = ErrorHandlingTask.call(upstream_error: error_obj)
32
- # expect(result).to have_received_thrown_failure
33
- #
34
- # @example Negative assertion for self-caused failures
35
- # result = ValidatingTask.call(data: "invalid")
36
- # expect(result).not_to have_received_thrown_failure
37
- #
38
- # @example Testing workflow interruption propagation
39
- # workflow_result = InterruptedWorkflow.call(data: "test")
40
- # interrupted_tasks = workflow_result.chain.select(&:thrown_failure?)
41
- # interrupted_tasks.each do |task|
42
- # expect(task).to have_received_thrown_failure
43
- # end
44
- RSpec::Matchers.define :have_received_thrown_failure do
45
- match do |result|
46
- result.failed? && result.thrown_failure?
47
- end
48
-
49
- failure_message do |result|
50
- if result.failed?
51
- "expected result to have received thrown failure, but it #{result.caused_failure? ? 'caused' : 'threw'} failure instead"
52
- else
53
- "expected result to have received thrown failure, but it was not failed (status: #{result.status})"
54
- end
55
- end
56
-
57
- failure_message_when_negated do |_result|
58
- "expected result not to have received thrown failure, but it did"
59
- end
60
-
61
- description do
62
- "have received thrown failure"
63
- end
64
- end
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matcher for asserting that a task result has runtime information.
4
- #
5
- # This matcher checks if a CMDx::Result object has recorded runtime information
6
- # from task execution. Runtime represents the elapsed time taken to execute the
7
- # task, measured in seconds as a Float. The matcher can be used to verify that
8
- # runtime was captured, or to test that runtime meets specific expectations
9
- # using direct values or RSpec matchers for performance testing.
10
- #
11
- # @param expected_runtime [Float, RSpec::Matchers::BuiltIn::BaseMatcher, nil]
12
- # optional expected runtime value or matcher
13
- #
14
- # @return [Boolean] true if the result has runtime and optionally matches expected value
15
- #
16
- # @example Testing that runtime was captured
17
- # result = ProcessDataTask.call(data: "test")
18
- # expect(result).to have_runtime
19
- #
20
- # @example Testing specific runtime value
21
- # result = QuickTask.call(data: "simple")
22
- # expect(result).to have_runtime(0.1)
23
- #
24
- # @example Testing runtime with RSpec matchers
25
- # result = ProcessingTask.call(data: "complex")
26
- # expect(result).to have_runtime(be > 0.5)
27
- #
28
- # @example Testing runtime ranges
29
- # result = OptimizedTask.call(data: "test")
30
- # expect(result).to have_runtime(be_between(0.1, 1.0))
31
- #
32
- # @example Testing performance constraints
33
- # result = PerformanceCriticalTask.call(data: "large_dataset")
34
- # expect(result).to have_runtime(be < 2.0)
35
- #
36
- # @example Negative assertion for unexecuted tasks
37
- # result = UnexecutedTask.new.result
38
- # expect(result).not_to have_runtime
39
- #
40
- # @example Testing runtime precision
41
- # result = PreciseTask.call(data: "test")
42
- # expect(result).to have_runtime(be_within(0.01).of(0.25))
43
- RSpec::Matchers.define :have_runtime do |expected_runtime = nil|
44
- match do |result|
45
- return false if result.runtime.nil?
46
- return true if expected_runtime.nil?
47
-
48
- if expected_runtime.respond_to?(:matches?)
49
- expected_runtime.matches?(result.runtime)
50
- else
51
- result.runtime == expected_runtime
52
- end
53
- end
54
-
55
- failure_message do |result|
56
- if result.runtime.nil?
57
- "expected result to have runtime, but it was nil"
58
- elsif expected_runtime
59
- "expected result runtime to #{expected_runtime}, but was #{result.runtime}"
60
- end
61
- end
62
-
63
- failure_message_when_negated do |result|
64
- if expected_runtime
65
- "expected result runtime not to #{expected_runtime}, but it was #{result.runtime}"
66
- else
67
- "expected result not to have runtime, but it was #{result.runtime}"
68
- end
69
- end
70
-
71
- description do
72
- if expected_runtime
73
- "have runtime #{expected_runtime}"
74
- else
75
- "have runtime"
76
- end
77
- end
78
- end
@@ -1,76 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # RSpec matcher for asserting that a task result has thrown a failure.
4
- #
5
- # This matcher checks if a CMDx::Result object represents a failure that was
6
- # thrown to another task. This is distinct from failures that were caused by
7
- # the task itself or received from other tasks. A result has thrown a failure
8
- # when it's both failed and actively passed the failure to another task in
9
- # the chain. Optionally verifies that the thrown failure came from a specific
10
- # original result, useful for testing complex failure propagation scenarios.
11
- #
12
- # @param expected_original_result [CMDx::Result, nil] optional original result that was thrown
13
- #
14
- # @return [Boolean] true if the result is failed, threw a failure, and optionally matches expected original
15
- #
16
- # @example Testing basic failure throwing
17
- # workflow_result = ProcessingWorkflow.call(data: "invalid")
18
- # throwing_task = workflow_result.chain.find(&:threw_failure?)
19
- # expect(throwing_task).to have_thrown_failure
20
- #
21
- # @example Testing failure propagation with specific original
22
- # workflow_result = MultiStepWorkflow.call(data: "problematic")
23
- # original_failure = workflow_result.chain.find(&:caused_failure?)
24
- # throwing_task = workflow_result.chain.find(&:threw_failure?)
25
- # expect(throwing_task).to have_thrown_failure(original_failure)
26
- #
27
- # @example Testing middleware failure handling
28
- # result = ErrorHandlingMiddleware.call(upstream_failure: failure_obj)
29
- # expect(result).to have_thrown_failure
30
- #
31
- # @example Distinguishing failure types in chain
32
- # workflow_result = FailingWorkflow.call(data: "invalid")
33
- # caused_task = workflow_result.chain.find(&:caused_failure?)
34
- # threw_task = workflow_result.chain.find(&:threw_failure?)
35
- # received_task = workflow_result.chain.find(&:thrown_failure?)
36
- # expect(caused_task).to have_caused_failure
37
- # expect(threw_task).to have_thrown_failure
38
- # expect(received_task).to have_received_thrown_failure
39
- #
40
- # @example Negative assertion for self-caused failures
41
- # result = ValidatingTask.call(data: "invalid")
42
- # expect(result).not_to have_thrown_failure
43
- #
44
- # @example Testing workflow interruption propagation
45
- # workflow_result = InterruptedWorkflow.call(data: "test")
46
- # propagating_tasks = workflow_result.chain.select(&:threw_failure?)
47
- # propagating_tasks.each do |task|
48
- # expect(task).to have_thrown_failure
49
- # end
50
- RSpec::Matchers.define :have_thrown_failure do |expected_original_result = nil|
51
- match do |result|
52
- result.failed? &&
53
- result.threw_failure? &&
54
- (expected_original_result.nil? || result.threw_failure == expected_original_result)
55
- end
56
-
57
- failure_message do |result|
58
- messages = []
59
- messages << "expected result to be failed, but was #{result.status}" unless result.failed?
60
- messages << "expected result to have thrown failure, but it #{result.caused_failure? ? 'caused' : 'received'} failure instead" unless result.threw_failure?
61
-
62
- messages << "expected to throw failure from #{expected_original_result}, but threw from #{result.threw_failure}" if expected_original_result && result.threw_failure != expected_original_result
63
-
64
- messages.join(", ")
65
- end
66
-
67
- failure_message_when_negated do |_result|
68
- "expected result not to have thrown failure, but it did"
69
- end
70
-
71
- description do
72
- desc = "have thrown failure"
73
- desc += " from #{expected_original_result}" if expected_original_result
74
- desc
75
- end
76
- end