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
data/docs/testing.md DELETED
@@ -1,553 +0,0 @@
1
- # Testing
2
-
3
- CMDx provides a comprehensive suite of custom RSpec matchers designed for expressive, maintainable testing of tasks, results, and business logic workflows.
4
-
5
- ## Table of Contents
6
-
7
- - [TLDR](#tldr)
8
- - [External Project Setup](#external-project-setup)
9
- - [Matcher Organization](#matcher-organization)
10
- - [Result Matchers](#result-matchers)
11
- - [Primary Outcome Matchers](#primary-outcome-matchers)
12
- - [State and Status Matchers](#state-and-status-matchers)
13
- - [Execution and Outcome Matchers](#execution-and-outcome-matchers)
14
- - [Metadata and Context Matchers](#metadata-and-context-matchers)
15
- - [Failure Chain Matchers](#failure-chain-matchers)
16
- - [Task Matchers](#task-matchers)
17
- - [Structure and Lifecycle Matchers](#structure-and-lifecycle-matchers)
18
- - [Parameter Testing Matchers](#parameter-testing-matchers)
19
- - [Callback and Middleware Matchers](#callback-and-middleware-matchers)
20
- - [Configuration Matchers](#configuration-matchers)
21
- - [Composable Testing](#composable-testing)
22
- - [Error Handling](#error-handling)
23
- - [Best Practices](#best-practices)
24
-
25
- ## TLDR
26
-
27
- ```ruby
28
- # Setup - require in spec helper
29
- require "cmdx/rspec/matchers"
30
-
31
- # Result outcome matchers
32
- expect(result).to be_successful_task(user_id: 123)
33
- expect(result).to be_failed_task("validation_error").with_metadata(field: "email")
34
- expect(result).to be_skipped_task.with_reason("already_processed")
35
-
36
- # Task structure matchers
37
- expect(MyTask).to be_well_formed_task
38
- expect(MyTask).to have_parameter(:email).that_is_required.with_type(:string)
39
- expect(MyTask).to have_callback(:before_execution)
40
- ```
41
-
42
- ## External Project Setup
43
-
44
- To use CMDx's custom matchers in an external RSpec-based project, update your `spec/spec_helper.rb` or `spec/rails_helper.rb`:
45
-
46
- ```ruby
47
- require "cmdx/rspec/matchers"
48
- ```
49
-
50
- ## Matcher Organization
51
-
52
- CMDx matchers are organized into two primary categories with comprehensive YARD documentation:
53
-
54
- | Category | Purpose | Matcher Count |
55
- |----------|---------|---------------|
56
- | **Result Matchers** | Task execution outcomes and side effects | 17 matchers |
57
- | **Task Matchers** | Task behavior, validation, and lifecycle | 6 matchers |
58
-
59
- > [!NOTE]
60
- > All matchers include complete parameter descriptions, multiple usage examples, return value specifications, negation examples, and version information.
61
-
62
- ## Result Matchers
63
-
64
- ### Primary Outcome Matchers
65
-
66
- These composite matchers validate complete task execution scenarios with single assertions:
67
-
68
- #### Successful Task Validation
69
-
70
- ```ruby
71
- # Basic successful task validation
72
- expect(result).to be_successful_task
73
-
74
- # Successful task with context validation
75
- expect(result).to be_successful_task(user_id: 123, processed: true)
76
-
77
- # With RSpec matchers for flexible context validation
78
- expect(result).to be_successful_task(
79
- user_id: be_a(Integer),
80
- processed_at: be_a(Time),
81
- email: match(/@/)
82
- )
83
- ```
84
-
85
- **What it validates:**
86
- - Result has success status
87
- - Result is in complete state
88
- - Result was executed
89
- - Optional context attributes match expected values
90
-
91
- #### Failed Task Validation
92
-
93
- ```ruby
94
- # Basic failed task validation
95
- expect(result).to be_failed_task
96
-
97
- # Failed task with specific reason
98
- expect(result).to be_failed_task("validation_failed")
99
-
100
- # Using with_reason chain
101
- expect(result).to be_failed_task.with_reason("invalid_data")
102
-
103
- # Combined reason and metadata validation
104
- expect(result).to be_failed_task("validation_error")
105
- .with_metadata(field: "email", rule: "format", retryable: false)
106
- ```
107
-
108
- **What it validates:**
109
- - Result has failed status
110
- - Result is in interrupted state
111
- - Result was executed
112
- - Optional reason and metadata match
113
-
114
- #### Skipped Task Validation
115
-
116
- ```ruby
117
- # Basic skipped task validation
118
- expect(result).to be_skipped_task
119
-
120
- # Skipped task with specific reason
121
- expect(result).to be_skipped_task("already_processed")
122
-
123
- # Using with_reason chain
124
- expect(result).to be_skipped_task.with_reason("order_already_processed")
125
-
126
- # Combined reason and metadata validation
127
- expect(result).to be_skipped_task("data_unchanged")
128
- .with_metadata(last_sync: be_a(Time), changes: 0)
129
- ```
130
-
131
- **What it validates:**
132
- - Result has skipped status
133
- - Result is in interrupted state
134
- - Result was executed
135
- - Optional reason and metadata match
136
-
137
- ### State and Status Matchers
138
-
139
- Individual validation matchers for granular testing:
140
-
141
- #### Execution State Matchers
142
-
143
- ```ruby
144
- # Auto-generated from CMDx::Result::STATES
145
- expect(result).to be_initialized
146
- expect(result).to be_executing
147
- expect(result).to be_complete
148
- expect(result).to be_interrupted
149
- ```
150
-
151
- > [!IMPORTANT]
152
- > State matchers are dynamically generated from the CMDx framework's state definitions, ensuring they stay in sync with framework updates.
153
-
154
- #### Execution Status Matchers
155
-
156
- ```ruby
157
- # Auto-generated from CMDx::Result::STATUSES
158
- expect(result).to be_success
159
- expect(result).to be_skipped
160
- expect(result).to be_failed
161
- ```
162
-
163
- ### Execution and Outcome Matchers
164
-
165
- ```ruby
166
- # Execution validation
167
- expect(result).to be_executed
168
-
169
- # Outcome classification
170
- expect(result).to have_good_outcome # success OR skipped
171
- expect(result).to have_bad_outcome # failed (not success)
172
- ```
173
-
174
- ### Metadata and Context Matchers
175
-
176
- #### Metadata Validation
177
-
178
- ```ruby
179
- # Basic metadata validation
180
- expect(result).to have_metadata(reason: "validation_failed", code: 422)
181
-
182
- # With RSpec matchers for flexible assertions
183
- expect(result).to have_metadata(
184
- reason: "validation_failed",
185
- started_at: be_a(Time),
186
- duration: be > 0,
187
- error_code: match(/^ERR/)
188
- )
189
-
190
- # Chainable metadata inclusion
191
- expect(result).to have_metadata(reason: "error")
192
- .including(retry_count: 3, retryable: false)
193
-
194
- # Empty metadata validation
195
- expect(result).to have_empty_metadata
196
- ```
197
-
198
- #### Runtime Validation
199
-
200
- ```ruby
201
- # Basic runtime presence validation
202
- expect(result).to have_runtime
203
-
204
- # Runtime with specific value
205
- expect(result).to have_runtime(0.5)
206
-
207
- # Runtime with RSpec matchers
208
- expect(result).to have_runtime(be > 0)
209
- expect(result).to have_runtime(be_within(0.1).of(0.5))
210
- expect(result).to have_runtime(be < 2.0) # Performance constraint
211
- ```
212
-
213
- #### Context Side Effects
214
-
215
- ```ruby
216
- # Context validation with direct values
217
- expect(result).to have_context(processed: true, user_id: 123)
218
-
219
- # With RSpec matchers for flexible validation
220
- expect(result).to have_context(
221
- user: have_attributes(id: 123, name: "John"),
222
- processed_at: be_a(Time),
223
- notifications: contain_exactly("email", "sms")
224
- )
225
-
226
- # Context preservation testing
227
- expect(result).to have_preserved_context(
228
- user_id: 123,
229
- original_data: "important"
230
- )
231
- ```
232
-
233
- > [!TIP]
234
- > Use `have_context` for testing side effects and new values, and `have_preserved_context` for verifying that certain values remained unchanged throughout execution.
235
-
236
- #### Chain Validation
237
-
238
- ```ruby
239
- # Chain position validation
240
- expect(result).to have_chain_index(0) # First task in chain
241
- expect(result).to have_chain_index(2) # Third task in chain
242
-
243
- # Workflow structure testing
244
- workflow_result = MyWorkflow.call(data: "test")
245
- first_task = workflow_result.chain.first
246
- expect(first_task).to have_chain_index(0)
247
- ```
248
-
249
- ### Failure Chain Matchers
250
-
251
- Test CMDx's failure propagation patterns:
252
-
253
- #### Original Failure Validation
254
-
255
- ```ruby
256
- # Test that result represents an original failure (not propagated)
257
- expect(result).to have_caused_failure
258
-
259
- # Distinguished from thrown failures
260
- result = ValidateDataTask.call(data: "invalid")
261
- expect(result).to have_caused_failure
262
- expect(result).not_to have_thrown_failure
263
- ```
264
-
265
- #### Failure Propagation Validation
266
-
267
- ```ruby
268
- # Basic thrown failure validation
269
- expect(result).to have_thrown_failure
270
-
271
- # Thrown failure with specific original result
272
- workflow_result = MultiStepWorkflow.call(data: "problematic")
273
- original_failure = workflow_result.chain.find(&:caused_failure?)
274
- throwing_task = workflow_result.chain.find(&:threw_failure?)
275
- expect(throwing_task).to have_thrown_failure(original_failure)
276
- ```
277
-
278
- #### Received Failure Validation
279
-
280
- ```ruby
281
- # Test that result received a thrown failure
282
- expect(result).to have_received_thrown_failure
283
-
284
- # Testing downstream task failure handling
285
- workflow_result = ProcessingWorkflow.call(data: "invalid")
286
- receiving_task = workflow_result.chain.find { |r| r.thrown_failure? }
287
- expect(receiving_task).to have_received_thrown_failure
288
- ```
289
-
290
- ## Task Matchers
291
-
292
- ### Structure and Lifecycle Matchers
293
-
294
- #### Well-Formed Task Validation
295
-
296
- ```ruby
297
- # Test task meets all structural requirements
298
- expect(MyTask).to be_well_formed_task
299
-
300
- # For dynamically created tasks
301
- task_class = Class.new(CMDx::Task) { def call; end }
302
- expect(task_class).to be_well_formed_task
303
- ```
304
-
305
- **What it validates:**
306
- - Inherits from CMDx::Task
307
- - Implements required call method
308
- - Has properly initialized parameter, callback, and middleware registries
309
-
310
- ### Parameter Testing Matchers
311
-
312
- #### Parameter Presence and Configuration
313
-
314
- ```ruby
315
- # Basic parameter presence
316
- expect(CreateUserTask).to have_parameter(:email)
317
-
318
- # Parameter requirement validation
319
- expect(ProcessOrderTask).to have_parameter(:order_id).that_is_required
320
- expect(ConfigTask).to have_parameter(:timeout).that_is_optional
321
-
322
- # Type coercion validation
323
- expect(CreateUserTask).to have_parameter(:age).with_type(:integer)
324
- expect(UpdateSettingsTask).to have_parameter(:enabled).with_coercion(:boolean)
325
-
326
- # Default value testing
327
- expect(ProcessTask).to have_parameter(:timeout).with_default(30)
328
- expect(EmailTask).to have_parameter(:priority).with_default("normal")
329
-
330
- # Validation rules testing
331
- expect(UserTask).to have_parameter(:email)
332
- .with_validations(:format, :presence)
333
- .that_is_required
334
- .with_type(:string)
335
- ```
336
-
337
- > [!WARNING]
338
- > Parameter validation matchers test the configuration of parameters, not their runtime behavior. Use result matchers to test parameter validation failures during execution.
339
-
340
- ### Callback and Middleware Matchers
341
-
342
- #### Callback Registration Testing
343
-
344
- ```ruby
345
- # Basic callback registration
346
- expect(ValidatedTask).to have_callback(:before_validation)
347
- expect(NotifiedTask).to have_callback(:on_success)
348
- expect(CleanupTask).to have_callback(:after_execution)
349
-
350
- # Callback with specific callable (if supported by implementation)
351
- expect(CustomTask).to have_callback(:on_failure).with_callable(my_proc)
352
- ```
353
-
354
- #### Callback Execution Testing
355
-
356
- ```ruby
357
- # Test callbacks execute during task lifecycle
358
- expect(task_instance).to have_executed_callbacks(:before_validation, :after_validation)
359
- expect(failed_task_instance).to have_executed_callbacks(:before_execution, :on_failure)
360
- ```
361
-
362
- > [!NOTE]
363
- > Callback execution testing requires task instances rather than task classes and may require mocking internal callback mechanisms for comprehensive validation.
364
-
365
- #### Middleware Registration Testing
366
-
367
- ```ruby
368
- # Test middleware registration
369
- expect(AuthenticatedTask).to have_middleware(AuthenticationMiddleware)
370
- expect(LoggedTask).to have_middleware(LoggingMiddleware)
371
- expect(TimedTask).to have_middleware(TimeoutMiddleware)
372
- ```
373
-
374
- ### Configuration Matchers
375
-
376
- #### Task Setting Validation
377
-
378
- ```ruby
379
- # Test setting presence
380
- expect(ConfiguredTask).to have_cmd_setting(:timeout)
381
- expect(CustomTask).to have_cmd_setting(:priority)
382
-
383
- # Test setting with specific value
384
- expect(TimedTask).to have_cmd_setting(:timeout, 30)
385
- expect(PriorityTask).to have_cmd_setting(:priority, "high")
386
- ```
387
-
388
- ## Composable Testing
389
-
390
- Following RSpec best practices, CMDx matchers are designed for composition:
391
-
392
- ### Chaining with `.and`
393
-
394
- ```ruby
395
- # Chain multiple result expectations
396
- expect(result).to be_successful_task(user_id: 123)
397
- .and have_context(processed_at: be_a(Time))
398
- .and have_runtime(be > 0)
399
- .and have_chain_index(0)
400
-
401
- # Chain task validation expectations
402
- expect(TaskClass).to be_well_formed_task
403
- .and have_parameter(:user_id).that_is_required
404
- .and have_callback(:before_execution)
405
- ```
406
-
407
- ### Integration with Built-in RSpec Matchers
408
-
409
- ```ruby
410
- # Combine with built-in matchers
411
- expect(result).to be_failed_task
412
- .with_metadata(error_code: match(/^ERR/), retryable: be_falsy)
413
- .and have_caused_failure
414
-
415
- # Complex context validation
416
- expect(result).to be_successful_task
417
- .and have_context(
418
- user: have_attributes(id: be_a(Integer), email: match(/@/)),
419
- timestamps: all(be_a(Time)),
420
- notifications: contain_exactly("email", "sms")
421
- )
422
- ```
423
-
424
- ## Error Handling
425
-
426
- ### Invalid Matcher Usage
427
-
428
- Common error scenarios and their resolution:
429
-
430
- ```ruby
431
- # Parameter not found
432
- expect(SimpleTask).to have_parameter(:nonexistent)
433
- # → "expected task to have parameter nonexistent, but had parameters: []"
434
-
435
- # Middleware not registered
436
- expect(SimpleTask).to have_middleware(ComplexMiddleware)
437
- # → "expected task to have middleware ComplexMiddleware, but had []"
438
-
439
- # Context mismatch
440
- expect(result).to have_context(user_id: 999)
441
- # → "expected context to include {user_id: 999}, but user_id: expected 999, got 123"
442
- ```
443
-
444
- ### Test Failures and Debugging
445
-
446
- ```ruby
447
- # Use descriptive failure messages for debugging
448
- result = ProcessDataTask.call(data: "invalid")
449
- expect(result).to be_successful_task
450
- # → "expected result to be successful, but was failed,
451
- # expected result to be complete, but was interrupted"
452
-
453
- # Combine matchers for comprehensive validation
454
- expect(result).to be_failed_task("validation_error")
455
- .with_metadata(field: "email", rule: "format")
456
- # → Clear indication of what specifically failed
457
- ```
458
-
459
- ## Best Practices
460
-
461
- ### 1. Use Composite Matchers When Possible
462
-
463
- **Preferred:**
464
- ```ruby
465
- expect(result).to be_successful_task(user_id: 123)
466
- ```
467
-
468
- **Instead of:**
469
- ```ruby
470
- expect(result).to be_success
471
- expect(result).to be_complete
472
- expect(result).to be_executed
473
- expect(result.context.user_id).to eq(123)
474
- ```
475
-
476
- ### 2. Combine Granular and Composite Testing
477
-
478
- Use composite matchers for primary assertions, granular matchers for specific edge cases:
479
-
480
- ```ruby
481
- # Primary assertion
482
- expect(result).to be_successful_task
483
-
484
- # Specific validations
485
- expect(result).to have_runtime(be < 1.0) # Performance requirement
486
- expect(result).to have_chain_index(0) # Position validation
487
- ```
488
-
489
- ### 3. Leverage RSpec Matcher Integration
490
-
491
- CMDx matchers work seamlessly with built-in RSpec matchers:
492
-
493
- ```ruby
494
- expect(result).to have_metadata(
495
- timestamp: be_within(1.second).of(Time.current),
496
- errors: be_empty,
497
- count: be_between(1, 100)
498
- )
499
- ```
500
-
501
- ### 4. Write Descriptive Test Names
502
-
503
- Matcher names are designed to read naturally in test descriptions:
504
-
505
- ```ruby
506
- describe ProcessOrderTask do
507
- it "has required parameters configured" do
508
- expect(described_class).to have_parameter(:order_id).that_is_required
509
- end
510
-
511
- it "registers necessary callbacks" do
512
- expect(described_class).to have_callback(:before_execution)
513
- end
514
-
515
- context "when processing succeeds" do
516
- it "returns successful result with order data" do
517
- result = described_class.call(order_id: 123)
518
-
519
- expect(result).to be_successful_task(order_id: 123)
520
- .and have_context(order: be_present, processed_at: be_a(Time))
521
- .and have_runtime(be_positive)
522
- end
523
- end
524
-
525
- context "when validation fails" do
526
- it "returns failed result with error details" do
527
- result = described_class.call(order_id: nil)
528
-
529
- expect(result).to be_failed_task("validation_failed")
530
- .with_metadata(field: "order_id", rule: "presence")
531
- end
532
- end
533
- end
534
- ```
535
-
536
- ### 5. Test Both Happy and Error Paths
537
-
538
- ```ruby
539
- # Happy path
540
- expect(result).to be_successful_task
541
- .and have_good_outcome
542
- .and have_empty_metadata
543
-
544
- # Error path
545
- expect(error_result).to be_failed_task
546
- .and have_bad_outcome
547
- .and have_metadata(error_code: be_present)
548
- ```
549
-
550
- ---
551
-
552
- - **Prev:** [Internationalization (i18n)](internationalization.md)
553
- - **Next:** [Deprecation](deprecation.md)
data/lib/cmdx/callback.rb DELETED
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CMDx
4
- # Base class for implementing callback functionality in task processing.
5
- #
6
- # Callbacks are executed at specific points in the task lifecycle, such as
7
- # before execution, after success, or on failure. All callback implementations
8
- # must inherit from this class and implement the abstract call method.
9
- class Callback
10
-
11
- # Executes a callback by creating a new instance and calling it.
12
- #
13
- # @param task [CMDx::Task] the task instance triggering the callback
14
- # @param type [Symbol] the callback type being executed (e.g., :before_execution, :on_success, :on_failure)
15
- #
16
- # @return [void]
17
- #
18
- # @raise [UndefinedCallError] when the callback subclass doesn't implement call
19
- #
20
- # @example Execute a callback for task success
21
- # LogSuccessCallback.call(task, :on_success)
22
- #
23
- # @example Execute a callback before task execution
24
- # SetupCallback.call(task, :before_execution)
25
- def self.call(task, type)
26
- new.call(task, type)
27
- end
28
-
29
- # Abstract method that must be implemented by callback subclasses.
30
- #
31
- # This method contains the actual callback logic to be executed at the
32
- # specified point in the task lifecycle. Subclasses must override this method
33
- # to provide their specific callback implementation.
34
- #
35
- # @param task [CMDx::Task] the task instance triggering the callback
36
- # @param type [Symbol] the callback type being executed
37
- #
38
- # @return [void]
39
- #
40
- # @raise [UndefinedCallError] always raised in the base class
41
- #
42
- # @example Implement in a subclass
43
- # class NotificationCallback < CMDx::Callback
44
- # def call(task, type)
45
- # puts "Task #{task.class.name} triggered #{type} callback"
46
- # end
47
- # end
48
- def call(task, type) # rubocop:disable Lint/UnusedMethodArgument
49
- raise UndefinedCallError, "call method not defined in #{self.class.name}"
50
- end
51
-
52
- end
53
- end
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CMDx
4
- # Provides formatted inspection and display functionality for chain execution results.
5
- #
6
- # ChainInspector creates human-readable string representations of execution chains,
7
- # displaying chain metadata, individual task results, and execution summary information
8
- # in a formatted layout. The inspector processes chain data to provide comprehensive
9
- # debugging and monitoring output for task execution sequences.
10
- module ChainInspector
11
-
12
- FOOTER_KEYS = %i[
13
- state status outcome runtime
14
- ].freeze
15
-
16
- module_function
17
-
18
- # Formats a chain into a human-readable inspection string with headers, results, and summary.
19
- #
20
- # Creates a comprehensive string representation of the execution chain including
21
- # a header with the chain ID, formatted individual task results, and a footer
22
- # summary with key execution metadata. The output uses visual separators for
23
- # clear section delineation and consistent formatting.
24
- #
25
- # @param chain [Chain] the execution chain to format and inspect
26
- #
27
- # @return [String] formatted multi-line string representation of the chain execution
28
- #
29
- # @example Format a simple chain
30
- # chain = MyWorkflow.call(user_id: 123)
31
- # output = ChainInspector.call(chain.chain)
32
- # puts output
33
- # # =>
34
- # # chain: abc123-def456-789
35
- # # ===============================
36
- # #
37
- # # {:task=>"MyTask", :state=>"complete", :status=>"success"}
38
- # # {:task=>"OtherTask", :state=>"complete", :status=>"success"}
39
- # #
40
- # # ===============================
41
- # # state: complete | status: success | outcome: good | runtime: 0.025
42
- def call(chain)
43
- header = "\nchain: #{chain.id}"
44
- footer = FOOTER_KEYS.map { |key| "#{key}: #{chain.send(key)}" }.join(" | ")
45
- spacer = "=" * [header.size, footer.size].max
46
-
47
- chain
48
- .results
49
- .map { |r| r.to_h.except(:chain_id).pretty_inspect }
50
- .unshift(header, "#{spacer}\n")
51
- .push(spacer, "#{footer}\n\n")
52
- .join("\n")
53
- end
54
-
55
- end
56
- end