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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +274 -173
- data/lib/cmdx/rspec/helpers.rb +244 -276
- 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 -5
|
@@ -1,33 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# Asserts a Task class (or instance) is marked deprecated via CMDx's
|
|
4
|
+
# `deprecation` declaration. Optionally constrains the deprecation
|
|
5
|
+
# behavior — pass it positionally, via `.with_behavior(:warn)`, or use
|
|
6
|
+
# the convenience chains `.with_warning`, `.with_logging`, `.with_error`.
|
|
4
7
|
#
|
|
5
|
-
# @
|
|
6
|
-
#
|
|
7
|
-
# - `:log` or `/log/` - checks if deprecation includes logging
|
|
8
|
-
# - `:raise` or `/raise/` or `true` - checks if deprecation raises or is truthy
|
|
9
|
-
# - `:none` or `false` or `nil` - checks if deprecation is false or nil
|
|
10
|
-
# - Any other value - checks for exact match
|
|
11
|
-
#
|
|
12
|
-
# @return [RSpec::Matchers::BuiltIn::BaseMatcher] The matcher instance
|
|
13
|
-
#
|
|
14
|
-
# @example Checking if a command is deprecated
|
|
15
|
-
# expect(MyCommand).to be_deprecated
|
|
16
|
-
#
|
|
17
|
-
# @example Checking deprecated with raise behavior
|
|
18
|
-
# expect(MyCommand).to be_deprecated(:raise)
|
|
19
|
-
# expect(MyCommand).to be_deprecated.with_raise
|
|
20
|
-
#
|
|
21
|
-
# @example Checking deprecated with warning behavior
|
|
22
|
-
# expect(MyCommand).to be_deprecated(:warn)
|
|
23
|
-
# expect(MyCommand).to be_deprecated.with_warning
|
|
24
|
-
#
|
|
25
|
-
# @example Checking deprecated with logging behavior
|
|
26
|
-
# expect(MyCommand).to be_deprecated(:log)
|
|
27
|
-
# expect(MyCommand).to be_deprecated.with_logging
|
|
28
|
-
#
|
|
29
|
-
# @example Using chainable matchers
|
|
30
|
-
# expect(MyCommand).to be_deprecated.with_behavior(:custom)
|
|
8
|
+
# @example
|
|
9
|
+
# expect(SomeTask).to be_deprecated.with_warning
|
|
31
10
|
RSpec::Matchers.define :be_deprecated do |expected_behavior = nil|
|
|
32
11
|
description do
|
|
33
12
|
if (behavior = @expected_behavior || expected_behavior)
|
|
@@ -54,35 +33,18 @@ RSpec::Matchers.define :be_deprecated do |expected_behavior = nil|
|
|
|
54
33
|
end
|
|
55
34
|
|
|
56
35
|
match do |actual|
|
|
57
|
-
|
|
58
|
-
|
|
36
|
+
target = actual.is_a?(Class) ? actual : actual.class
|
|
37
|
+
deprecation = target.respond_to?(:deprecation) ? target.deprecation : nil
|
|
38
|
+
next false unless deprecation
|
|
59
39
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return false unless deprecate_setting
|
|
40
|
+
behavior = @expected_behavior || expected_behavior
|
|
41
|
+
next true unless behavior
|
|
63
42
|
|
|
64
|
-
|
|
65
|
-
behavior_to_check = @expected_behavior || expected_behavior
|
|
66
|
-
return true unless behavior_to_check
|
|
67
|
-
|
|
68
|
-
# Check specific behavior
|
|
69
|
-
case behavior_to_check
|
|
70
|
-
when :warn, /warn/
|
|
71
|
-
deprecate_setting.to_s.include?("warn")
|
|
72
|
-
when :log, /log/
|
|
73
|
-
deprecate_setting.to_s.include?("log")
|
|
74
|
-
when :raise, /raise/, true
|
|
75
|
-
deprecate_setting == true || deprecate_setting.to_s.include?("raise")
|
|
76
|
-
when :none, false, nil
|
|
77
|
-
!deprecate_setting || deprecate_setting == false
|
|
78
|
-
else
|
|
79
|
-
deprecate_setting == behavior_to_check
|
|
80
|
-
end
|
|
43
|
+
deprecation.instance_variable_get(:@value) == behavior
|
|
81
44
|
end
|
|
82
45
|
|
|
83
|
-
# Chainable matchers for specific behaviors
|
|
84
|
-
chain(:with_raise) { @expected_behavior = :raise }
|
|
85
|
-
chain(:with_logging) { @expected_behavior = :log }
|
|
86
46
|
chain(:with_warning) { @expected_behavior = :warn }
|
|
47
|
+
chain(:with_logging) { @expected_behavior = :log }
|
|
48
|
+
chain(:with_error) { @expected_behavior = :error }
|
|
87
49
|
chain(:with_behavior) { |behavior| @expected_behavior = behavior }
|
|
88
50
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Matcher to verify that a result was interrupted (skipped or failed).
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# expect(MyCommand.execute).to be_interrupted
|
|
7
|
+
RSpec::Matchers.define :be_interrupted do
|
|
8
|
+
description { "have been interrupted" }
|
|
9
|
+
|
|
10
|
+
failure_message do |result|
|
|
11
|
+
"expected #{result.inspect} to have state #{CMDx::Signal::INTERRUPTED.inspect}, " \
|
|
12
|
+
"but was #{result.state.inspect}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
match do |result|
|
|
16
|
+
raise ArgumentError, "must be a CMDx::Result" unless result.is_a?(CMDx::Result)
|
|
17
|
+
|
|
18
|
+
result.state == CMDx::Signal::INTERRUPTED
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Matcher to verify that a result is "ko" (skipped or failed, anything but success).
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# expect(MyCommand.execute).to be_ko
|
|
7
|
+
RSpec::Matchers.define :be_ko do
|
|
8
|
+
description { "have been ko" }
|
|
9
|
+
|
|
10
|
+
failure_message do |result|
|
|
11
|
+
"expected #{result.inspect} to be ko (skipped or failed), but status was #{result.status.inspect}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
match do |result|
|
|
15
|
+
raise ArgumentError, "must be a CMDx::Result" unless result.is_a?(CMDx::Result)
|
|
16
|
+
|
|
17
|
+
result.ko?
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Matcher to verify that a result is "ok" (success or skipped, anything but failed).
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# expect(MyCommand.execute).to be_ok
|
|
7
|
+
RSpec::Matchers.define :be_ok do
|
|
8
|
+
description { "have been ok" }
|
|
9
|
+
|
|
10
|
+
failure_message do |result|
|
|
11
|
+
"expected #{result.inspect} to be ok (success or skipped), but status was #{result.status.inspect}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
match do |result|
|
|
15
|
+
raise ArgumentError, "must be a CMDx::Result" unless result.is_a?(CMDx::Result)
|
|
16
|
+
|
|
17
|
+
result.ok?
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -1,23 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# Asserts a {CMDx::Result} represents a successful execution
|
|
4
|
+
# (`state: complete`, `status: success`). Additional keyword args are
|
|
5
|
+
# merged into a `result.to_h` inclusion check, so any Result field can be
|
|
6
|
+
# constrained inline (e.g. `be_successful(metadata: { id: 1 })`).
|
|
4
7
|
#
|
|
5
|
-
# @
|
|
6
|
-
#
|
|
7
|
-
# @option data [Symbol] :status Expected status (defaults to CMDx::Result::SUCCESS)
|
|
8
|
-
# @option data [Symbol] :outcome Expected outcome (defaults to CMDx::Result::SUCCESS)
|
|
9
|
-
#
|
|
10
|
-
# @return [RSpec::Matchers::BuiltIn::BaseMatcher] The matcher instance
|
|
11
|
-
#
|
|
12
|
-
# @raise [ArgumentError] if the actual value is not a CMDx::Result
|
|
13
|
-
#
|
|
14
|
-
# @example Checking if a result is successful
|
|
15
|
-
# result = MyCommand.execute
|
|
16
|
-
# expect(result).to be_successful
|
|
17
|
-
#
|
|
18
|
-
# @example Checking success with additional attributes
|
|
19
|
-
# result = MyCommand.execute
|
|
20
|
-
# expect(result).to be_successful(state: CMDx::Result::COMPLETE)
|
|
8
|
+
# @example
|
|
9
|
+
# expect(SomeTask.execute).to be_successful
|
|
21
10
|
RSpec::Matchers.define :be_successful do |**data|
|
|
22
11
|
description { "have been a success" }
|
|
23
12
|
|
|
@@ -25,9 +14,8 @@ RSpec::Matchers.define :be_successful do |**data|
|
|
|
25
14
|
raise ArgumentError, "must be a CMDx::Result" unless result.is_a?(CMDx::Result)
|
|
26
15
|
|
|
27
16
|
expect(result.to_h).to include(
|
|
28
|
-
state: CMDx::
|
|
29
|
-
status: CMDx::
|
|
30
|
-
outcome: CMDx::Result::SUCCESS,
|
|
17
|
+
state: CMDx::Signal::COMPLETE,
|
|
18
|
+
status: CMDx::Signal::SUCCESS,
|
|
31
19
|
**data
|
|
32
20
|
)
|
|
33
21
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Matcher to verify that a result was retried at least once, optionally
|
|
4
|
+
# matching a specific retry count.
|
|
5
|
+
#
|
|
6
|
+
# @example Any retry
|
|
7
|
+
# expect(result).to have_been_retried
|
|
8
|
+
#
|
|
9
|
+
# @example Exact retry count
|
|
10
|
+
# expect(result).to have_been_retried(3)
|
|
11
|
+
RSpec::Matchers.define :have_been_retried do |expected_count = nil|
|
|
12
|
+
description do
|
|
13
|
+
expected_count ? "have been retried #{expected_count} times" : "have been retried"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
failure_message do |result|
|
|
17
|
+
if expected_count
|
|
18
|
+
"expected #{result.inspect} to have been retried #{expected_count} times, but it was #{result.retries}"
|
|
19
|
+
else
|
|
20
|
+
"expected #{result.inspect} to have been retried, but it wasn't"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
failure_message_when_negated do |result|
|
|
25
|
+
if expected_count
|
|
26
|
+
"expected #{result.inspect} not to have been retried #{expected_count} times, but it was"
|
|
27
|
+
else
|
|
28
|
+
"expected #{result.inspect} not to have been retried, but it was retried #{result.retries} times"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
match do |result|
|
|
33
|
+
raise ArgumentError, "must be a CMDx::Result" unless result.is_a?(CMDx::Result)
|
|
34
|
+
|
|
35
|
+
if expected_count.nil?
|
|
36
|
+
result.retried?
|
|
37
|
+
else
|
|
38
|
+
result.retries == expected_count
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Matcher to verify that a failing task ran its rollback hook.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# expect(result).to have_been_rolled_back
|
|
7
|
+
RSpec::Matchers.define :have_been_rolled_back do
|
|
8
|
+
description { "have been rolled back" }
|
|
9
|
+
|
|
10
|
+
failure_message do |result|
|
|
11
|
+
"expected #{result.inspect} to have been rolled back, but it wasn't"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
failure_message_when_negated do |result|
|
|
15
|
+
"expected #{result.inspect} not to have been rolled back, but it was"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
match do |result|
|
|
19
|
+
raise ArgumentError, "must be a CMDx::Result" unless result.is_a?(CMDx::Result)
|
|
20
|
+
|
|
21
|
+
result.rolled_back?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Matcher to verify that a task class registered a callback for +event+.
|
|
4
|
+
# Optional +callable+ further constrains the matcher; matched by `==`
|
|
5
|
+
# (Symbol method names, Proc/Object identity), or by class membership
|
|
6
|
+
# when given a class.
|
|
7
|
+
#
|
|
8
|
+
# @example Any callback for an event
|
|
9
|
+
# expect(MyCommand).to have_callback(:before_execution)
|
|
10
|
+
#
|
|
11
|
+
# @example A specific Symbol callback
|
|
12
|
+
# expect(MyCommand).to have_callback(:before_execution, :authenticate!)
|
|
13
|
+
#
|
|
14
|
+
# @example A callable instance class
|
|
15
|
+
# expect(MyCommand).to have_callback(:on_failed, AlertOnFailure)
|
|
16
|
+
RSpec::Matchers.define :have_callback do |event, callable = nil|
|
|
17
|
+
description do
|
|
18
|
+
callable.nil? ? "have callback for #{event.inspect}" : "have callback #{callable.inspect} for #{event.inspect}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
failure_message do |actual|
|
|
22
|
+
target = actual.is_a?(Class) ? actual : actual.class
|
|
23
|
+
entries = target.callbacks.registry[event] || []
|
|
24
|
+
if entries.empty?
|
|
25
|
+
"expected #{target} to register a callback for #{event.inspect}, but none were registered"
|
|
26
|
+
else
|
|
27
|
+
"expected #{target} to register #{callable.inspect} for #{event.inspect}, but had #{entries.map(&:first).inspect}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
match do |actual|
|
|
32
|
+
target = actual.is_a?(Class) ? actual : actual.class
|
|
33
|
+
next false unless target.respond_to?(:callbacks)
|
|
34
|
+
|
|
35
|
+
entries = target.callbacks.registry[event]
|
|
36
|
+
next false if entries.nil? || entries.empty?
|
|
37
|
+
next true if callable.nil?
|
|
38
|
+
|
|
39
|
+
entries.any? do |cb, _opts|
|
|
40
|
+
case callable
|
|
41
|
+
when Class then cb.is_a?(callable)
|
|
42
|
+
else cb == callable
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Matcher to verify that a {CMDx::Chain}'s root is a {CMDx::Result} for
|
|
4
|
+
# the given task class. Accepts either a Chain or a Result.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# expect(result).to have_chain_root(MyWorkflow)
|
|
8
|
+
RSpec::Matchers.define :have_chain_root do |task_class|
|
|
9
|
+
description { "have chain root #{task_class}" }
|
|
10
|
+
|
|
11
|
+
failure_message do |actual|
|
|
12
|
+
chain = extract_chain(actual)
|
|
13
|
+
"expected chain root to be #{task_class}, but was #{chain&.root&.task.inspect}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
match do |actual|
|
|
17
|
+
chain = extract_chain(actual)
|
|
18
|
+
next false if chain.nil?
|
|
19
|
+
next false if chain.root.nil?
|
|
20
|
+
|
|
21
|
+
chain.root.task <= task_class
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
define_method(:extract_chain) do |actual|
|
|
25
|
+
case actual
|
|
26
|
+
when CMDx::Chain then actual
|
|
27
|
+
when CMDx::Result then actual.chain
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Matcher to verify the size of a {CMDx::Chain}, accepting either a Chain
|
|
4
|
+
# directly or a {CMDx::Result} (whose +chain+ is read).
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# expect(result).to have_chain_size(3)
|
|
8
|
+
RSpec::Matchers.define :have_chain_size do |expected|
|
|
9
|
+
description { "have chain size #{expected}" }
|
|
10
|
+
|
|
11
|
+
failure_message do |actual|
|
|
12
|
+
chain = extract_chain(actual)
|
|
13
|
+
"expected chain size to be #{expected}, but was #{chain&.size.inspect}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
match do |actual|
|
|
17
|
+
chain = extract_chain(actual)
|
|
18
|
+
next false if chain.nil?
|
|
19
|
+
|
|
20
|
+
chain.size == expected
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
define_method(:extract_chain) do |actual|
|
|
24
|
+
case actual
|
|
25
|
+
when CMDx::Chain then actual
|
|
26
|
+
when CMDx::Result then actual.chain
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Matcher to verify a {CMDx::Result}'s duration (in milliseconds) falls
|
|
4
|
+
# within an upper bound, lower bound, or both.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# expect(result).to have_duration(less_than: 100)
|
|
8
|
+
# expect(result).to have_duration(greater_than: 0.1)
|
|
9
|
+
# expect(result).to have_duration(greater_than: 1, less_than: 50)
|
|
10
|
+
RSpec::Matchers.define :have_duration do |less_than: nil, greater_than: nil|
|
|
11
|
+
description do
|
|
12
|
+
parts = []
|
|
13
|
+
parts << "greater than #{greater_than}ms" if greater_than
|
|
14
|
+
parts << "less than #{less_than}ms" if less_than
|
|
15
|
+
"have duration #{parts.join(' and ')}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
failure_message do |result|
|
|
19
|
+
"expected duration to satisfy bounds (less_than: #{less_than}, greater_than: #{greater_than}), but was #{result.duration}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
match do |result|
|
|
23
|
+
raise ArgumentError, "must be a CMDx::Result" unless result.is_a?(CMDx::Result)
|
|
24
|
+
raise ArgumentError, "provide :less_than and/or :greater_than" if less_than.nil? && greater_than.nil?
|
|
25
|
+
next false if result.duration.nil?
|
|
26
|
+
|
|
27
|
+
(less_than.nil? || result.duration < less_than) &&
|
|
28
|
+
(greater_than.nil? || result.duration > greater_than)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -1,22 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# Asserts the subject's context hash is empty. Accepts a `Hash`,
|
|
4
|
+
# {CMDx::Context}, or {CMDx::Result} (in which case `result.context` is
|
|
5
|
+
# inspected). Raises if the subject is none of those.
|
|
4
6
|
#
|
|
5
|
-
# @
|
|
6
|
-
# - If Hash, checks the hash directly
|
|
7
|
-
# - If CMDx::Context, converts to hash and checks
|
|
8
|
-
# - If CMDx::Result, extracts context and checks
|
|
9
|
-
#
|
|
10
|
-
# @return [RSpec::Matchers::BuiltIn::BaseMatcher] The matcher instance
|
|
11
|
-
#
|
|
12
|
-
# @raise [RuntimeError] if the context type is unknown
|
|
13
|
-
#
|
|
14
|
-
# @example Checking empty context from a hash
|
|
15
|
-
# context = {}
|
|
16
|
-
# expect(context).to have_empty_context
|
|
17
|
-
#
|
|
18
|
-
# @example Checking empty context from a result
|
|
19
|
-
# result = MyCommand.execute
|
|
7
|
+
# @example
|
|
20
8
|
# expect(result).to have_empty_context
|
|
21
9
|
RSpec::Matchers.define :have_empty_context do
|
|
22
10
|
description { "have an empty context" }
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# Asserts a {CMDx::Result}'s `metadata` hash is empty. Raises
|
|
4
|
+
# `ArgumentError` when the subject is not a Result.
|
|
4
5
|
#
|
|
5
|
-
# @
|
|
6
|
-
#
|
|
7
|
-
# @return [RSpec::Matchers::BuiltIn::BaseMatcher] The matcher instance
|
|
8
|
-
#
|
|
9
|
-
# @raise [ArgumentError] if the actual value is not a CMDx::Result
|
|
10
|
-
#
|
|
11
|
-
# @example Checking if a result has empty metadata
|
|
12
|
-
# result = MyCommand.execute
|
|
6
|
+
# @example
|
|
13
7
|
# expect(result).to have_empty_metadata
|
|
14
8
|
RSpec::Matchers.define :have_empty_metadata do
|
|
15
9
|
description { "have an empty metadata" }
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Matcher to verify that a {CMDx::Result}, task instance, or {CMDx::Errors}
|
|
4
|
+
# carries at least one error under +key+. Optional +messages+ further
|
|
5
|
+
# constrain the matcher to specific error strings.
|
|
6
|
+
#
|
|
7
|
+
# @example Any error on :email
|
|
8
|
+
# expect(result).to have_errors_on(:email)
|
|
9
|
+
#
|
|
10
|
+
# @example Specific message
|
|
11
|
+
# expect(result).to have_errors_on(:email, "is required")
|
|
12
|
+
#
|
|
13
|
+
# @example Multiple messages (all must be present)
|
|
14
|
+
# expect(task).to have_errors_on(:email, "is required", "is invalid")
|
|
15
|
+
RSpec::Matchers.define :have_errors_on do |key, *messages|
|
|
16
|
+
description do
|
|
17
|
+
if messages.empty?
|
|
18
|
+
"have errors on #{key.inspect}"
|
|
19
|
+
else
|
|
20
|
+
"have errors on #{key.inspect} matching #{messages.inspect}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
failure_message do |actual|
|
|
25
|
+
errors = extract_errors(actual)
|
|
26
|
+
if errors.nil?
|
|
27
|
+
"expected #{actual.inspect} to expose an Errors collection"
|
|
28
|
+
elsif !errors.key?(key)
|
|
29
|
+
"expected errors on #{key.inspect}, but only had: #{errors.keys.inspect}"
|
|
30
|
+
else
|
|
31
|
+
"expected errors on #{key.inspect} to include #{messages.inspect}, but had #{errors[key].inspect}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
match do |actual|
|
|
36
|
+
errors = extract_errors(actual)
|
|
37
|
+
next false if errors.nil?
|
|
38
|
+
next false unless errors.key?(key)
|
|
39
|
+
|
|
40
|
+
messages.all? { |m| errors.added?(key, m) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
define_method(:extract_errors) do |actual|
|
|
44
|
+
case actual
|
|
45
|
+
when CMDx::Errors then actual
|
|
46
|
+
when CMDx::Result, CMDx::Task then actual.errors
|
|
47
|
+
else actual.respond_to?(:errors) ? actual.errors : nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -1,25 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# Asserts a {CMDx::Result} failed (`state: interrupted`,
|
|
4
|
+
# `status: failed`). Extra keyword args constrain other `result.to_h`
|
|
5
|
+
# fields such as `:reason`, `:cause`, or `:metadata`.
|
|
4
6
|
#
|
|
5
|
-
# @
|
|
6
|
-
#
|
|
7
|
-
# @option data [Symbol] :status Expected status (defaults to CMDx::Result::FAILED)
|
|
8
|
-
# @option data [Symbol] :outcome Expected outcome (defaults to CMDx::Result::FAILED)
|
|
9
|
-
# @option data [String] :reason Expected reason string
|
|
10
|
-
# @option data [CMDx::FailFault] :cause Expected cause fault
|
|
11
|
-
#
|
|
12
|
-
# @return [RSpec::Matchers::BuiltIn::BaseMatcher] The matcher instance
|
|
13
|
-
#
|
|
14
|
-
# @raise [ArgumentError] if the actual value is not a CMDx::Result
|
|
15
|
-
#
|
|
16
|
-
# @example Checking if a result is a failure
|
|
17
|
-
# result = MyCommand.execute
|
|
18
|
-
# expect(result).to have_failed
|
|
19
|
-
#
|
|
20
|
-
# @example Checking failure with specific reason
|
|
21
|
-
# result = MyCommand.execute
|
|
22
|
-
# expect(result).to have_failed(reason: "Custom error message")
|
|
7
|
+
# @example
|
|
8
|
+
# expect(result).to have_failed(cause: be_a(NoMethodError))
|
|
23
9
|
RSpec::Matchers.define :have_failed do |**data|
|
|
24
10
|
description { "have been a failure" }
|
|
25
11
|
|
|
@@ -27,11 +13,8 @@ RSpec::Matchers.define :have_failed do |**data|
|
|
|
27
13
|
raise ArgumentError, "must be a CMDx::Result" unless result.is_a?(CMDx::Result)
|
|
28
14
|
|
|
29
15
|
expect(result.to_h).to include(
|
|
30
|
-
state: CMDx::
|
|
31
|
-
status: CMDx::
|
|
32
|
-
outcome: CMDx::Result::FAILED,
|
|
33
|
-
reason: CMDx::Locale.t("cmdx.faults.unspecified"),
|
|
34
|
-
cause: be_a(CMDx::FailFault),
|
|
16
|
+
state: CMDx::Signal::INTERRUPTED,
|
|
17
|
+
status: CMDx::Signal::FAILED,
|
|
35
18
|
**data
|
|
36
19
|
)
|
|
37
20
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Matcher to verify that a task class declares an input named +name+.
|
|
4
|
+
# Optional keyword arguments are matched against the input's serialized
|
|
5
|
+
# {CMDx::Input#to_h} (partial match — only provided keys are checked).
|
|
6
|
+
#
|
|
7
|
+
# @example Existence check
|
|
8
|
+
# expect(MyCommand).to have_input(:user_id)
|
|
9
|
+
#
|
|
10
|
+
# @example Required input
|
|
11
|
+
# expect(MyCommand).to have_input(:user_id, required: true)
|
|
12
|
+
#
|
|
13
|
+
# @example Type / coercion check (matches against the +options+ hash)
|
|
14
|
+
# expect(MyCommand).to have_input(:user_id, options: hash_including(coerce: :integer))
|
|
15
|
+
RSpec::Matchers.define :have_input do |name, **expected|
|
|
16
|
+
description do
|
|
17
|
+
expected.empty? ? "have input #{name.inspect}" : "have input #{name.inspect} matching #{expected.inspect}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
failure_message do |actual|
|
|
21
|
+
target = actual.is_a?(Class) ? actual : actual.class
|
|
22
|
+
input = target.inputs.registry[name.to_sym]
|
|
23
|
+
if input.nil?
|
|
24
|
+
"expected #{target} to declare input #{name.inspect}, but registry has #{target.inputs.registry.keys.inspect}"
|
|
25
|
+
else
|
|
26
|
+
"expected #{target}'s input #{name.inspect} to match #{expected.inspect}, but it was #{input.to_h.inspect}"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
match do |actual|
|
|
31
|
+
target = actual.is_a?(Class) ? actual : actual.class
|
|
32
|
+
next false unless target.respond_to?(:inputs)
|
|
33
|
+
|
|
34
|
+
input = target.inputs.registry[name.to_sym]
|
|
35
|
+
next false if input.nil?
|
|
36
|
+
next true if expected.empty?
|
|
37
|
+
|
|
38
|
+
schema = input.to_h
|
|
39
|
+
expected.all? { |k, v| values_match?(v, schema[k]) }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -1,26 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# Asserts the subject's context includes the supplied keys/values.
|
|
4
|
+
# Accepts a `Hash`, {CMDx::Context}, or {CMDx::Result}. With no keyword
|
|
5
|
+
# args, delegates to {have_empty_context}.
|
|
4
6
|
#
|
|
5
|
-
# @
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# @param context [Hash, CMDx::Context, CMDx::Result] The context to check
|
|
9
|
-
# - If Hash, checks the hash directly
|
|
10
|
-
# - If CMDx::Context, converts to hash and checks
|
|
11
|
-
# - If CMDx::Result, extracts context and checks
|
|
12
|
-
#
|
|
13
|
-
# @return [RSpec::Matchers::BuiltIn::BaseMatcher] The matcher instance
|
|
14
|
-
#
|
|
15
|
-
# @raise [RuntimeError] if the context type is unknown
|
|
16
|
-
#
|
|
17
|
-
# @example Checking context matches specific values
|
|
18
|
-
# result = MyCommand.execute(user_id: 123, role: "admin")
|
|
19
|
-
# expect(result).to have_matching_context(user_id: 123, role: "admin")
|
|
20
|
-
#
|
|
21
|
-
# @example Checking empty context
|
|
22
|
-
# result = MyCommand.execute
|
|
23
|
-
# expect(result).to have_matching_context
|
|
7
|
+
# @example
|
|
8
|
+
# expect(result).to have_matching_context(stored_id: 123)
|
|
24
9
|
RSpec::Matchers.define :have_matching_context do |**data|
|
|
25
10
|
description { "have matching context" }
|
|
26
11
|
|
|
@@ -1,23 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# Asserts a {CMDx::Result}'s `metadata` hash includes the supplied
|
|
4
|
+
# keys/values. With no keyword args, delegates to {have_empty_metadata}.
|
|
5
|
+
# Raises `ArgumentError` when the subject is not a Result.
|
|
4
6
|
#
|
|
5
|
-
# @
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# @param result [CMDx::Result] The result to check
|
|
9
|
-
#
|
|
10
|
-
# @return [RSpec::Matchers::BuiltIn::BaseMatcher] The matcher instance
|
|
11
|
-
#
|
|
12
|
-
# @raise [ArgumentError] if the actual value is not a CMDx::Result
|
|
13
|
-
#
|
|
14
|
-
# @example Checking metadata matches specific values
|
|
15
|
-
# result = MyCommand.execute
|
|
16
|
-
# expect(result).to have_matching_metadata(key: "value", count: 42)
|
|
17
|
-
#
|
|
18
|
-
# @example Checking empty metadata
|
|
19
|
-
# result = MyCommand.execute
|
|
20
|
-
# expect(result).to have_matching_metadata
|
|
7
|
+
# @example
|
|
8
|
+
# expect(result).to have_matching_metadata(status_code: 500)
|
|
21
9
|
RSpec::Matchers.define :have_matching_metadata do |**data|
|
|
22
10
|
description { "have matching metadata" }
|
|
23
11
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Matcher to verify that a task class registered +middleware+. When given
|
|
4
|
+
# a class, matches by `is_a?`; otherwise by `==` (works for module-level
|
|
5
|
+
# callables like `MyMiddleware` referenced by name).
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# expect(MyCommand).to have_middleware(LoggingMiddleware)
|
|
9
|
+
RSpec::Matchers.define :have_middleware do |middleware|
|
|
10
|
+
description { "have middleware #{middleware.inspect}" }
|
|
11
|
+
|
|
12
|
+
failure_message do |actual|
|
|
13
|
+
target = actual.is_a?(Class) ? actual : actual.class
|
|
14
|
+
"expected #{target} to register middleware #{middleware.inspect}, but had #{target.middlewares.registry.inspect}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
match do |actual|
|
|
18
|
+
target = actual.is_a?(Class) ? actual : actual.class
|
|
19
|
+
next false unless target.respond_to?(:middlewares)
|
|
20
|
+
|
|
21
|
+
target.middlewares.registry.any? do |entry|
|
|
22
|
+
m, = entry
|
|
23
|
+
case middleware
|
|
24
|
+
when Class then m.is_a?(middleware) || m == middleware
|
|
25
|
+
else m == middleware
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|