cmdx 1.0.1 → 1.1.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 (157) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/prompts/rspec.md +20 -0
  3. data/.cursor/prompts/yardoc.md +8 -0
  4. data/.rubocop.yml +2 -0
  5. data/CHANGELOG.md +17 -2
  6. data/README.md +1 -1
  7. data/docs/basics/call.md +2 -2
  8. data/docs/basics/chain.md +1 -1
  9. data/docs/callbacks.md +3 -36
  10. data/docs/configuration.md +58 -12
  11. data/docs/interruptions/exceptions.md +1 -1
  12. data/docs/interruptions/faults.md +2 -2
  13. data/docs/logging.md +4 -4
  14. data/docs/middlewares.md +43 -43
  15. data/docs/parameters/coercions.md +49 -38
  16. data/docs/parameters/defaults.md +1 -1
  17. data/docs/parameters/validations.md +0 -39
  18. data/docs/testing.md +11 -12
  19. data/docs/workflows.md +4 -4
  20. data/lib/cmdx/.DS_Store +0 -0
  21. data/lib/cmdx/callback.rb +36 -56
  22. data/lib/cmdx/callback_registry.rb +82 -73
  23. data/lib/cmdx/chain.rb +65 -122
  24. data/lib/cmdx/chain_inspector.rb +22 -115
  25. data/lib/cmdx/chain_serializer.rb +17 -148
  26. data/lib/cmdx/coercion.rb +49 -0
  27. data/lib/cmdx/coercion_registry.rb +94 -0
  28. data/lib/cmdx/coercions/array.rb +18 -36
  29. data/lib/cmdx/coercions/big_decimal.rb +21 -33
  30. data/lib/cmdx/coercions/boolean.rb +21 -40
  31. data/lib/cmdx/coercions/complex.rb +18 -31
  32. data/lib/cmdx/coercions/date.rb +20 -39
  33. data/lib/cmdx/coercions/date_time.rb +22 -39
  34. data/lib/cmdx/coercions/float.rb +19 -32
  35. data/lib/cmdx/coercions/hash.rb +22 -41
  36. data/lib/cmdx/coercions/integer.rb +20 -33
  37. data/lib/cmdx/coercions/rational.rb +20 -32
  38. data/lib/cmdx/coercions/string.rb +23 -31
  39. data/lib/cmdx/coercions/time.rb +24 -40
  40. data/lib/cmdx/coercions/virtual.rb +14 -31
  41. data/lib/cmdx/configuration.rb +57 -171
  42. data/lib/cmdx/context.rb +22 -165
  43. data/lib/cmdx/core_ext/hash.rb +42 -67
  44. data/lib/cmdx/core_ext/module.rb +35 -79
  45. data/lib/cmdx/core_ext/object.rb +63 -98
  46. data/lib/cmdx/correlator.rb +40 -156
  47. data/lib/cmdx/error.rb +37 -202
  48. data/lib/cmdx/errors.rb +165 -202
  49. data/lib/cmdx/fault.rb +55 -158
  50. data/lib/cmdx/faults.rb +26 -137
  51. data/lib/cmdx/immutator.rb +22 -109
  52. data/lib/cmdx/lazy_struct.rb +103 -187
  53. data/lib/cmdx/log_formatters/json.rb +14 -40
  54. data/lib/cmdx/log_formatters/key_value.rb +14 -40
  55. data/lib/cmdx/log_formatters/line.rb +14 -48
  56. data/lib/cmdx/log_formatters/logstash.rb +14 -57
  57. data/lib/cmdx/log_formatters/pretty_json.rb +14 -50
  58. data/lib/cmdx/log_formatters/pretty_key_value.rb +13 -46
  59. data/lib/cmdx/log_formatters/pretty_line.rb +16 -54
  60. data/lib/cmdx/log_formatters/raw.rb +19 -49
  61. data/lib/cmdx/logger.rb +20 -82
  62. data/lib/cmdx/logger_ansi.rb +18 -75
  63. data/lib/cmdx/logger_serializer.rb +24 -114
  64. data/lib/cmdx/middleware.rb +38 -60
  65. data/lib/cmdx/middleware_registry.rb +81 -77
  66. data/lib/cmdx/middlewares/correlate.rb +41 -226
  67. data/lib/cmdx/middlewares/timeout.rb +46 -185
  68. data/lib/cmdx/parameter.rb +120 -198
  69. data/lib/cmdx/parameter_evaluator.rb +231 -0
  70. data/lib/cmdx/parameter_inspector.rb +25 -56
  71. data/lib/cmdx/parameter_registry.rb +59 -84
  72. data/lib/cmdx/parameter_serializer.rb +23 -74
  73. data/lib/cmdx/railtie.rb +24 -107
  74. data/lib/cmdx/result.rb +254 -260
  75. data/lib/cmdx/result_ansi.rb +19 -85
  76. data/lib/cmdx/result_inspector.rb +27 -68
  77. data/lib/cmdx/result_logger.rb +18 -81
  78. data/lib/cmdx/result_serializer.rb +28 -132
  79. data/lib/cmdx/rspec/matchers.rb +28 -0
  80. data/lib/cmdx/rspec/result_matchers/be_executed.rb +42 -0
  81. data/lib/cmdx/rspec/result_matchers/be_failed_task.rb +94 -0
  82. data/lib/cmdx/rspec/result_matchers/be_skipped_task.rb +94 -0
  83. data/lib/cmdx/rspec/result_matchers/be_state_matchers.rb +59 -0
  84. data/lib/cmdx/rspec/result_matchers/be_status_matchers.rb +57 -0
  85. data/lib/cmdx/rspec/result_matchers/be_successful_task.rb +87 -0
  86. data/lib/cmdx/rspec/result_matchers/have_bad_outcome.rb +51 -0
  87. data/lib/cmdx/rspec/result_matchers/have_caused_failure.rb +58 -0
  88. data/lib/cmdx/rspec/result_matchers/have_chain_index.rb +59 -0
  89. data/lib/cmdx/rspec/result_matchers/have_context.rb +86 -0
  90. data/lib/cmdx/rspec/result_matchers/have_empty_metadata.rb +54 -0
  91. data/lib/cmdx/rspec/result_matchers/have_good_outcome.rb +52 -0
  92. data/lib/cmdx/rspec/result_matchers/have_metadata.rb +114 -0
  93. data/lib/cmdx/rspec/result_matchers/have_preserved_context.rb +66 -0
  94. data/lib/cmdx/rspec/result_matchers/have_received_thrown_failure.rb +64 -0
  95. data/lib/cmdx/rspec/result_matchers/have_runtime.rb +78 -0
  96. data/lib/cmdx/rspec/result_matchers/have_thrown_failure.rb +76 -0
  97. data/lib/cmdx/rspec/task_matchers/be_well_formed_task.rb +62 -0
  98. data/lib/cmdx/rspec/task_matchers/have_callback.rb +85 -0
  99. data/lib/cmdx/rspec/task_matchers/have_cmd_setting.rb +68 -0
  100. data/lib/cmdx/rspec/task_matchers/have_executed_callbacks.rb +92 -0
  101. data/lib/cmdx/rspec/task_matchers/have_middleware.rb +46 -0
  102. data/lib/cmdx/rspec/task_matchers/have_parameter.rb +181 -0
  103. data/lib/cmdx/task.rb +213 -425
  104. data/lib/cmdx/task_deprecator.rb +55 -0
  105. data/lib/cmdx/task_processor.rb +245 -0
  106. data/lib/cmdx/task_serializer.rb +22 -70
  107. data/lib/cmdx/utils/ansi_color.rb +13 -89
  108. data/lib/cmdx/utils/log_timestamp.rb +13 -42
  109. data/lib/cmdx/utils/monotonic_runtime.rb +13 -63
  110. data/lib/cmdx/utils/name_affix.rb +21 -71
  111. data/lib/cmdx/validator.rb +48 -0
  112. data/lib/cmdx/validator_registry.rb +86 -0
  113. data/lib/cmdx/validators/exclusion.rb +55 -94
  114. data/lib/cmdx/validators/format.rb +31 -85
  115. data/lib/cmdx/validators/inclusion.rb +65 -110
  116. data/lib/cmdx/validators/length.rb +117 -133
  117. data/lib/cmdx/validators/numeric.rb +123 -130
  118. data/lib/cmdx/validators/presence.rb +38 -79
  119. data/lib/cmdx/version.rb +1 -7
  120. data/lib/cmdx/workflow.rb +46 -339
  121. data/lib/cmdx.rb +1 -1
  122. data/lib/generators/cmdx/install_generator.rb +14 -31
  123. data/lib/generators/cmdx/task_generator.rb +39 -55
  124. data/lib/generators/cmdx/templates/install.rb +24 -6
  125. data/lib/generators/cmdx/workflow_generator.rb +41 -66
  126. data/lib/locales/ar.yml +0 -1
  127. data/lib/locales/cs.yml +0 -1
  128. data/lib/locales/da.yml +0 -1
  129. data/lib/locales/de.yml +0 -1
  130. data/lib/locales/el.yml +0 -1
  131. data/lib/locales/en.yml +0 -1
  132. data/lib/locales/es.yml +0 -1
  133. data/lib/locales/fi.yml +0 -1
  134. data/lib/locales/fr.yml +0 -1
  135. data/lib/locales/he.yml +0 -1
  136. data/lib/locales/hi.yml +0 -1
  137. data/lib/locales/it.yml +0 -1
  138. data/lib/locales/ja.yml +0 -1
  139. data/lib/locales/ko.yml +0 -1
  140. data/lib/locales/nl.yml +0 -1
  141. data/lib/locales/no.yml +0 -1
  142. data/lib/locales/pl.yml +0 -1
  143. data/lib/locales/pt.yml +0 -1
  144. data/lib/locales/ru.yml +0 -1
  145. data/lib/locales/sv.yml +0 -1
  146. data/lib/locales/th.yml +0 -1
  147. data/lib/locales/tr.yml +0 -1
  148. data/lib/locales/vi.yml +0 -1
  149. data/lib/locales/zh.yml +0 -1
  150. metadata +34 -8
  151. data/lib/cmdx/parameter_validator.rb +0 -81
  152. data/lib/cmdx/parameter_value.rb +0 -244
  153. data/lib/cmdx/parameters_inspector.rb +0 -72
  154. data/lib/cmdx/parameters_serializer.rb +0 -115
  155. data/lib/cmdx/rspec/result_matchers.rb +0 -917
  156. data/lib/cmdx/rspec/task_matchers.rb +0 -570
  157. data/lib/cmdx/validators/custom.rb +0 -102
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec matcher for asserting that a task class has a specific task setting.
4
+ #
5
+ # This matcher checks if a CMDx::Task class has registered a task setting with the
6
+ # specified name. Task settings are configuration options that control task behavior
7
+ # such as execution timeouts, retry policies, or custom flags. The matcher can
8
+ # optionally verify that the setting has a specific value for more precise validation.
9
+ #
10
+ # @param setting_name [Symbol, String] the name of the task setting to check for
11
+ # @param expected_value [Object, nil] the expected value of the setting (optional)
12
+ #
13
+ # @return [Boolean] true if the task has the specified setting and optionally the expected value
14
+ #
15
+ # @example Testing basic task setting presence
16
+ # class MyTask < CMDx::Task
17
+ # cmd_setting tags: ["admin"]
18
+ # def call; end
19
+ # end
20
+ # expect(MyTask).to have_cmd_setting(:tags)
21
+ #
22
+ # @example Testing task setting with specific value
23
+ # class ProcessTask < CMDx::Task
24
+ # cmd_setting tags: ["admin"]
25
+ # def call; end
26
+ # end
27
+ # expect(ProcessTask).to have_cmd_setting(:tags, ["admin"])
28
+ #
29
+ # @example Negative assertion
30
+ # class SimpleTask < CMDx::Task
31
+ # def call; end
32
+ # end
33
+ # expect(SimpleTask).not_to have_cmd_setting(:tags)
34
+ RSpec::Matchers.define :have_cmd_setting do |setting_name, expected_value = nil|
35
+ match do |task_class|
36
+ return false unless task_class.cmd_setting?(setting_name)
37
+
38
+ if expected_value
39
+ task_class.cmd_setting(setting_name) == expected_value
40
+ else
41
+ true
42
+ end
43
+ end
44
+
45
+ failure_message do |task_class|
46
+ if expected_value
47
+ actual_value = task_class.cmd_setting(setting_name)
48
+ "expected task to have setting #{setting_name} with value #{expected_value}, but was #{actual_value}"
49
+ else
50
+ available_settings = task_class.cmd_settings.keys
51
+ "expected task to have setting #{setting_name}, but had #{available_settings}"
52
+ end
53
+ end
54
+
55
+ failure_message_when_negated do |_task_class|
56
+ if expected_value
57
+ "expected task not to have setting #{setting_name} with value #{expected_value}, but it did"
58
+ else
59
+ "expected task not to have setting #{setting_name}, but it did"
60
+ end
61
+ end
62
+
63
+ description do
64
+ desc = "have task setting #{setting_name}"
65
+ desc += " with value #{expected_value}" if expected_value
66
+ desc
67
+ end
68
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec matcher for asserting that a task has executed specific callbacks.
4
+ #
5
+ # This matcher verifies that callbacks were actually invoked during task execution,
6
+ # not just registered. It works by mocking the callback execution to track which
7
+ # callbacks are called, then executing the task and checking that the expected
8
+ # callbacks were invoked. This is useful for testing that callback logic is properly
9
+ # triggered during task execution rather than just checking callback registration.
10
+ #
11
+ # @param callback_names [Array<Symbol, String>] the names of callbacks expected to execute
12
+ #
13
+ # @return [Boolean] true if all specified callbacks were executed during task execution
14
+ #
15
+ # @example Testing basic callback execution
16
+ # class MyTask < CMDx::Task
17
+ # before_execution :setup
18
+ # def call; end
19
+ # end
20
+ # expect(MyTask.new).to have_executed_callbacks(:before_execution)
21
+ #
22
+ # @example Testing callback execution with specific callable
23
+ # class ProcessTask < CMDx::Task
24
+ # after_execution :log_completion
25
+ # def call; end
26
+ # end
27
+ # expect(ProcessTask).to have_callback(:after_execution).with_callable(:log_completion)
28
+ #
29
+ # @example Testing callback execution with result
30
+ # result = MyTask.call(data: "test")
31
+ # expect(result).to have_executed_callbacks(:before_execution, :after_execution)
32
+ #
33
+ # @example Negative assertion
34
+ # class SimpleTask < CMDx::Task
35
+ # def call; end
36
+ # end
37
+ # expect(SimpleTask.new).not_to have_executed_callbacks(:before_execution)
38
+ RSpec::Matchers.define :have_executed_callbacks do |*callback_names|
39
+ match do |task_or_result|
40
+ @executed_callbacks = []
41
+
42
+ # Mock the callback execution to track what gets called
43
+ if task_or_result.is_a?(CMDx::Task)
44
+ task = task_or_result
45
+ original_callback_call = task.cmd_callbacks.method(:call)
46
+
47
+ allow(task.cmd_callbacks).to receive(:call) do |task_instance, callback_name|
48
+ @executed_callbacks << callback_name
49
+ original_callback_call.call(task_instance, callback_name)
50
+ end
51
+
52
+ task.process
53
+ else
54
+ # If it's a result, check if callbacks were executed during task execution
55
+ result = task_or_result
56
+ # This would require the callbacks to be tracked during execution
57
+ # For now, assume callbacks were executed based on result state
58
+ @executed_callbacks = infer_executed_callbacks(result)
59
+ end
60
+
61
+ callback_names.all? { |callback_name| @executed_callbacks.include?(callback_name) }
62
+ end
63
+
64
+ failure_message do |_task_or_result|
65
+ missing_callbacks = callback_names - @executed_callbacks
66
+ "expected to execute callbacks #{callback_names}, but missing #{missing_callbacks}. Executed: #{@executed_callbacks}"
67
+ end
68
+
69
+ failure_message_when_negated do |_task_or_result|
70
+ "expected not to execute callbacks #{callback_names}, but executed #{@executed_callbacks & callback_names}"
71
+ end
72
+
73
+ description do
74
+ "execute callbacks #{callback_names}"
75
+ end
76
+
77
+ private
78
+
79
+ def infer_executed_callbacks(result)
80
+ callbacks = []
81
+ callbacks << :before_validation if result.executed?
82
+ callbacks << :after_validation if result.executed?
83
+ callbacks << :before_execution if result.executed?
84
+ callbacks << :after_execution if result.executed?
85
+ callbacks << :on_executed if result.executed?
86
+ callbacks << :"on_#{result.status}" if result.executed?
87
+ callbacks << :on_good if result.good?
88
+ callbacks << :on_bad if result.bad?
89
+ callbacks << :"on_#{result.state}" if result.executed?
90
+ callbacks
91
+ end
92
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec matcher for asserting that a task class has a specific middleware.
4
+ #
5
+ # This matcher checks if a CMDx::Task class has registered a middleware of the
6
+ # specified class. Middlewares are components that wrap around task execution to
7
+ # provide cross-cutting concerns like logging, timing, error handling, or other
8
+ # aspects. The matcher verifies that the middleware is properly registered and
9
+ # available for execution during task performance.
10
+ #
11
+ # @param middleware_class [Class] the middleware class to check for
12
+ #
13
+ # @return [Boolean] true if the task has the specified middleware class registered
14
+ #
15
+ # @example Testing middleware registration
16
+ # class MyTask < CMDx::Task
17
+ # use :middleware, CMDx::Middlewares::Timeout, timeout: 10
18
+ # def call; end
19
+ # end
20
+ # expect(MyTask).to have_middleware(TimeoutMiddleware)
21
+ #
22
+ # @example Negative assertion
23
+ # class SimpleTask < CMDx::Task
24
+ # def call; end
25
+ # end
26
+ # expect(SimpleTask).not_to have_middleware(TimeoutMiddleware)
27
+ RSpec::Matchers.define :have_middleware do |middleware_class|
28
+ match do |task_class|
29
+ task_class.cmd_middlewares.any? do |middleware|
30
+ middleware.is_a?(middleware_class) || middleware.instance_of?(middleware_class)
31
+ end
32
+ end
33
+
34
+ failure_message do |task_class|
35
+ middleware_classes = task_class.cmd_middlewares.map(&:class)
36
+ "expected task to have middleware #{middleware_class}, but had #{middleware_classes}"
37
+ end
38
+
39
+ failure_message_when_negated do |_task_class|
40
+ "expected task not to have middleware #{middleware_class}, but it did"
41
+ end
42
+
43
+ description do
44
+ "have middleware #{middleware_class}"
45
+ end
46
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec matcher for asserting that a task class has a specific parameter.
4
+ #
5
+ # This matcher checks if a CMDx::Task class has registered a parameter with the
6
+ # specified name. Parameters are inputs to task execution that can be required
7
+ # or optional, typed with coercions, validated, and have default values. The
8
+ # matcher supports various chain methods for precise parameter validation.
9
+ #
10
+ # @param parameter_name [Symbol, String] the name of the parameter to check for
11
+ #
12
+ # @return [Boolean] true if the task has the specified parameter and optionally matches all criteria
13
+ #
14
+ # @example Testing basic parameter presence
15
+ # class MyTask < CMDx::Task
16
+ # optional :input_file, type: :string
17
+ # def call; end
18
+ # end
19
+ # expect(MyTask).to have_parameter(:input_file)
20
+ #
21
+ # @example Testing required parameter
22
+ # class ProcessTask < CMDx::Task
23
+ # required data, type: :string
24
+ # def call; end
25
+ # end
26
+ # expect(ProcessTask).to have_parameter(:data).that_is_required
27
+ #
28
+ # @example Testing optional parameter with default
29
+ # class ConfigTask < CMDx::Task
30
+ # optional timeout, type: :integer, default: 30
31
+ # def call; end
32
+ # end
33
+ # expect(ConfigTask).to have_parameter(:timeout).that_is_optional.with_default(30)
34
+ #
35
+ # @example Testing parameter with type coercion
36
+ # class ImportTask < CMDx::Task
37
+ # optional csv_file, type: :string
38
+ # optional batch_size, type: :integer
39
+ # def call; end
40
+ # end
41
+ # expect(ImportTask).to have_parameter(:csv_file).with_type(:string)
42
+ # expect(ImportTask).to have_parameter(:batch_size).with_coercion(:integer)
43
+ #
44
+ # @example Testing parameter with validations
45
+ # class UserTask < CMDx::Task
46
+ # optional email, type: :string, format: /@/, presence: true
47
+ # def call; end
48
+ # end
49
+ # expect(UserTask).to have_parameter(:email).with_validations(:format, :presence)
50
+ #
51
+ # @example Negative assertion
52
+ # class SimpleTask < CMDx::Task
53
+ # def call; end
54
+ # end
55
+ # expect(SimpleTask).not_to have_parameter(:nonexistent)
56
+ RSpec::Matchers.define :have_parameter do |parameter_name|
57
+ match do |task_class|
58
+ @parameter = task_class.cmd_parameters.registry.find { |p| p.method_name == parameter_name }
59
+ return false unless @parameter
60
+
61
+ # Check if parameter exists
62
+ parameter_exists = !@parameter.nil?
63
+ return false unless parameter_exists
64
+
65
+ # Check required/optional if specified
66
+ unless @expected_required.nil?
67
+ required_matches = @parameter.required? == @expected_required
68
+ return false unless required_matches
69
+ end
70
+
71
+ # Check type/coercion if specified
72
+ if @expected_type
73
+ type_matches = @parameter.type == @expected_type
74
+ return false unless type_matches
75
+ end
76
+
77
+ # Check validations if specified
78
+ if @expected_validations&.any?
79
+ validations_match = @expected_validations.all? do |validation_type|
80
+ @parameter.options.key?(validation_type)
81
+ end
82
+ return false unless validations_match
83
+ end
84
+
85
+ # Check default value if specified
86
+ if @expected_default_value != :__not_specified__
87
+ default_matches = @parameter.options[:default] == @expected_default_value
88
+ return false unless default_matches
89
+ end
90
+
91
+ true
92
+ end
93
+
94
+ chain :that_is_required do
95
+ @expected_required = true
96
+ end
97
+
98
+ chain :that_is_optional do
99
+ @expected_required = false
100
+ end
101
+
102
+ chain :with_type do |type|
103
+ @expected_type = type
104
+ end
105
+
106
+ chain :with_coercion do |type|
107
+ @expected_type = type
108
+ end
109
+
110
+ chain :with_validations do |*validations|
111
+ @expected_validations = validations
112
+ end
113
+
114
+ chain :with_validation do |validation|
115
+ @expected_validations = [@expected_validations, validation].flatten.compact
116
+ end
117
+
118
+ chain :with_default do |default_value|
119
+ @expected_default_value = default_value
120
+ end
121
+
122
+ define_method :initialize do |parameter_name|
123
+ @parameter_name = parameter_name
124
+ @expected_required = nil
125
+ @expected_type = nil
126
+ @expected_validations = []
127
+ @expected_default_value = :__not_specified__
128
+ end
129
+
130
+ failure_message do |task_class|
131
+ if @parameter.nil?
132
+ available_parameters = task_class.cmd_parameters.registry.map(&:method_name)
133
+ "expected task to have parameter #{@parameter_name}, but had parameters: #{available_parameters}"
134
+ else
135
+ issues = []
136
+
137
+ if !@expected_required.nil? && @parameter.required? != @expected_required
138
+ expected_req_text = @expected_required ? "required" : "optional"
139
+ actual_req_text = @parameter.required? ? "required" : "optional"
140
+ issues << "expected parameter to be #{expected_req_text}, but was #{actual_req_text}"
141
+ end
142
+
143
+ if @expected_type
144
+ actual_type = @parameter.type
145
+ issues << "expected parameter type to be #{@expected_type}, but was #{actual_type}" unless actual_type == @expected_type
146
+ end
147
+
148
+ if @expected_validations&.any?
149
+ missing_validations = @expected_validations.reject do |validation_type|
150
+ @parameter.options.key?(validation_type)
151
+ end
152
+
153
+ if missing_validations.any?
154
+ actual_validations = @parameter.options.keys
155
+ issues << "expected parameter to have validations #{missing_validations}, but had #{actual_validations}"
156
+ end
157
+ end
158
+
159
+ issues << "expected parameter default to be #{@expected_default_value}, but was #{@parameter.options[:default]}" if (@expected_default_value != :__not_specified__) && @parameter.options[:default] != @expected_default_value
160
+
161
+ if issues.any?
162
+ "expected parameter #{@parameter_name} to match criteria, but #{issues.join(', ')}"
163
+ else
164
+ "expected parameter #{@parameter_name} to match all criteria, but something didn't match"
165
+ end
166
+ end
167
+ end
168
+
169
+ failure_message_when_negated do |_task_class|
170
+ "expected task not to have parameter #{@parameter_name}, but it did"
171
+ end
172
+
173
+ description do
174
+ desc = "have parameter #{@parameter_name}"
175
+ desc += " that is #{@expected_required ? 'required' : 'optional'}" unless @expected_required.nil?
176
+ desc += " with type #{@expected_type}" if @expected_type
177
+ desc += " with validations #{@expected_validations}" if @expected_validations&.any?
178
+ desc += " with default #{@expected_default_value}" if @expected_default_value != :__not_specified__
179
+ desc
180
+ end
181
+ end