cmdx-rspec 1.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +279 -156
- data/lib/cmdx/rspec/helpers.rb +257 -259
- data/lib/cmdx/rspec/matchers/be_complete.rb +20 -0
- data/lib/cmdx/rspec/matchers/be_deprecated.rb +14 -52
- data/lib/cmdx/rspec/matchers/be_interrupted.rb +20 -0
- data/lib/cmdx/rspec/matchers/be_ko.rb +19 -0
- data/lib/cmdx/rspec/matchers/be_ok.rb +19 -0
- data/lib/cmdx/rspec/matchers/be_successful.rb +8 -20
- data/lib/cmdx/rspec/matchers/have_been_retried.rb +41 -0
- data/lib/cmdx/rspec/matchers/have_been_rolled_back.rb +23 -0
- data/lib/cmdx/rspec/matchers/have_callback.rb +46 -0
- data/lib/cmdx/rspec/matchers/have_chain_root.rb +30 -0
- data/lib/cmdx/rspec/matchers/have_chain_size.rb +29 -0
- data/lib/cmdx/rspec/matchers/have_duration.rb +30 -0
- data/lib/cmdx/rspec/matchers/have_empty_context.rb +4 -16
- data/lib/cmdx/rspec/matchers/have_empty_metadata.rb +3 -9
- data/lib/cmdx/rspec/matchers/have_errors_on.rb +50 -0
- data/lib/cmdx/rspec/matchers/have_failed.rb +7 -24
- data/lib/cmdx/rspec/matchers/have_input.rb +41 -0
- data/lib/cmdx/rspec/matchers/have_matching_context.rb +5 -20
- data/lib/cmdx/rspec/matchers/have_matching_metadata.rb +5 -17
- data/lib/cmdx/rspec/matchers/have_middleware.rb +29 -0
- data/lib/cmdx/rspec/matchers/have_no_errors.rb +30 -0
- data/lib/cmdx/rspec/matchers/have_output.rb +46 -0
- data/lib/cmdx/rspec/matchers/have_pipeline_tasks.rb +27 -0
- data/lib/cmdx/rspec/matchers/have_retry_on.rb +36 -0
- data/lib/cmdx/rspec/matchers/have_skipped.rb +7 -24
- data/lib/cmdx/rspec/matchers/have_tag.rb +30 -0
- data/lib/cmdx/rspec/matchers/raise_cmdx_fault.rb +74 -0
- data/lib/cmdx/rspec/version.rb +2 -1
- data/lib/cmdx/rspec.rb +22 -3
- metadata +24 -15
- data/.cursor/prompts/docs.md +0 -12
- data/.cursor/prompts/llms.md +0 -20
- data/.cursor/prompts/rspec.md +0 -24
- data/.cursor/prompts/yardoc.md +0 -14
- data/.cursor/rules/cursor-instructions.mdc +0 -62
- data/.rspec +0 -4
- data/.rubocop.yml +0 -64
- data/src/cmdx-dark-logo.png +0 -0
- data/src/cmdx-light-logo.png +0 -0
- data/src/cmdx-logo.svg +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9d99809d55db6542a630f55457df90bacbf7598a9969a8a1e54cf27630362030
|
|
4
|
+
data.tar.gz: 7fe0770bb36f09002b9b45cbf01751b0aab0efac7529be7c3c65d5a751399dbb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3fa228ad4eec439aea347ed0c12746f66252e0712dfba58ddd246ffde63102dcedc5c2b2c5816441f9d5eec9a3261937ad19ea22c31b197c74b7e20f5daa8cdc
|
|
7
|
+
data.tar.gz: 5818b4c8a34414a5834abb7e8665480d24b9c0e63ae1d5bebc2f1fa37c5156b9b377ec26cef78251a24c1ce2bd98b4d8f439031aa4ba8efb52a9b8888c903c91
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,19 @@ 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
|
+
|
|
17
|
+
## [1.4.0] - 2026-04-09
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Added `stub_workflow_tasks` helper
|
|
21
|
+
|
|
9
22
|
## [1.3.0] - 2025-11-09
|
|
10
23
|
|
|
11
24
|
### 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,141 +15,267 @@
|
|
|
15
15
|
|
|
16
16
|
# CMDx::RSpec
|
|
17
17
|
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
+
require "cmdx/rspec"
|
|
26
37
|
```
|
|
27
38
|
|
|
28
|
-
|
|
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
|
-
|
|
41
|
+
## Matchers
|
|
31
42
|
|
|
32
|
-
|
|
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.
|
|
33
44
|
|
|
34
|
-
|
|
45
|
+
### Result state & status
|
|
35
46
|
|
|
36
|
-
|
|
47
|
+
#### `be_successful`
|
|
37
48
|
|
|
38
|
-
|
|
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.
|
|
39
50
|
|
|
40
|
-
|
|
51
|
+
```ruby
|
|
52
|
+
expect(SomeTask.execute).to be_successful
|
|
53
|
+
expect(SomeTask.execute).to be_successful(metadata: { id: 1 })
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### `have_skipped`
|
|
57
|
+
|
|
58
|
+
Asserts a result was skipped (`state: interrupted`, `status: skipped`). Extra keyword args constrain other `result.to_h` fields.
|
|
41
59
|
|
|
42
60
|
```ruby
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
expect(result).to have_skipped
|
|
62
|
+
expect(result).to have_skipped(
|
|
63
|
+
reason: "out of stock",
|
|
64
|
+
cause: be_a(CMDx::SkipFault)
|
|
65
|
+
)
|
|
66
|
+
```
|
|
45
67
|
|
|
46
|
-
|
|
47
|
-
|
|
68
|
+
#### `have_failed`
|
|
69
|
+
|
|
70
|
+
Asserts a result failed (`state: interrupted`, `status: failed`). Extra keyword args constrain other `result.to_h` fields.
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
expect(result).to have_failed
|
|
74
|
+
expect(result).to have_failed(
|
|
75
|
+
reason: "boom",
|
|
76
|
+
cause: be_a(NoMethodError)
|
|
77
|
+
)
|
|
48
78
|
```
|
|
49
79
|
|
|
50
|
-
|
|
80
|
+
#### `be_ok` / `be_ko`
|
|
51
81
|
|
|
52
|
-
|
|
82
|
+
`be_ok` passes when the result is success or skipped (anything but failed). `be_ko` is its inverse.
|
|
53
83
|
|
|
54
84
|
```ruby
|
|
55
|
-
|
|
56
|
-
|
|
85
|
+
expect(result).to be_ok
|
|
86
|
+
expect(result).to be_ko
|
|
87
|
+
```
|
|
57
88
|
|
|
58
|
-
|
|
59
|
-
expect(result).to have_skipped
|
|
89
|
+
#### `be_complete` / `be_interrupted`
|
|
60
90
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
)
|
|
67
|
-
end
|
|
91
|
+
State-only assertions. `be_complete` passes when `state == :complete`; `be_interrupted` when `state == :interrupted`.
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
expect(result).to be_complete
|
|
95
|
+
expect(result).to be_interrupted
|
|
68
96
|
```
|
|
69
97
|
|
|
70
|
-
###
|
|
98
|
+
### Result data
|
|
71
99
|
|
|
72
|
-
|
|
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).
|
|
73
103
|
|
|
74
104
|
```ruby
|
|
75
|
-
|
|
76
|
-
|
|
105
|
+
expect(result).to have_empty_metadata
|
|
106
|
+
expect(result).to have_matching_metadata(status_code: 500)
|
|
107
|
+
```
|
|
77
108
|
|
|
78
|
-
|
|
79
|
-
expect(result).to have_failed
|
|
109
|
+
#### `have_empty_context` / `have_matching_context`
|
|
80
110
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
)
|
|
87
|
-
end
|
|
111
|
+
Same pattern for the result's context. Both accept a `Hash`, `CMDx::Context`, or `CMDx::Result` (the result's `.context` is unwrapped automatically).
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
expect(result).to have_empty_context
|
|
115
|
+
expect(result).to have_matching_context(stored_id: 123)
|
|
88
116
|
```
|
|
89
117
|
|
|
90
|
-
###
|
|
118
|
+
### Errors
|
|
91
119
|
|
|
92
|
-
|
|
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`.
|
|
93
123
|
|
|
94
124
|
```ruby
|
|
95
|
-
|
|
96
|
-
|
|
125
|
+
expect(result).to have_no_errors
|
|
126
|
+
```
|
|
97
127
|
|
|
98
|
-
|
|
99
|
-
|
|
128
|
+
#### `have_errors_on`
|
|
129
|
+
|
|
130
|
+
Asserts at least one error is present under `key`. Optional positional `messages` further constrain the matcher — all must be present.
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
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")
|
|
100
136
|
```
|
|
101
137
|
|
|
102
|
-
###
|
|
138
|
+
### Execution metrics
|
|
139
|
+
|
|
140
|
+
#### `have_been_retried`
|
|
103
141
|
|
|
104
|
-
|
|
142
|
+
Passes when the result was retried at least once. Pass an integer to require an exact retry count.
|
|
105
143
|
|
|
106
144
|
```ruby
|
|
107
|
-
|
|
108
|
-
|
|
145
|
+
expect(result).to have_been_retried
|
|
146
|
+
expect(result).to have_been_retried(3)
|
|
147
|
+
```
|
|
109
148
|
|
|
110
|
-
|
|
111
|
-
|
|
149
|
+
#### `have_been_rolled_back`
|
|
150
|
+
|
|
151
|
+
Passes when a failing task ran its rollback hook.
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
expect(result).to have_been_rolled_back
|
|
112
155
|
```
|
|
113
156
|
|
|
114
|
-
|
|
157
|
+
#### `have_duration`
|
|
115
158
|
|
|
116
|
-
Asserts
|
|
159
|
+
Asserts the result's duration (in milliseconds) falls within the supplied bounds. At least one of `:less_than` or `:greater_than` is required.
|
|
117
160
|
|
|
118
161
|
```ruby
|
|
119
|
-
|
|
120
|
-
|
|
162
|
+
expect(result).to have_duration(less_than: 100)
|
|
163
|
+
expect(result).to have_duration(greater_than: 0.1, less_than: 50)
|
|
164
|
+
```
|
|
121
165
|
|
|
122
|
-
|
|
123
|
-
|
|
166
|
+
### Faults
|
|
167
|
+
|
|
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.
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
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
|
+
```
|
|
180
|
+
|
|
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)
|
|
124
191
|
```
|
|
125
192
|
|
|
126
|
-
|
|
193
|
+
#### `have_chain_size`
|
|
127
194
|
|
|
128
|
-
|
|
195
|
+
Passes when the chain's size matches `expected`. Accepts a `CMDx::Chain` or `CMDx::Result`.
|
|
129
196
|
|
|
130
197
|
```ruby
|
|
131
|
-
|
|
132
|
-
|
|
198
|
+
expect(result).to have_chain_size(3)
|
|
199
|
+
```
|
|
133
200
|
|
|
134
|
-
|
|
135
|
-
|
|
201
|
+
### Task class declarations
|
|
202
|
+
|
|
203
|
+
These matchers introspect a Task class's configuration. They accept either the class or an instance.
|
|
204
|
+
|
|
205
|
+
#### `be_deprecated`
|
|
206
|
+
|
|
207
|
+
Asserts a Task class is marked deprecated. Optionally constrain the deprecation behavior via a positional value or a chained convenience method.
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
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)
|
|
136
215
|
```
|
|
137
216
|
|
|
138
|
-
|
|
217
|
+
#### `have_input` / `have_output`
|
|
139
218
|
|
|
140
|
-
Asserts
|
|
219
|
+
Asserts the class declares the given input/output. Keyword args are matched (partial) against the parameter's serialized `to_h`.
|
|
141
220
|
|
|
142
221
|
```ruby
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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)
|
|
225
|
+
```
|
|
226
|
+
|
|
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
|
+
```
|
|
236
|
+
|
|
237
|
+
#### `have_middleware`
|
|
238
|
+
|
|
239
|
+
Asserts the class registered `middleware`. Class arguments match by `is_a?` or `==`; other values match by `==`.
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
expect(SomeTask).to have_middleware(LoggingMiddleware)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### `have_retry_on`
|
|
246
|
+
|
|
247
|
+
Asserts the class is configured to retry on `exception`. Keyword args check `CMDx::Retry` configuration values (`:limit`, `:delay`, `:max_delay`, `:jitter`).
|
|
248
|
+
|
|
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
|
+
```
|
|
253
|
+
|
|
254
|
+
#### `have_tag`
|
|
255
|
+
|
|
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)
|
|
261
|
+
```
|
|
262
|
+
|
|
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.
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
expect(MyWorkflow).to have_pipeline_tasks(StepA, StepB, StepC)
|
|
271
|
+
expect(MyWorkflow).to have_pipeline_tasks(StepA, StepC, StepB).in_any_order
|
|
146
272
|
```
|
|
147
273
|
|
|
148
274
|
## Helpers
|
|
149
275
|
|
|
150
|
-
### Including
|
|
276
|
+
### Including helper modules
|
|
151
277
|
|
|
152
|
-
|
|
278
|
+
Mix into all example groups via RSpec config, or include in specific groups:
|
|
153
279
|
|
|
154
280
|
```ruby
|
|
155
281
|
RSpec.configure do |config|
|
|
@@ -157,140 +283,137 @@ RSpec.configure do |config|
|
|
|
157
283
|
end
|
|
158
284
|
```
|
|
159
285
|
|
|
160
|
-
Or include them in specific example groups:
|
|
161
|
-
|
|
162
286
|
```ruby
|
|
163
287
|
describe MyFeature do
|
|
164
288
|
include CMDx::RSpec::Helpers
|
|
165
|
-
|
|
166
|
-
# Your specs...
|
|
289
|
+
# ...
|
|
167
290
|
end
|
|
168
291
|
```
|
|
169
292
|
|
|
170
293
|
### Stubs
|
|
171
294
|
|
|
172
|
-
|
|
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.
|
|
173
296
|
|
|
174
|
-
####
|
|
297
|
+
#### Result-type stubs
|
|
175
298
|
|
|
176
299
|
```ruby
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
it "stubs task with arguments" do
|
|
192
|
-
# eg: SomeTask.execute(some: "value")
|
|
193
|
-
stub_task_success(SomeTask, some: "value")
|
|
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
|
+
```
|
|
194
314
|
|
|
195
|
-
|
|
196
|
-
stub_task_skip!(SomeTask, some: "value")
|
|
315
|
+
Common options:
|
|
197
316
|
|
|
198
|
-
|
|
199
|
-
|
|
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"))
|
|
200
321
|
```
|
|
201
322
|
|
|
202
|
-
####
|
|
323
|
+
#### Specialized stubs
|
|
203
324
|
|
|
204
325
|
```ruby
|
|
205
|
-
|
|
206
|
-
|
|
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")
|
|
207
329
|
|
|
208
|
-
|
|
209
|
-
|
|
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)
|
|
210
333
|
|
|
211
|
-
|
|
212
|
-
|
|
334
|
+
# Returns a successful Result flagged as `deprecated?` without triggering
|
|
335
|
+
# the real Deprecation action.
|
|
336
|
+
stub_task_deprecated(SomeTask)
|
|
337
|
+
```
|
|
213
338
|
|
|
214
|
-
|
|
215
|
-
end
|
|
339
|
+
#### Workflow stubs
|
|
216
340
|
|
|
217
|
-
|
|
218
|
-
stub_task_fail!(SomeTask, cause: NoMethodError.new("just blow it up"))
|
|
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.
|
|
219
342
|
|
|
220
|
-
|
|
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
|
|
221
349
|
end
|
|
350
|
+
|
|
351
|
+
MyWorkflow.execute
|
|
222
352
|
```
|
|
223
353
|
|
|
224
|
-
####
|
|
354
|
+
#### Unstubbing
|
|
225
355
|
|
|
226
|
-
|
|
227
|
-
it "unstubs task executions by type" do
|
|
228
|
-
# eg: SomeTask.execute
|
|
229
|
-
unstub_task(SomeTask)
|
|
356
|
+
Restores the original implementation. When `context` is supplied, only that argument signature is unstubbed.
|
|
230
357
|
|
|
231
|
-
|
|
232
|
-
|
|
358
|
+
```ruby
|
|
359
|
+
unstub_task(SomeTask) # SomeTask.execute
|
|
360
|
+
unstub_task!(SomeTask) # SomeTask.execute!
|
|
361
|
+
unstub_task(SomeTask, some: "value") # SomeTask.execute(some: "value")
|
|
362
|
+
```
|
|
233
363
|
|
|
234
|
-
|
|
235
|
-
end
|
|
364
|
+
### Mocks
|
|
236
365
|
|
|
237
|
-
|
|
238
|
-
# eg: SomeTask.execute(some: "value")
|
|
239
|
-
unstub_task(SomeTask, some: "value")
|
|
366
|
+
Message expectations on `execute` / `execute!`. When `context` is supplied, the expectation is constrained to that signature.
|
|
240
367
|
|
|
241
|
-
|
|
242
|
-
|
|
368
|
+
```ruby
|
|
369
|
+
expect_task_execution(SomeTask)
|
|
370
|
+
expect_task_execution!(SomeTask)
|
|
371
|
+
expect_task_execution(SomeTask, some: "value")
|
|
243
372
|
|
|
244
|
-
|
|
245
|
-
|
|
373
|
+
expect_no_task_execution(SomeTask)
|
|
374
|
+
expect_no_task_execution!(SomeTask)
|
|
375
|
+
expect_no_task_execution(SomeTask, some: "value")
|
|
246
376
|
```
|
|
247
377
|
|
|
248
|
-
###
|
|
378
|
+
### Diagnostics
|
|
249
379
|
|
|
250
|
-
|
|
380
|
+
#### `capture_cmdx_logs`
|
|
251
381
|
|
|
252
|
-
|
|
382
|
+
Captures lines written to a temporary `CMDx.configuration.logger` for the duration of the block. The previous logger is restored on exit.
|
|
253
383
|
|
|
254
384
|
```ruby
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
expect_no_task_execution(SomeTask)
|
|
385
|
+
logs = capture_cmdx_logs { MyCommand.execute }
|
|
386
|
+
expect(logs.join).to include("status=success")
|
|
387
|
+
```
|
|
259
388
|
|
|
260
|
-
|
|
261
|
-
expect_task_execution!(BangCommand)
|
|
262
|
-
expect_no_task_execution!(SomeTask)
|
|
389
|
+
#### `subscribe_telemetry`
|
|
263
390
|
|
|
264
|
-
|
|
265
|
-
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`.
|
|
266
392
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
393
|
+
```ruby
|
|
394
|
+
events = subscribe_telemetry(MyCommand, :task_executed) { MyCommand.execute }
|
|
395
|
+
expect(events.map(&:name)).to eq([:task_executed])
|
|
396
|
+
```
|
|
271
397
|
|
|
272
|
-
|
|
273
|
-
expect_task_execution!(SomeTask, some: "value")
|
|
274
|
-
expect_no_task_execution!(SomeTask, some: "value")
|
|
398
|
+
#### `with_cmdx_chain`
|
|
275
399
|
|
|
276
|
-
|
|
277
|
-
|
|
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
|
|
278
405
|
```
|
|
279
406
|
|
|
280
407
|
## Development
|
|
281
408
|
|
|
282
|
-
|
|
409
|
+
Run `bin/setup` to install dependencies, then `rake spec` to run the tests. Use `bin/console` for an interactive prompt.
|
|
283
410
|
|
|
284
|
-
|
|
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).
|
|
285
412
|
|
|
286
413
|
## Contributing
|
|
287
414
|
|
|
288
|
-
Bug reports and pull requests are welcome
|
|
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).
|
|
289
416
|
|
|
290
417
|
## License
|
|
291
418
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
## Code of Conduct
|
|
295
|
-
|
|
296
|
-
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).
|