cmdx 1.1.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/.cursor/prompts/docs.md +4 -1
- data/.cursor/prompts/llms.md +20 -0
- data/.cursor/prompts/rspec.md +4 -1
- data/.cursor/prompts/yardoc.md +3 -2
- data/.cursor/rules/cursor-instructions.mdc +56 -1
- data/.irbrc +6 -0
- data/.rubocop.yml +29 -18
- data/CHANGELOG.md +5 -133
- data/LLM.md +3317 -0
- data/README.md +68 -44
- data/docs/attributes/coercions.md +162 -0
- data/docs/attributes/defaults.md +90 -0
- data/docs/attributes/definitions.md +281 -0
- data/docs/attributes/naming.md +78 -0
- data/docs/attributes/validations.md +309 -0
- data/docs/basics/chain.md +56 -249
- data/docs/basics/context.md +56 -289
- data/docs/basics/execution.md +114 -0
- data/docs/basics/setup.md +37 -334
- data/docs/callbacks.md +89 -467
- data/docs/deprecation.md +91 -174
- data/docs/getting_started.md +212 -202
- data/docs/internationalization.md +11 -647
- data/docs/interruptions/exceptions.md +23 -198
- data/docs/interruptions/faults.md +71 -151
- data/docs/interruptions/halt.md +109 -186
- data/docs/logging.md +44 -256
- data/docs/middlewares.md +113 -426
- data/docs/outcomes/result.md +81 -228
- data/docs/outcomes/states.md +33 -221
- data/docs/outcomes/statuses.md +21 -311
- data/docs/tips_and_tricks.md +120 -70
- data/docs/workflows.md +99 -283
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/attribute.rb +229 -0
- data/lib/cmdx/attribute_registry.rb +94 -0
- data/lib/cmdx/attribute_value.rb +193 -0
- data/lib/cmdx/callback_registry.rb +69 -77
- data/lib/cmdx/chain.rb +56 -73
- data/lib/cmdx/coercion_registry.rb +52 -68
- data/lib/cmdx/coercions/array.rb +19 -18
- data/lib/cmdx/coercions/big_decimal.rb +20 -24
- data/lib/cmdx/coercions/boolean.rb +26 -25
- data/lib/cmdx/coercions/complex.rb +21 -22
- data/lib/cmdx/coercions/date.rb +25 -23
- data/lib/cmdx/coercions/date_time.rb +24 -25
- data/lib/cmdx/coercions/float.rb +25 -22
- data/lib/cmdx/coercions/hash.rb +31 -32
- data/lib/cmdx/coercions/integer.rb +30 -24
- data/lib/cmdx/coercions/rational.rb +29 -24
- data/lib/cmdx/coercions/string.rb +19 -22
- data/lib/cmdx/coercions/symbol.rb +37 -0
- data/lib/cmdx/coercions/time.rb +26 -25
- data/lib/cmdx/configuration.rb +49 -108
- data/lib/cmdx/context.rb +222 -44
- data/lib/cmdx/deprecator.rb +61 -0
- data/lib/cmdx/errors.rb +42 -252
- data/lib/cmdx/exceptions.rb +39 -0
- data/lib/cmdx/faults.rb +78 -39
- data/lib/cmdx/freezer.rb +51 -0
- data/lib/cmdx/identifier.rb +30 -0
- data/lib/cmdx/locale.rb +52 -0
- data/lib/cmdx/log_formatters/json.rb +21 -22
- data/lib/cmdx/log_formatters/key_value.rb +20 -22
- data/lib/cmdx/log_formatters/line.rb +15 -22
- data/lib/cmdx/log_formatters/logstash.rb +22 -23
- data/lib/cmdx/log_formatters/raw.rb +16 -22
- data/lib/cmdx/middleware_registry.rb +70 -74
- data/lib/cmdx/middlewares/correlate.rb +90 -54
- data/lib/cmdx/middlewares/runtime.rb +58 -0
- data/lib/cmdx/middlewares/timeout.rb +48 -68
- data/lib/cmdx/railtie.rb +12 -45
- data/lib/cmdx/result.rb +229 -314
- data/lib/cmdx/task.rb +194 -366
- data/lib/cmdx/utils/call.rb +49 -0
- data/lib/cmdx/utils/condition.rb +71 -0
- data/lib/cmdx/utils/format.rb +61 -0
- data/lib/cmdx/validator_registry.rb +63 -72
- data/lib/cmdx/validators/exclusion.rb +38 -67
- data/lib/cmdx/validators/format.rb +48 -49
- data/lib/cmdx/validators/inclusion.rb +43 -74
- data/lib/cmdx/validators/length.rb +91 -154
- data/lib/cmdx/validators/numeric.rb +87 -162
- data/lib/cmdx/validators/presence.rb +37 -50
- data/lib/cmdx/version.rb +1 -1
- data/lib/cmdx/worker.rb +178 -0
- data/lib/cmdx/workflow.rb +85 -81
- data/lib/cmdx.rb +19 -13
- data/lib/generators/cmdx/install_generator.rb +14 -13
- data/lib/generators/cmdx/task_generator.rb +25 -50
- data/lib/generators/cmdx/templates/install.rb +11 -46
- data/lib/generators/cmdx/templates/task.rb.tt +3 -2
- data/lib/locales/en.yml +18 -4
- data/src/cmdx-logo.png +0 -0
- metadata +32 -116
- data/docs/ai_prompts.md +0 -393
- data/docs/basics/call.md +0 -317
- data/docs/configuration.md +0 -344
- data/docs/parameters/coercions.md +0 -396
- data/docs/parameters/defaults.md +0 -335
- data/docs/parameters/definitions.md +0 -446
- data/docs/parameters/namespacing.md +0 -378
- data/docs/parameters/validations.md +0 -405
- data/docs/testing.md +0 -553
- data/lib/cmdx/callback.rb +0 -53
- data/lib/cmdx/chain_inspector.rb +0 -56
- data/lib/cmdx/chain_serializer.rb +0 -63
- data/lib/cmdx/coercion.rb +0 -57
- data/lib/cmdx/coercions/virtual.rb +0 -29
- data/lib/cmdx/core_ext/hash.rb +0 -83
- data/lib/cmdx/core_ext/module.rb +0 -98
- data/lib/cmdx/core_ext/object.rb +0 -125
- data/lib/cmdx/correlator.rb +0 -122
- data/lib/cmdx/error.rb +0 -67
- data/lib/cmdx/fault.rb +0 -140
- data/lib/cmdx/immutator.rb +0 -52
- data/lib/cmdx/lazy_struct.rb +0 -246
- data/lib/cmdx/log_formatters/pretty_json.rb +0 -40
- data/lib/cmdx/log_formatters/pretty_key_value.rb +0 -38
- data/lib/cmdx/log_formatters/pretty_line.rb +0 -41
- data/lib/cmdx/logger.rb +0 -49
- data/lib/cmdx/logger_ansi.rb +0 -68
- data/lib/cmdx/logger_serializer.rb +0 -116
- data/lib/cmdx/middleware.rb +0 -70
- data/lib/cmdx/parameter.rb +0 -312
- data/lib/cmdx/parameter_evaluator.rb +0 -231
- data/lib/cmdx/parameter_inspector.rb +0 -66
- data/lib/cmdx/parameter_registry.rb +0 -106
- data/lib/cmdx/parameter_serializer.rb +0 -59
- data/lib/cmdx/result_ansi.rb +0 -71
- data/lib/cmdx/result_inspector.rb +0 -71
- data/lib/cmdx/result_logger.rb +0 -59
- data/lib/cmdx/result_serializer.rb +0 -104
- data/lib/cmdx/rspec/matchers.rb +0 -28
- data/lib/cmdx/rspec/result_matchers/be_executed.rb +0 -42
- data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +0 -94
- data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +0 -94
- data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +0 -59
- data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +0 -57
- data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +0 -87
- data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +0 -51
- data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +0 -58
- data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +0 -59
- data/lib/cmdx/rspec/result_matchers/have_context.rb +0 -86
- data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +0 -54
- data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +0 -52
- data/lib/cmdx/rspec/result_matchers/have_metadata.rb +0 -114
- data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +0 -66
- data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +0 -64
- data/lib/cmdx/rspec/result_matchers/have_runtime.rb +0 -78
- data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +0 -76
- data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +0 -62
- data/lib/cmdx/rspec/task_matchers/have_callback.rb +0 -85
- data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +0 -68
- data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +0 -92
- data/lib/cmdx/rspec/task_matchers/have_middleware.rb +0 -46
- data/lib/cmdx/rspec/task_matchers/have_parameter.rb +0 -181
- data/lib/cmdx/task_deprecator.rb +0 -58
- data/lib/cmdx/task_processor.rb +0 -246
- data/lib/cmdx/task_serializer.rb +0 -57
- data/lib/cmdx/utils/ansi_color.rb +0 -73
- data/lib/cmdx/utils/log_timestamp.rb +0 -36
- data/lib/cmdx/utils/monotonic_runtime.rb +0 -34
- data/lib/cmdx/utils/name_affix.rb +0 -52
- data/lib/cmdx/validator.rb +0 -57
- data/lib/generators/cmdx/templates/workflow.rb.tt +0 -7
- data/lib/generators/cmdx/workflow_generator.rb +0 -84
- data/lib/locales/ar.yml +0 -35
- data/lib/locales/cs.yml +0 -35
- data/lib/locales/da.yml +0 -35
- data/lib/locales/de.yml +0 -35
- data/lib/locales/el.yml +0 -35
- data/lib/locales/es.yml +0 -35
- data/lib/locales/fi.yml +0 -35
- data/lib/locales/fr.yml +0 -35
- data/lib/locales/he.yml +0 -35
- data/lib/locales/hi.yml +0 -35
- data/lib/locales/it.yml +0 -35
- data/lib/locales/ja.yml +0 -35
- data/lib/locales/ko.yml +0 -35
- data/lib/locales/nl.yml +0 -35
- data/lib/locales/no.yml +0 -35
- data/lib/locales/pl.yml +0 -35
- data/lib/locales/pt.yml +0 -35
- data/lib/locales/ru.yml +0 -35
- data/lib/locales/sv.yml +0 -35
- data/lib/locales/th.yml +0 -35
- data/lib/locales/tr.yml +0 -35
- data/lib/locales/vi.yml +0 -35
- 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
|
data/lib/cmdx/chain_inspector.rb
DELETED
@@ -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
|