cmdx-rspec 1.4.0 → 2.0.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +274 -173
  4. data/lib/cmdx/rspec/helpers.rb +244 -276
  5. data/lib/cmdx/rspec/matchers/be_complete.rb +20 -0
  6. data/lib/cmdx/rspec/matchers/be_deprecated.rb +14 -52
  7. data/lib/cmdx/rspec/matchers/be_interrupted.rb +20 -0
  8. data/lib/cmdx/rspec/matchers/be_ko.rb +19 -0
  9. data/lib/cmdx/rspec/matchers/be_ok.rb +19 -0
  10. data/lib/cmdx/rspec/matchers/be_successful.rb +8 -20
  11. data/lib/cmdx/rspec/matchers/have_been_retried.rb +41 -0
  12. data/lib/cmdx/rspec/matchers/have_been_rolled_back.rb +23 -0
  13. data/lib/cmdx/rspec/matchers/have_callback.rb +46 -0
  14. data/lib/cmdx/rspec/matchers/have_chain_root.rb +30 -0
  15. data/lib/cmdx/rspec/matchers/have_chain_size.rb +29 -0
  16. data/lib/cmdx/rspec/matchers/have_duration.rb +30 -0
  17. data/lib/cmdx/rspec/matchers/have_empty_context.rb +4 -16
  18. data/lib/cmdx/rspec/matchers/have_empty_metadata.rb +3 -9
  19. data/lib/cmdx/rspec/matchers/have_errors_on.rb +50 -0
  20. data/lib/cmdx/rspec/matchers/have_failed.rb +7 -24
  21. data/lib/cmdx/rspec/matchers/have_input.rb +41 -0
  22. data/lib/cmdx/rspec/matchers/have_matching_context.rb +5 -20
  23. data/lib/cmdx/rspec/matchers/have_matching_metadata.rb +5 -17
  24. data/lib/cmdx/rspec/matchers/have_middleware.rb +29 -0
  25. data/lib/cmdx/rspec/matchers/have_no_errors.rb +30 -0
  26. data/lib/cmdx/rspec/matchers/have_output.rb +46 -0
  27. data/lib/cmdx/rspec/matchers/have_pipeline_tasks.rb +27 -0
  28. data/lib/cmdx/rspec/matchers/have_retry_on.rb +36 -0
  29. data/lib/cmdx/rspec/matchers/have_skipped.rb +7 -24
  30. data/lib/cmdx/rspec/matchers/have_tag.rb +30 -0
  31. data/lib/cmdx/rspec/matchers/raise_cmdx_fault.rb +74 -0
  32. data/lib/cmdx/rspec/version.rb +2 -1
  33. data/lib/cmdx/rspec.rb +22 -3
  34. metadata +24 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0616efb98c57af6ccff186eac481bf366c42feb22c3c9da20e9060c79a261ab5
4
- data.tar.gz: abaf7805901a616df62e2d3825862f221981d4e9a7424ee442ddd18203e7074d
3
+ metadata.gz: 9d99809d55db6542a630f55457df90bacbf7598a9969a8a1e54cf27630362030
4
+ data.tar.gz: 7fe0770bb36f09002b9b45cbf01751b0aab0efac7529be7c3c65d5a751399dbb
5
5
  SHA512:
6
- metadata.gz: 986ae1097c9f5b084f8ec3b65b9c803f3443bc77fbd21299712d8fceb1983c05e61ea7ca7619053e48fd8d6057002259fb315c1a8b16457de79d6bfd874c6c84
7
- data.tar.gz: 00e76f0d58ce7b4334adccc0e39dec3b83f22b4399ffe42511f747bbc0ab87eedbb3edf0357a7e5a0438d727103b2563ea92253d92696c36d66f0c04ec866c9b
6
+ metadata.gz: 3fa228ad4eec439aea347ed0c12746f66252e0712dfba58ddd246ffde63102dcedc5c2b2c5816441f9d5eec9a3261937ad19ea22c31b197c74b7e20f5daa8cdc
7
+ data.tar.gz: 5818b4c8a34414a5834abb7e8665480d24b9c0e63ae1d5bebc2f1fa37c5156b9b377ec26cef78251a24c1ce2bd98b4d8f439031aa4ba8efb52a9b8888c903c91
data/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.0.0] - Unreleased
10
+
11
+ ### Added
12
+ - Support for CMDx v2
13
+
14
+ ### Changes
15
+ - Updated helpers and matchers for new internals
16
+
9
17
  ## [1.4.0] - 2026-04-09
10
18
 
11
19
  ### Added
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ---
6
6
 
7
- Collection of RSpec matchers for the CMDx framework.
7
+ Collection of RSpec matchers and helpers for the CMDx framework.
8
8
 
9
9
  [Changelog](./CHANGELOG.md) · [Report Bug](https://github.com/drexed/cmdx-rspec/issues) · [Request Feature](https://github.com/drexed/cmdx-rspec/issues)
10
10
 
@@ -15,304 +15,405 @@
15
15
 
16
16
  # CMDx::RSpec
17
17
 
18
- Collection of RSpec matchers for [CMDx](https://github.com/drexed/cmdx).
18
+ RSpec matchers and helpers for asserting [CMDx](https://github.com/drexed/cmdx) task and workflow behavior — result state, errors, faults, callbacks, retries, chains, and more — without invoking real `work` blocks.
19
+
20
+ ## Requirements
21
+
22
+ - Ruby: MRI 3.3+ or a compatible JRuby/TruffleRuby release
23
+ - CMDx 2.0+
19
24
 
20
25
  ## Installation
21
26
 
22
- Add this line to your application's Gemfile:
27
+ ```sh
28
+ gem install cmdx-rspec
29
+ # - or -
30
+ bundle add cmdx-rspec --group test
31
+ ```
32
+
33
+ Require the library in `spec_helper.rb` (or equivalent):
23
34
 
24
35
  ```ruby
25
- gem 'cmdx-rspec'
36
+ require "cmdx/rspec"
26
37
  ```
27
38
 
28
- And then execute:
39
+ This loads every matcher under `RSpec::Matchers` and exposes the helpers under `CMDx::RSpec::Helpers`. See [Helpers](#helpers) for how to mix the helpers into your example groups.
29
40
 
30
- $ bundle
31
-
32
- Or install it yourself as:
41
+ ## Matchers
33
42
 
34
- $ gem install cmdx-rspec
43
+ All result-oriented matchers raise `ArgumentError` when given a subject that isn't a `CMDx::Result`. Class-oriented matchers accept either the Task class or an instance.
35
44
 
36
- ## Matchers
45
+ ### Result state & status
37
46
 
38
- ### be_successful
47
+ #### `be_successful`
39
48
 
40
- Asserts that a CMDx task result indicates successful execution.
49
+ Asserts a `CMDx::Result` completed with `state: complete` and `status: success`. Extra keyword args are forwarded to a `result.to_h` inclusion check, so any field can be constrained inline.
41
50
 
42
51
  ```ruby
43
- it "returns success" do
44
- result = SomeTask.execute
52
+ expect(SomeTask.execute).to be_successful
53
+ expect(SomeTask.execute).to be_successful(metadata: { id: 1 })
54
+ ```
45
55
 
46
- expect(result).to be_successful
47
- end
56
+ #### `have_skipped`
57
+
58
+ Asserts a result was skipped (`state: interrupted`, `status: skipped`). Extra keyword args constrain other `result.to_h` fields.
59
+
60
+ ```ruby
61
+ expect(result).to have_skipped
62
+ expect(result).to have_skipped(
63
+ reason: "out of stock",
64
+ cause: be_a(CMDx::SkipFault)
65
+ )
48
66
  ```
49
67
 
50
- ### have_skipped
68
+ #### `have_failed`
51
69
 
52
- Asserts that a CMDx task result indicates the task was skipped during execution.
70
+ Asserts a result failed (`state: interrupted`, `status: failed`). Extra keyword args constrain other `result.to_h` fields.
53
71
 
54
72
  ```ruby
55
- it "returns skipped" do
56
- result = SomeTask.execute
73
+ expect(result).to have_failed
74
+ expect(result).to have_failed(
75
+ reason: "boom",
76
+ cause: be_a(NoMethodError)
77
+ )
78
+ ```
57
79
 
58
- # Default result
59
- expect(result).to have_skipped
80
+ #### `be_ok` / `be_ko`
60
81
 
61
- # Custom result
62
- expect(result).to have_skipped(
63
- reason: "Skipping for a custom reason",
64
- cause: be_a(CMDx::SkipFault)
65
- # Other members of `result.to_h`...
66
- )
67
- end
82
+ `be_ok` passes when the result is success or skipped (anything but failed). `be_ko` is its inverse.
83
+
84
+ ```ruby
85
+ expect(result).to be_ok
86
+ expect(result).to be_ko
68
87
  ```
69
88
 
70
- ### have_failed
89
+ #### `be_complete` / `be_interrupted`
71
90
 
72
- Asserts that a CMDx task result indicates execution failure.
91
+ State-only assertions. `be_complete` passes when `state == :complete`; `be_interrupted` when `state == :interrupted`.
73
92
 
74
93
  ```ruby
75
- it "returns failure" do
76
- result = SomeTask.execute
94
+ expect(result).to be_complete
95
+ expect(result).to be_interrupted
96
+ ```
77
97
 
78
- # Default result
79
- expect(result).to have_failed
98
+ ### Result data
80
99
 
81
- # Custom result
82
- expect(result).to have_failed(
83
- reason: "Failed for a custom reason",
84
- cause: be_a(NoMethodError)
85
- # Other members of `result.to_h`...
86
- )
87
- end
100
+ #### `have_empty_metadata` / `have_matching_metadata`
101
+
102
+ `have_empty_metadata` requires `metadata` to be empty. `have_matching_metadata` performs a partial-hash inclusion match (and delegates to `have_empty_metadata` when called with no args).
103
+
104
+ ```ruby
105
+ expect(result).to have_empty_metadata
106
+ expect(result).to have_matching_metadata(status_code: 500)
88
107
  ```
89
108
 
90
- ### have_empty_metadata
109
+ #### `have_empty_context` / `have_matching_context`
91
110
 
92
- Asserts that a CMDx task result has no metadata.
111
+ Same pattern for the result's context. Both accept a `Hash`, `CMDx::Context`, or `CMDx::Result` (the result's `.context` is unwrapped automatically).
93
112
 
94
113
  ```ruby
95
- it "returns empty metadata" do
96
- result = SomeTask.execute
114
+ expect(result).to have_empty_context
115
+ expect(result).to have_matching_context(stored_id: 123)
116
+ ```
97
117
 
98
- expect(result).to have_empty_metadata
99
- end
118
+ ### Errors
119
+
120
+ #### `have_no_errors`
121
+
122
+ Passes when the subject's `errors` collection is empty. Accepts a `CMDx::Result`, `CMDx::Task` instance, or `CMDx::Errors`.
123
+
124
+ ```ruby
125
+ expect(result).to have_no_errors
100
126
  ```
101
127
 
102
- ### have_matching_metadata
128
+ #### `have_errors_on`
103
129
 
104
- Asserts that a CMDx task result contains specific metadata.
130
+ Asserts at least one error is present under `key`. Optional positional `messages` further constrain the matcher — all must be present.
105
131
 
106
132
  ```ruby
107
- it "returns matching metadata" do
108
- result = SomeTask.execute
133
+ expect(result).to have_errors_on(:email)
134
+ expect(result).to have_errors_on(:email, "is required")
135
+ expect(task).to have_errors_on(:email, "is required", "is invalid")
136
+ ```
109
137
 
110
- expect(result).to have_matching_metadata(status_code: 500)
111
- end
138
+ ### Execution metrics
139
+
140
+ #### `have_been_retried`
141
+
142
+ Passes when the result was retried at least once. Pass an integer to require an exact retry count.
143
+
144
+ ```ruby
145
+ expect(result).to have_been_retried
146
+ expect(result).to have_been_retried(3)
112
147
  ```
113
148
 
114
- ### have_empty_context
149
+ #### `have_been_rolled_back`
115
150
 
116
- Asserts that a CMDx task result has no context data.
151
+ Passes when a failing task ran its rollback hook.
117
152
 
118
153
  ```ruby
119
- it "returns empty context" do
120
- result = SomeTask.execute
154
+ expect(result).to have_been_rolled_back
155
+ ```
121
156
 
122
- expect(result).to have_empty_context
123
- end
157
+ #### `have_duration`
158
+
159
+ Asserts the result's duration (in milliseconds) falls within the supplied bounds. At least one of `:less_than` or `:greater_than` is required.
160
+
161
+ ```ruby
162
+ expect(result).to have_duration(less_than: 100)
163
+ expect(result).to have_duration(greater_than: 0.1, less_than: 50)
124
164
  ```
125
165
 
126
- ### have_matching_context
166
+ ### Faults
127
167
 
128
- Asserts that a CMDx task result contains specific context data.
168
+ #### `raise_cmdx_fault`
169
+
170
+ Block matcher that asserts a `CMDx::Fault` is raised. Optionally constrain by originating task class, reason, or underlying cause.
129
171
 
130
172
  ```ruby
131
- it "returns matching context" do
132
- result = SomeTask.execute
173
+ expect { SomeTask.execute! }.to raise_cmdx_fault
174
+ expect { SomeTask.execute! }.to raise_cmdx_fault(SomeTask)
175
+ expect { SomeTask.execute! }
176
+ .to raise_cmdx_fault(SomeTask)
177
+ .with_reason(/invalid/)
178
+ .with_cause(MyError)
179
+ ```
133
180
 
134
- expect(result).to have_matching_context(stored_result: 123)
135
- end
181
+ `with_reason` accepts a string (equality) or a Regexp; `with_cause` accepts a class (matches via `is_a?`) or a value (matched with `values_match?`).
182
+
183
+ ### Chains
184
+
185
+ #### `have_chain_root`
186
+
187
+ Passes when the chain's root task class matches (or is a subclass of) `task_class`. Accepts a `CMDx::Chain` or `CMDx::Result`.
188
+
189
+ ```ruby
190
+ expect(result).to have_chain_root(MyWorkflow)
136
191
  ```
137
192
 
138
- ### be_deprecated
193
+ #### `have_chain_size`
139
194
 
140
- Asserts that a CMDx task result indicates the task is deprecated.
195
+ Passes when the chain's size matches `expected`. Accepts a `CMDx::Chain` or `CMDx::Result`.
141
196
 
142
197
  ```ruby
143
- it "returns deprecated" do
144
- expect(SomeTask).to be_deprecated
145
- end
198
+ expect(result).to have_chain_size(3)
146
199
  ```
147
200
 
148
- ## Helpers
201
+ ### Task class declarations
202
+
203
+ These matchers introspect a Task class's configuration. They accept either the class or an instance.
149
204
 
150
- ### Including Helper Modules
205
+ #### `be_deprecated`
151
206
 
152
- Include the helper modules in your RSpec configuration or example groups:
207
+ Asserts a Task class is marked deprecated. Optionally constrain the deprecation behavior via a positional value or a chained convenience method.
153
208
 
154
209
  ```ruby
155
- RSpec.configure do |config|
156
- config.include CMDx::RSpec::Helpers
157
- end
210
+ expect(SomeTask).to be_deprecated
211
+ expect(SomeTask).to be_deprecated.with_warning # :warn
212
+ expect(SomeTask).to be_deprecated.with_logging # :log
213
+ expect(SomeTask).to be_deprecated.with_error # :error
214
+ expect(SomeTask).to be_deprecated.with_behavior(:custom)
158
215
  ```
159
216
 
160
- Or include them in specific example groups:
217
+ #### `have_input` / `have_output`
161
218
 
162
- ```ruby
163
- describe MyFeature do
164
- include CMDx::RSpec::Helpers
219
+ Asserts the class declares the given input/output. Keyword args are matched (partial) against the parameter's serialized `to_h`.
165
220
 
166
- # Your specs...
167
- end
221
+ ```ruby
222
+ expect(SomeTask).to have_input(:user_id)
223
+ expect(SomeTask).to have_input(:user_id, type: :integer, required: true)
224
+ expect(SomeTask).to have_output(:total, required: true)
168
225
  ```
169
226
 
170
- ### Stubs
227
+ #### `have_callback`
228
+
229
+ Asserts a callback is registered for `event`. Optional `callable` further constrains the match — by `==` for symbols/lambdas, or by `is_a?` when given a class.
230
+
231
+ ```ruby
232
+ expect(SomeTask).to have_callback(:before_execution)
233
+ expect(SomeTask).to have_callback(:before_execution, :authenticate!)
234
+ expect(SomeTask).to have_callback(:on_failed, AlertOnFailure)
235
+ ```
171
236
 
172
- Helper methods for stubbing CMDx command execution.
237
+ #### `have_middleware`
173
238
 
174
- #### Types
239
+ Asserts the class registered `middleware`. Class arguments match by `is_a?` or `==`; other values match by `==`.
175
240
 
176
241
  ```ruby
177
- it "stubs task executions by type" do
178
- # eg: SomeTask.execute
179
- stub_task_success(SomeTask)
180
- stub_task_skip(SomeTask)
181
- stub_task_fail(SomeTask)
242
+ expect(SomeTask).to have_middleware(LoggingMiddleware)
243
+ ```
182
244
 
183
- # eg: SomeTask.execute!
184
- stub_task_success!(SomeTask)
185
- stub_task_skip!(SomeTask)
186
- stub_task_fail!(SomeTask)
245
+ #### `have_retry_on`
187
246
 
188
- # Your specs...
189
- end
247
+ Asserts the class is configured to retry on `exception`. Keyword args check `CMDx::Retry` configuration values (`:limit`, `:delay`, `:max_delay`, `:jitter`).
190
248
 
191
- it "stubs task with arguments" do
192
- # eg: SomeTask.execute(some: "value")
193
- stub_task_success(SomeTask, some: "value")
249
+ ```ruby
250
+ expect(SomeTask).to have_retry_on(Net::OpenTimeout)
251
+ expect(SomeTask).to have_retry_on(Net::OpenTimeout, limit: 5, jitter: :exponential)
252
+ ```
194
253
 
195
- # eg: SomeTask.execute!(some: "value")
196
- stub_task_skip!(SomeTask, some: "value")
254
+ #### `have_tag`
197
255
 
198
- # Your specs...
199
- end
256
+ Asserts the subject carries `tag`. Accepts a Task class (reads `settings.tags`) or a `CMDx::Result` (reads `result.tags`).
257
+
258
+ ```ruby
259
+ expect(SomeTask).to have_tag(:critical)
260
+ expect(result).to have_tag(:critical)
200
261
  ```
201
262
 
202
- #### Options
263
+ ### Workflows
264
+
265
+ #### `have_pipeline_tasks`
266
+
267
+ Asserts a `CMDx::Workflow` class declares the given pipeline tasks. Order-sensitive by default; chain `.in_any_order` for set comparison.
203
268
 
204
269
  ```ruby
205
- it "stubs task with metadata" do
206
- stub_task_success(SomeTask, metadata: { some: "value" })
270
+ expect(MyWorkflow).to have_pipeline_tasks(StepA, StepB, StepC)
271
+ expect(MyWorkflow).to have_pipeline_tasks(StepA, StepC, StepB).in_any_order
272
+ ```
207
273
 
208
- # Your specs...
209
- end
274
+ ## Helpers
210
275
 
211
- it "stubs task with a custom reason" do
212
- stub_task_skip!(SomeTask, reason: "Skipping for a custom reason")
276
+ ### Including helper modules
213
277
 
214
- # Your specs...
215
- end
278
+ Mix into all example groups via RSpec config, or include in specific groups:
216
279
 
217
- it "stubs task with a custom cause" do
218
- stub_task_fail!(SomeTask, cause: NoMethodError.new("just blow it up"))
280
+ ```ruby
281
+ RSpec.configure do |config|
282
+ config.include CMDx::RSpec::Helpers
283
+ end
284
+ ```
219
285
 
220
- # Your specs...
286
+ ```ruby
287
+ describe MyFeature do
288
+ include CMDx::RSpec::Helpers
289
+ # ...
221
290
  end
222
291
  ```
223
292
 
224
- #### Workflows
293
+ ### Stubs
294
+
295
+ Each stub builds a frozen `CMDx::Result` carrying the requested signal and wires it into a fresh `CMDx::Chain`, so callers see realistic execution shape without invoking the task's `work`. Any extra keyword args (besides the documented ones) are forwarded to `command.new` as context overrides.
225
296
 
226
- Yields each distinct task class from the workflow’s pipeline (first-seen order) so you can stub them in one place.
297
+ #### Result-type stubs
227
298
 
228
299
  ```ruby
229
- it "stubs every pipeline task for a workflow" do
230
- stub_workflow_tasks(MyWorkflow) do |t|
231
- if t == TaskB
232
- stub_task_success(t)
233
- elsif t == TaskC
234
- stub_task_skip(t)
235
- else
236
- stub_task_success(t)
237
- end
238
- end
300
+ # Non-bang variants stub `SomeTask.execute`
301
+ stub_task_success(SomeTask)
302
+ stub_task_skip(SomeTask)
303
+ stub_task_fail(SomeTask)
304
+
305
+ # Bang variants stub `SomeTask.execute!`
306
+ stub_task_success!(SomeTask)
307
+ stub_task_skip!(SomeTask)
308
+ stub_task_fail!(SomeTask)
309
+
310
+ # Stub a specific argument signature
311
+ stub_task_success(SomeTask, some: "value") # SomeTask.execute(some: "value")
312
+ stub_task_skip!(SomeTask, some: "value") # SomeTask.execute!(some: "value")
313
+ ```
239
314
 
240
- MyWorkflow.execute
315
+ Common options:
241
316
 
242
- # Your specs...
243
- end
317
+ ```ruby
318
+ stub_task_success(SomeTask, metadata: { id: 1 })
319
+ stub_task_skip!(SomeTask, reason: "out of stock")
320
+ stub_task_fail!(SomeTask, cause: NoMethodError.new("boom"))
244
321
  ```
245
322
 
246
- #### Reset
323
+ #### Specialized stubs
247
324
 
248
325
  ```ruby
249
- it "unstubs task executions by type" do
250
- # eg: SomeTask.execute
251
- unstub_task(SomeTask)
326
+ # Models the rescued StandardError -> failed signal path that Runtime
327
+ # takes when `work` raises something other than a Fault.
328
+ stub_task_error(SomeTask, Net::OpenTimeout, "boom")
252
329
 
253
- # eg: SomeTask.execute!
254
- unstub_task!(SomeTask)
330
+ # Models the `throw!`-then-propagate path used by nested tasks/workflows.
331
+ # `upstream_result` must be a failed CMDx::Result.
332
+ stub_task_throw(SomeTask, upstream_result)
255
333
 
256
- # Your specs...
257
- end
334
+ # Returns a successful Result flagged as `deprecated?` without triggering
335
+ # the real Deprecation action.
336
+ stub_task_deprecated(SomeTask)
337
+ ```
258
338
 
259
- it "unstubs task with arguments" do
260
- # eg: SomeTask.execute(some: "value")
261
- unstub_task(SomeTask, some: "value")
339
+ #### Workflow stubs
262
340
 
263
- # eg: SomeTask.execute!(some: "value")
264
- unstub_task!(SomeTask, some: "value")
341
+ `stub_workflow_tasks` yields each distinct Task class reachable from a Workflow's pipeline (first-seen order) so you can stub them in one place.
265
342
 
266
- # Your specs...
343
+ ```ruby
344
+ stub_workflow_tasks(MyWorkflow) do |task|
345
+ case task
346
+ when TaskC then stub_task_skip(task)
347
+ else stub_task_success(task)
348
+ end
267
349
  end
350
+
351
+ MyWorkflow.execute
352
+ ```
353
+
354
+ #### Unstubbing
355
+
356
+ Restores the original implementation. When `context` is supplied, only that argument signature is unstubbed.
357
+
358
+ ```ruby
359
+ unstub_task(SomeTask) # SomeTask.execute
360
+ unstub_task!(SomeTask) # SomeTask.execute!
361
+ unstub_task(SomeTask, some: "value") # SomeTask.execute(some: "value")
268
362
  ```
269
363
 
270
364
  ### Mocks
271
365
 
272
- Helper methods for setting expectations on CMDx command execution.
366
+ Message expectations on `execute` / `execute!`. When `context` is supplied, the expectation is constrained to that signature.
367
+
368
+ ```ruby
369
+ expect_task_execution(SomeTask)
370
+ expect_task_execution!(SomeTask)
371
+ expect_task_execution(SomeTask, some: "value")
372
+
373
+ expect_no_task_execution(SomeTask)
374
+ expect_no_task_execution!(SomeTask)
375
+ expect_no_task_execution(SomeTask, some: "value")
376
+ ```
377
+
378
+ ### Diagnostics
273
379
 
274
- #### Types
380
+ #### `capture_cmdx_logs`
381
+
382
+ Captures lines written to a temporary `CMDx.configuration.logger` for the duration of the block. The previous logger is restored on exit.
275
383
 
276
384
  ```ruby
277
- it "mocks task executions by type" do
278
- # eg: SomeTask.execute
279
- expect_task_execution(SomeTask)
280
- expect_no_task_execution(SomeTask)
385
+ logs = capture_cmdx_logs { MyCommand.execute }
386
+ expect(logs.join).to include("status=success")
387
+ ```
281
388
 
282
- # eg: SomeTask.execute!
283
- expect_task_execution!(BangCommand)
284
- expect_no_task_execution!(SomeTask)
389
+ #### `subscribe_telemetry`
285
390
 
286
- # Your specs...
287
- end
391
+ Subscribes to telemetry events on a Task's telemetry registry for the duration of the block and returns every emitted event in order. Tasks subclassing `command` also fire (the registry is shared by reference until `dup`). Defaults to all events in `CMDx::Telemetry::EVENTS`.
288
392
 
289
- it "mocks task with arguments" do
290
- # eg: SomeTask.execute(some: "value")
291
- expect_task_execution(SomeTask, some: "value")
292
- expect_no_task_execution(SomeTask, some: "value")
393
+ ```ruby
394
+ events = subscribe_telemetry(MyCommand, :task_executed) { MyCommand.execute }
395
+ expect(events.map(&:name)).to eq([:task_executed])
396
+ ```
293
397
 
294
- # eg: SomeTask.execute!(some: "value")
295
- expect_task_execution!(SomeTask, some: "value")
296
- expect_no_task_execution!(SomeTask, some: "value")
398
+ #### `with_cmdx_chain`
297
399
 
298
- # Your specs...
299
- end
400
+ Captures the `CMDx::Chain` produced by the first root execution of `command` within the block. Returns `nil` if `command` didn't run as a root.
401
+
402
+ ```ruby
403
+ chain = with_cmdx_chain(MyWorkflow) { MyWorkflow.execute }
404
+ expect(chain.size).to be > 1
300
405
  ```
301
406
 
302
407
  ## Development
303
408
 
304
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
409
+ Run `bin/setup` to install dependencies, then `rake spec` to run the tests. Use `bin/console` for an interactive prompt.
305
410
 
306
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
411
+ Release flow: bump `lib/cmdx/rspec/version.rb`, then `bundle exec rake release` to tag, push, and publish to [rubygems.org](https://rubygems.org).
307
412
 
308
413
  ## Contributing
309
414
 
310
- Bug reports and pull requests are welcome on GitHub at https://github.com/drexed/cmdx-rspec. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/drexed/cmdx-rspec/blob/master/CODE_OF_CONDUCT.md).
415
+ Bug reports and pull requests are welcome at <https://github.com/drexed/cmdx-rspec>. Contributors are expected to follow the [code of conduct](https://github.com/drexed/cmdx-rspec/blob/master/CODE_OF_CONDUCT.md).
311
416
 
312
417
  ## License
313
418
 
314
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
315
-
316
- ## Code of Conduct
317
-
318
- Everyone interacting in the Cmdx::Rspec project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/drexed/cmdx-rspec/blob/master/CODE_OF_CONDUCT.md).
419
+ Released under the [MIT License](https://opensource.org/licenses/MIT).