cuprum 0.8.0 → 0.9.0.beta.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 +5 -5
- data/CHANGELOG.md +41 -0
- data/DEVELOPMENT.md +14 -10
- data/README.md +197 -337
- data/lib/cuprum.rb +0 -25
- data/lib/cuprum/built_in/identity_command.rb +4 -4
- data/lib/cuprum/chaining.rb +119 -149
- data/lib/cuprum/command.rb +16 -14
- data/lib/cuprum/error.rb +37 -0
- data/lib/cuprum/errors/command_not_implemented.rb +35 -0
- data/lib/cuprum/errors/operation_not_called.rb +35 -0
- data/lib/cuprum/operation.rb +30 -27
- data/lib/cuprum/processing.rb +47 -80
- data/lib/cuprum/result.rb +52 -127
- data/lib/cuprum/rspec.rb +8 -0
- data/lib/cuprum/rspec/be_a_result.rb +19 -0
- data/lib/cuprum/rspec/be_a_result_matcher.rb +271 -0
- data/lib/cuprum/version.rb +3 -3
- metadata +11 -9
- data/lib/cuprum/errors/process_not_implemented_error.rb +0 -14
- data/lib/cuprum/result_helpers.rb +0 -113
- data/lib/cuprum/utils/result_not_empty_warning.rb +0 -72
data/lib/cuprum/result.rb
CHANGED
@@ -1,166 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'cuprum'
|
2
4
|
|
3
5
|
module Cuprum
|
4
6
|
# Data object that encapsulates the result of calling a Cuprum command.
|
5
7
|
class Result
|
8
|
+
STATUSES = %i[success failure].freeze
|
9
|
+
|
6
10
|
# @param value [Object] The value returned by calling the command.
|
7
|
-
# @param
|
8
|
-
# called.
|
9
|
-
|
11
|
+
# @param error [Object] The error (if any) generated when the command was
|
12
|
+
# called. Can be a Cuprum::Error, a model errors object, etc.
|
13
|
+
# @param status [String, Symbol] The status of the result. Must be :success,
|
14
|
+
# :failure, or nil.
|
15
|
+
def initialize(value: nil, error: nil, status: nil)
|
10
16
|
@value = value
|
11
|
-
@
|
12
|
-
@status =
|
13
|
-
|
14
|
-
end # constructor
|
17
|
+
@error = error
|
18
|
+
@status = resolve_status(status)
|
19
|
+
end
|
15
20
|
|
16
21
|
# @return [Object] the value returned by calling the command.
|
17
|
-
|
22
|
+
attr_reader :value
|
18
23
|
|
19
|
-
# @return [
|
24
|
+
# @return [Object] the error (if any) generated when the command was
|
20
25
|
# called.
|
21
|
-
|
26
|
+
attr_reader :error
|
27
|
+
|
28
|
+
# @return [Symbol] the status of the result, either :success or :failure.
|
29
|
+
attr_reader :status
|
22
30
|
|
23
|
-
# rubocop:disable Metrics/AbcSize
|
24
31
|
# rubocop:disable Metrics/CyclomaticComplexity
|
25
|
-
# rubocop:disable Metrics/MethodLength
|
26
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
27
32
|
|
28
33
|
# Compares the other object to the result.
|
29
34
|
#
|
30
35
|
# @param other [#value, #success?] An object responding to, at minimum,
|
31
|
-
# #value and #success?. If present, the #failure
|
32
|
-
#
|
36
|
+
# #value and #success?. If present, the #failure? and #error values
|
37
|
+
# will also be compared.
|
33
38
|
#
|
34
39
|
# @return [Boolean] True if all present values match the result, otherwise
|
35
40
|
# false.
|
36
|
-
def ==
|
37
|
-
return false unless other.respond_to?(:value)
|
38
|
-
|
39
|
-
unless other.respond_to?(:
|
40
|
-
return false
|
41
|
-
end # unless
|
42
|
-
|
43
|
-
if other.respond_to?(:failure?) && other.failure? != failure?
|
44
|
-
return false
|
45
|
-
end # if
|
46
|
-
|
47
|
-
if other.respond_to?(:errors) && other.errors != errors
|
48
|
-
return false
|
49
|
-
end # if
|
50
|
-
|
51
|
-
if other.respond_to?(:halted?) && other.halted? != halted?
|
52
|
-
return false
|
53
|
-
end # if
|
41
|
+
def ==(other)
|
42
|
+
return false unless other.respond_to?(:value) && other.value == value
|
43
|
+
return false unless other.respond_to?(:status) && other.status == status
|
44
|
+
return false unless other.respond_to?(:error) && other.error == error
|
54
45
|
|
55
46
|
true
|
56
|
-
end
|
57
|
-
# rubocop:enable Metrics/AbcSize
|
47
|
+
end
|
58
48
|
# rubocop:enable Metrics/CyclomaticComplexity
|
59
|
-
# rubocop:enable Metrics/MethodLength
|
60
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
61
|
-
|
62
|
-
# @return [Boolean] true if the result is empty, i.e. has no value or errors
|
63
|
-
# and does not have its status set or is halted.
|
64
|
-
def empty?
|
65
|
-
value.nil? && errors.empty? && @status.nil? && !halted?
|
66
|
-
end # method empty?
|
67
49
|
|
68
|
-
#
|
69
|
-
# errors.
|
70
|
-
#
|
71
|
-
# @return [Cuprum::Result] The result.
|
72
|
-
def failure!
|
73
|
-
@status = :failure
|
74
|
-
|
75
|
-
self
|
76
|
-
end # method failure!
|
77
|
-
|
78
|
-
# @return [Boolean] false if the command did not generate any errors,
|
79
|
-
# otherwise true.
|
50
|
+
# @return [Boolean] true if the result status is :failure, otherwise false.
|
80
51
|
def failure?
|
81
|
-
@status == :failure
|
82
|
-
end
|
52
|
+
@status == :failure
|
53
|
+
end
|
83
54
|
|
84
|
-
#
|
85
|
-
# run.
|
86
|
-
#
|
87
|
-
# @return [Cuprum::Result] The result.
|
88
|
-
def halt!
|
89
|
-
@halted = true
|
90
|
-
|
91
|
-
self
|
92
|
-
end # method halt!
|
93
|
-
|
94
|
-
# @return [Boolean] true if the command has been halted, and will not run
|
95
|
-
# any subsequent chained commands.
|
96
|
-
def halted?
|
97
|
-
@halted
|
98
|
-
end # method halted?
|
99
|
-
|
100
|
-
# Marks the result as a success, whether or not the command generated any
|
101
|
-
# errors.
|
102
|
-
#
|
103
|
-
# @return [Cuprum::Result] The result.
|
104
|
-
def success!
|
105
|
-
@status = :success
|
106
|
-
|
107
|
-
self
|
108
|
-
end # method success!
|
109
|
-
|
110
|
-
# @return [Boolean] true if the command did not generate any errors,
|
111
|
-
# otherwise false.
|
55
|
+
# @return [Boolean] true if the result status is :success, otherwise false.
|
112
56
|
def success?
|
113
|
-
@status == :success
|
114
|
-
end
|
57
|
+
@status == :success
|
58
|
+
end
|
115
59
|
|
116
60
|
# @return [Cuprum::Result] The result.
|
117
|
-
def
|
61
|
+
def to_cuprum_result
|
118
62
|
self
|
119
|
-
end
|
120
|
-
|
121
|
-
# @api private
|
122
|
-
def update other_result
|
123
|
-
return self if other_result.nil?
|
124
|
-
|
125
|
-
self.value = other_result.value
|
63
|
+
end
|
126
64
|
|
127
|
-
|
128
|
-
|
129
|
-
update_errors(other_result)
|
65
|
+
private
|
130
66
|
|
131
|
-
|
67
|
+
def defined_statuses
|
68
|
+
self.class::STATUSES
|
69
|
+
end
|
132
70
|
|
133
|
-
|
134
|
-
|
71
|
+
def normalize_status(status)
|
72
|
+
return status unless status.is_a?(String) || status.is_a?(Symbol)
|
135
73
|
|
136
|
-
|
137
|
-
|
138
|
-
attr_reader :status
|
74
|
+
tools.string.underscore(status).intern
|
75
|
+
end
|
139
76
|
|
140
|
-
|
141
|
-
|
142
|
-
# @!visibility public
|
143
|
-
#
|
144
|
-
# Generates an empty errors object. When the command is called, the result
|
145
|
-
# will have its #errors property initialized to the value returned by
|
146
|
-
# #build_errors. By default, this is an array. If you want to use a custom
|
147
|
-
# errors object type, override this method in a subclass.
|
148
|
-
#
|
149
|
-
# @return [Array] An empty errors object.
|
150
|
-
def build_errors
|
151
|
-
[]
|
152
|
-
end # method build_errors
|
77
|
+
def resolve_status(status)
|
78
|
+
return error.nil? ? :success : :failure if status.nil?
|
153
79
|
|
154
|
-
|
155
|
-
return if other_result.errors.empty?
|
80
|
+
normalized = normalize_status(status)
|
156
81
|
|
157
|
-
|
158
|
-
end # method update_errors
|
82
|
+
return normalized if defined_statuses.include?(normalized)
|
159
83
|
|
160
|
-
|
161
|
-
|
84
|
+
raise ArgumentError, "invalid status #{status.inspect}"
|
85
|
+
end
|
162
86
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
end
|
87
|
+
def tools
|
88
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/cuprum/rspec.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rspec/be_a_result_matcher'
|
4
|
+
|
5
|
+
module RSpec
|
6
|
+
module Matchers # rubocop:disable Style/Documentation
|
7
|
+
def be_a_failing_result
|
8
|
+
be_a_result.with_status(:failure)
|
9
|
+
end
|
10
|
+
|
11
|
+
def be_a_passing_result
|
12
|
+
be_a_result.with_status(:success)
|
13
|
+
end
|
14
|
+
|
15
|
+
def be_a_result
|
16
|
+
Cuprum::RSpec::BeAResultMatcher.new
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/errors/operation_not_called'
|
4
|
+
require 'cuprum/rspec'
|
5
|
+
|
6
|
+
module Cuprum::RSpec
|
7
|
+
# Custom matcher that asserts the actual object is a Cuprum result object with
|
8
|
+
# the specified properties.
|
9
|
+
class BeAResultMatcher # rubocop:disable Metrics/ClassLength
|
10
|
+
DEFAULT_VALUE = Object.new.freeze
|
11
|
+
private_constant :DEFAULT_VALUE
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@expected_error = DEFAULT_VALUE
|
15
|
+
@expected_value = DEFAULT_VALUE
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [String] a short description of the matcher and expected
|
19
|
+
# properties.
|
20
|
+
def description
|
21
|
+
message = 'be a Cuprum result'
|
22
|
+
|
23
|
+
return message unless expected_properties?
|
24
|
+
|
25
|
+
"#{message} #{properties_description}"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Checks that the given actual object is not a Cuprum result.
|
29
|
+
#
|
30
|
+
# @param actual [Object] The actual object to match.
|
31
|
+
#
|
32
|
+
# @return [Boolean] false if the actual object is a result; otherwise true.
|
33
|
+
def does_not_match?(actual)
|
34
|
+
@actual = actual
|
35
|
+
|
36
|
+
raise ArgumentError, negated_matcher_warning if expected_properties?
|
37
|
+
|
38
|
+
!actual_is_result?
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [String] a summary message describing a failed expectation.
|
42
|
+
def failure_message
|
43
|
+
message = "expected #{actual.inspect} to #{description}"
|
44
|
+
|
45
|
+
if !actual_is_result?
|
46
|
+
message + ', but the object is not a result'
|
47
|
+
elsif actual_is_uncalled_operation?
|
48
|
+
message + ', but the object is an uncalled operation'
|
49
|
+
elsif !properties_match?
|
50
|
+
message + properties_failure_message
|
51
|
+
else
|
52
|
+
# :nocov:
|
53
|
+
message
|
54
|
+
# :nocov:
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [String] a summary message describing a failed negated
|
59
|
+
# expectation.
|
60
|
+
def failure_message_when_negated
|
61
|
+
"expected #{actual.inspect} not to #{description}"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Checks that the given actual object is a Cuprum result or compatible
|
65
|
+
# object and has the specified properties.
|
66
|
+
#
|
67
|
+
# @param actual [Object] The actual object to match.
|
68
|
+
#
|
69
|
+
# @return [Boolean] true if the actual object is a result with the expected
|
70
|
+
# properties; otherwise false.
|
71
|
+
def matches?(actual)
|
72
|
+
@actual = actual
|
73
|
+
|
74
|
+
actual_is_result? && !actual_is_uncalled_operation? && properties_match?
|
75
|
+
end
|
76
|
+
|
77
|
+
# Sets an error expectation on the matcher. Calls to #matches? will fail
|
78
|
+
# unless the actual object has the specified error.
|
79
|
+
#
|
80
|
+
# @param error [Cuprum::Error, Object] The expected error.
|
81
|
+
#
|
82
|
+
# @return [BeAResultMatcher] the updated matcher.
|
83
|
+
def with_error(error)
|
84
|
+
@expected_error = error
|
85
|
+
|
86
|
+
self
|
87
|
+
end
|
88
|
+
alias_method :and_error, :with_error
|
89
|
+
|
90
|
+
# Sets a status expectation on the matcher. Calls to #matches? will fail
|
91
|
+
# unless the actual object has the specified status.
|
92
|
+
#
|
93
|
+
# @param status [Symbol] The expected status.
|
94
|
+
#
|
95
|
+
# @return [BeAResultMatcher] the updated matcher.
|
96
|
+
def with_status(status)
|
97
|
+
@expected_status = status
|
98
|
+
|
99
|
+
self
|
100
|
+
end
|
101
|
+
alias_method :and_status, :with_status
|
102
|
+
|
103
|
+
# Sets a value expectation on the matcher. Calls to #matches? will fail
|
104
|
+
# unless the actual object has the specified value.
|
105
|
+
#
|
106
|
+
# @param value [Object] The expected value.
|
107
|
+
#
|
108
|
+
# @return [BeAResultMatcher] the updated matcher.
|
109
|
+
def with_value(value)
|
110
|
+
@expected_value = value
|
111
|
+
|
112
|
+
self
|
113
|
+
end
|
114
|
+
alias_method :and_value, :with_value
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
attr_reader \
|
119
|
+
:actual,
|
120
|
+
:expected_error,
|
121
|
+
:expected_status,
|
122
|
+
:expected_value
|
123
|
+
|
124
|
+
def actual_is_result?
|
125
|
+
actual.respond_to?(:to_cuprum_result)
|
126
|
+
end
|
127
|
+
|
128
|
+
def actual_is_uncalled_operation?
|
129
|
+
result.error.is_a?(Cuprum::Errors::OperationNotCalled)
|
130
|
+
end
|
131
|
+
|
132
|
+
def compare_items(expected, actual)
|
133
|
+
return expected.matches?(actual) if expected.respond_to?(:matches?)
|
134
|
+
|
135
|
+
expected == actual
|
136
|
+
end
|
137
|
+
|
138
|
+
def error_failure_message
|
139
|
+
return '' if error_matches?
|
140
|
+
|
141
|
+
"\n expected error: #{inspect_expected(expected_error)}" \
|
142
|
+
"\n actual error: #{result.error.inspect}"
|
143
|
+
end
|
144
|
+
|
145
|
+
def error_matches?
|
146
|
+
return @error_matches unless @error_matches.nil?
|
147
|
+
|
148
|
+
return @error_matches = true unless expected_error?
|
149
|
+
|
150
|
+
@error_matches = compare_items(expected_error, result.error)
|
151
|
+
end
|
152
|
+
|
153
|
+
def expected_properties?
|
154
|
+
expected_error? || expected_status? || expected_value?
|
155
|
+
end
|
156
|
+
|
157
|
+
def expected_error?
|
158
|
+
expected_error != DEFAULT_VALUE
|
159
|
+
end
|
160
|
+
|
161
|
+
def expected_status?
|
162
|
+
!!expected_status
|
163
|
+
end
|
164
|
+
|
165
|
+
def expected_value?
|
166
|
+
expected_value != DEFAULT_VALUE
|
167
|
+
end
|
168
|
+
|
169
|
+
def inspect_expected(expected)
|
170
|
+
return expected.description if expected.respond_to?(:description)
|
171
|
+
|
172
|
+
expected.inspect
|
173
|
+
end
|
174
|
+
|
175
|
+
def negated_matcher_warning
|
176
|
+
"Using `expect().not_to be_a_result#{properties_warning}` risks false" \
|
177
|
+
' positives, since any other result will match.'
|
178
|
+
end
|
179
|
+
|
180
|
+
def properties_description # rubocop:disable Metrics/AbcSize
|
181
|
+
msg = ''
|
182
|
+
ary = []
|
183
|
+
ary << 'value' if expected_value?
|
184
|
+
ary << 'error' if expected_error?
|
185
|
+
|
186
|
+
unless ary.empty?
|
187
|
+
msg = "with the expected #{tools.array.humanize_list(ary)}"
|
188
|
+
end
|
189
|
+
|
190
|
+
return msg unless expected_status?
|
191
|
+
|
192
|
+
return "with status: #{expected_status.inspect}" if msg.empty?
|
193
|
+
|
194
|
+
msg + " and status: #{expected_status.inspect}"
|
195
|
+
end
|
196
|
+
|
197
|
+
def properties_failure_message
|
198
|
+
properties_short_message +
|
199
|
+
status_failure_message +
|
200
|
+
value_failure_message +
|
201
|
+
error_failure_message
|
202
|
+
end
|
203
|
+
|
204
|
+
def properties_match?
|
205
|
+
error_matches? && status_matches? && value_matches?
|
206
|
+
end
|
207
|
+
|
208
|
+
def properties_short_message
|
209
|
+
ary = []
|
210
|
+
ary << 'status' unless status_matches?
|
211
|
+
ary << 'value' unless value_matches?
|
212
|
+
ary << 'error' unless error_matches?
|
213
|
+
|
214
|
+
", but the #{tools.array.humanize_list(ary)}" \
|
215
|
+
" #{tools.integer.pluralize(ary.size, 'does', 'do')} not match:"
|
216
|
+
end
|
217
|
+
|
218
|
+
def properties_warning
|
219
|
+
ary = []
|
220
|
+
ary << 'value' if expected_value?
|
221
|
+
ary << 'status' if expected_status?
|
222
|
+
ary << 'error' if expected_error?
|
223
|
+
|
224
|
+
return '' if ary.empty?
|
225
|
+
|
226
|
+
message = ".with_#{ary.first}()"
|
227
|
+
|
228
|
+
return message if ary.size == 1
|
229
|
+
|
230
|
+
message + ary[1..-1].map { |str| ".and_#{str}()" }.join
|
231
|
+
end
|
232
|
+
|
233
|
+
def result
|
234
|
+
@result ||= actual.to_cuprum_result
|
235
|
+
end
|
236
|
+
|
237
|
+
def status_failure_message
|
238
|
+
return '' if status_matches?
|
239
|
+
|
240
|
+
"\n expected status: #{expected_status.inspect}" \
|
241
|
+
"\n actual status: #{result.status.inspect}"
|
242
|
+
end
|
243
|
+
|
244
|
+
def status_matches?
|
245
|
+
return @status_matches unless @status_matches.nil?
|
246
|
+
|
247
|
+
return @status_matches = true unless expected_status?
|
248
|
+
|
249
|
+
@status_matches = result.status == expected_status
|
250
|
+
end
|
251
|
+
|
252
|
+
def tools
|
253
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
254
|
+
end
|
255
|
+
|
256
|
+
def value_failure_message
|
257
|
+
return '' if value_matches?
|
258
|
+
|
259
|
+
"\n expected value: #{inspect_expected(expected_value)}" \
|
260
|
+
"\n actual value: #{result.value.inspect}"
|
261
|
+
end
|
262
|
+
|
263
|
+
def value_matches?
|
264
|
+
return @value_matches unless @value_matches.nil?
|
265
|
+
|
266
|
+
return @value_matches = true unless expected_value?
|
267
|
+
|
268
|
+
@value_matches = compare_items(expected_value, result.value)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|