brine-dsl 0.11.0 → 0.12.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.
@@ -1,14 +1,13 @@
1
1
  ##
2
2
  # @file selecting.rb
3
- # Selection of one or more values to be used by Brine (normally for assertion).
3
+ # Support selection of one or more values to be used by Brine (normally for assertion).
4
4
  ##
5
-
6
5
  require 'brine/transforming.rb'
7
6
 
8
7
  module Brine
9
8
 
10
9
  ##
11
- # A module providing assorted means to select particular values out of objects/graphs.
10
+ # Provide assorted means to select particular values out of objects/graphs.
12
11
  ##
13
12
  module Selecting
14
13
 
@@ -17,7 +16,7 @@ module Brine
17
16
  require 'jsonpath'
18
17
 
19
18
  ##
20
- # An object responsible for selecting one or more values.
19
+ # Allow selection of one or more values.
21
20
  # This Selector will test whether the targeted value itself satisfies the assertion.
22
21
  #
23
22
  # RSpec is used within this implementation to perform assertions.
@@ -29,15 +28,15 @@ module Brine
29
28
  include RSpec::Matchers
30
29
 
31
30
  ##
32
- # [Coercer] The Coercer that may modify values prior to performing assertions.
31
+ # [Coercer] Expose the Coercer that may modify values prior to performing assertions.
33
32
  ##
34
33
  attr_accessor :coercer
35
34
 
36
35
  ##
37
36
  # Construct a selector to perform assertions against a provided target.
38
37
  #
39
- # @param [Object] taret The value against which assertions will be performed.
40
- # @param [Boolean] negated Whether the assertions from this selector should be negated.
38
+ # @param target [Object] Provide the value against which assertions will be performed.
39
+ # @param negated [Boolean] Specify whether the assertions from this selector should be negated.
41
40
  # This is deprecated and should instead be passed to #assert_that.
42
41
  ##
43
42
  def initialize(target, negated=false)
@@ -49,10 +48,11 @@ module Brine
49
48
  # Optionally perform some modification to the RSpec matcher prior to assertion.
50
49
  #
51
50
  # This is designed to allow subclassess to be able to modify the way
52
- # in which matchers are applied against the values. This method is a pass-through in this class.
51
+ # in which matchers are applied against the values.
52
+ # The default implementation is a passthrough.
53
53
  #
54
- # @param [RSpec::Matcher] matcher The matcher originally passed to #assert_that.
55
- # @return [RSpec::Matcher] The Matcher to be used while performing the assertion.
54
+ # @param matcher [RSpec::Matcher] Relay the matcher originally passed to #assert_that.
55
+ # @return [RSpec::Matcher] Return the Matcher to use while performing the assertion.
56
56
  ##
57
57
  def filter_matcher(matcher)
58
58
  matcher
@@ -63,16 +63,17 @@ module Brine
63
63
  #
64
64
  # The values will be coerced prior to evaluation.
65
65
  #
66
- # @param [Object] value The value/parameter against which the target will be compared.
66
+ # @param value [Object] Specify the value/parameter against which the target will be compared.
67
67
  # In cases where no parameter is needed for comparison, this may be nil.
68
- # @param [Boolean] negated If true the assertion should be expected to _fail_, otherwise it should pass.
69
- # @param [Block] A block which will be passed a coerced copy of `value` and which should return an
68
+ # @param binding [Object] Provide the binding environment which will be used to expand any templates
69
+ # @param negated [Boolean] Specify whether the assertion should be expected to _fail_, if false it should pass.
70
+ # @param [Block] Define the logic which will be passed a coerced copy of `value` and which should return an
70
71
  # RSpec matcher which will be evaluated against the coerced target value.
71
72
  ##
72
- def assert_that(value, negated=nil)
73
+ def assert_that(value, binding, negated=nil)
73
74
  # shim while moving negation to assertions.
74
75
  negated = @negated if negated.nil?
75
- target, value = coercer.coerce(expand(@target), expand(value))
76
+ target, value = coercer.coerce(expand(@target, binding), expand(value, binding))
76
77
  message = negated ? :to_not : :to
77
78
  matcher = filter_matcher(yield(value))
78
79
  expect(target).send(message, matcher)
@@ -81,7 +82,7 @@ module Brine
81
82
  end
82
83
 
83
84
  ##
84
- # A Selector which will test whether any of the children of the targeted value satisfy the assertion.
85
+ # Define a Selector which will test whether any of the children of the targeted value satisfy the assertion.
85
86
  ##
86
87
  class AnySelector < Selector
87
88
  def filter_matcher(matcher)
@@ -90,7 +91,7 @@ module Brine
90
91
  end
91
92
 
92
93
  ##
93
- # A Selector which will test whether all of the children of the targeted value satisfy the assertion.
94
+ # Define a Selector which will test whether all of the children of the targeted value satisfy the assertion.
94
95
  ##
95
96
  class AllSelector < Selector
96
97
  def filter_matcher(matcher)
@@ -101,8 +102,8 @@ module Brine
101
102
  ##
102
103
  # Activate a Selector for the provided target.
103
104
  #
104
- # @param [Object] target The value which the Selector should target.
105
- # @param [Boolean] negated DEPRECATED If true the assertions should be expected to fail.
105
+ # @param target [Object] Provide the value which the Selector should target.
106
+ # @param negated [Boolean] Specify whether the assertions should be expected to fail (DEPRECATED).
106
107
  ##
107
108
  def select(target, negated=nil)
108
109
  use_selector(Selector.new(target, negated))
@@ -111,8 +112,8 @@ module Brine
111
112
  ##
112
113
  # Activate a Selector for any of the children of the provided target.
113
114
  #
114
- # @param [Object] target The value which the Selector should target.
115
- # @param [Boolean] negated DEPRECATED If true the assertions should be expected to fail.
115
+ # @param target [Object] Provide the value which the Selector should target.
116
+ # @param negated [Boolean] Specify whether the assertions should be expected to fail (DEPRECATED).
116
117
  ##
117
118
  def select_any(target, negated=nil)
118
119
  use_selector(AnySelector.new(target, negated))
@@ -121,8 +122,8 @@ module Brine
121
122
  ##
122
123
  # Activate a Selector for all of the children of the provided target.
123
124
  #
124
- # @param [Object] target The value which the Selector should target.
125
- # @param [Boolean] negated DEPRECATED If true the assertions should be expected to fail.
125
+ # @param target [Object] Provide the value which the Selector should target.
126
+ # @param negated [Boolean] Specify whether the assertions should be expected to fail (DEPRECATED).
126
127
  ##
127
128
  def select_all(target, negated=nil)
128
129
  use_selector(AllSelector.new(target, negated))
@@ -131,7 +132,7 @@ module Brine
131
132
  ##
132
133
  # Configure selector and make it the active Selector.
133
134
  #
134
- # @param [Selector] selector The selector which will be activated.
135
+ # @param selector [Selector] Provide the selector which will be activated.
135
136
  ##
136
137
  def use_selector(selector)
137
138
  selector.coercer = coercer
@@ -139,32 +140,235 @@ module Brine
139
140
  end
140
141
 
141
142
  ##
142
- # The currently active Selector.
143
+ # Return the currently active Selector.
143
144
  #
144
- # @return The Selector as set by #use_selector
145
+ # @return [Selector] Return the Selector as set by #use_selector.
145
146
  ##
146
147
  def selector
147
148
  @selector
148
149
  end
149
150
 
151
+ ##
152
+ # Capture a path for which the value will be retrieved from a root.
153
+ ##
154
+ class Traversal
155
+
156
+ ##
157
+ # Return a traversal for the provided path.
158
+ #
159
+ # @param path [String] Define the JSONPath for the location of the value(s) to retrieve.
160
+ # @param is_plural [Boolean] Specify whether a collection should be returned for possibly multiple values,
161
+ # false if only a single value should be expected/returned.
162
+ ##
163
+ def initialize(path, is_plural)
164
+ @path = path
165
+ @message = is_plural ? :on : :first
166
+ end
167
+
168
+ ##
169
+ # Return values out of the provided root based on the traversal definition.
170
+ #
171
+ # @param root [String] Provide the JSON structure from which the value will be retrieved.
172
+ # @return [Object] Return the value or values (if is_plural) at the defined path or nil if none found.
173
+ ##
174
+ def visit(root)
175
+ !@path ? root : JsonPath.new("$.#{@path}").send(@message, root)
176
+ end
177
+ end
178
+
179
+ ##
180
+ # Return a Traversal based on the provided arguments.
181
+ #
182
+ # This primarily exists as the exported interface to retrieve a Traversal instance.
183
+ #
184
+ # @param path [String] Define the JSONPath to which the traversal should descend.
185
+ # @param is_plural [Object] Specify whether to produce a traversal for a collection of all matches:
186
+ # nil will target only a single match, any non-nil value will target all matches.
187
+ # @return [Traversal] Return a Traversal configured based on the provided arguments.
188
+ ##
189
+ def traversal(path, is_plural)
190
+ Traversal.new(path, !is_plural.nil?)
191
+ end
150
192
  end
151
193
 
152
194
  # Mix the Selecting module functionality into the main Brine module.
153
195
  include Selecting
196
+
154
197
  end
155
198
 
199
+ ##
200
+ # Retrieve the requested attribute from the current response.
156
201
  #
157
- # Steps
202
+ # @param attribute [String] Specify the attribute to return from the response.
203
+ # @return [Object] Return the value of the attribute for the current response.
204
+ ##
205
+ def response_attribute(attribute)
206
+ response.send(attribute.to_sym)
207
+ end
208
+
209
+
210
+ ##
211
+ # Extract assertion step text.
212
+ ##
213
+ ParameterType(
214
+ name: 'assertion',
215
+ regexp: /.*[^:]/,
216
+ transformer: -> (input) { input }
217
+ )
218
+
219
+ ##
220
+ # Extract the text for a standard HTTP method.
221
+ ##
222
+ ParameterType(
223
+ name: 'http_method',
224
+ regexp: /(DELETE|GET|HEAD|OPTIONS|PATCH|POST|PUT)/,
225
+ transformer: -> (input) { input }
226
+ )
227
+
228
+ ##
229
+ # Indicate whether not is present.
230
+ ##
231
+ ParameterType(
232
+ name: 'maybe_not',
233
+ regexp: /( not)?/,
234
+ transformer: -> (input=nil) { !input.nil? }
235
+ )
236
+
237
+ ##
238
+ # Extract the text for a supported response attribute.
239
+ ##
240
+ ParameterType(
241
+ name: 'response_attribute',
242
+ regexp: /(status|headers|body)/,
243
+ transformer: -> (input) { input }
244
+ )
245
+
246
+ # Messing with the parameters is due to cucumber expressions
247
+ # not working properly...that should be chased down.
248
+
249
+ ##
250
+ # Produce a Traversal for one or all elements at the specified path.
251
+ ##
252
+ ParameterType(
253
+ name: 'traversal',
254
+ regexp: /(?: child(ren)? `([^`]*)`)?/,
255
+ transformer: -> (plural_or_path=nil, path=nil) {
256
+ if path.nil?
257
+ path=plural_or_path
258
+ plural_or_path=nil
259
+ end
260
+ traversal(path, plural_or_path)
261
+ }
262
+ )
263
+
264
+ ##
265
+ # Select a directly provided value.
158
266
  #
159
- RESPONSE_ATTRIBUTES='(status|headers|body)'
160
- Then(/^the value of `([^`]*)` is( not)? (.*)$/) do |value, negated, assertion|
161
- select(value, (!negated.nil?))
267
+ # @param value [Object] Provide the value for which the assertion will be evaluated.
268
+ # @param negated [Object] Indicate whether the assertion should be exected to fail.
269
+ # @param assertion [String] Provide the assertion step to evaluate.
270
+ ##
271
+ Then('the value of {grave_param} is{maybe_not} {assertion}') do |value, negated, assertion|
272
+ select(value, negated)
162
273
  # Stringify in case the assertion clause is a template.
163
- step "it is #{assertion.to_s}"
274
+ step "it is #{assertion}"
275
+ end
276
+
277
+ ##
278
+ # Evaluate an assertion against a response attribute.
279
+ #
280
+ # @param attribute [String] Indicate from which response attribute the value will be selected.
281
+ # @param traversal [Traversal] Provide a Traversal to retrieve the value from the attribute.
282
+ # @param negated [Boolean] Specify whether the assertion should be expected to fail (DEPRECATED).
283
+ # @param assertion [Object] Provide the assertion step to evaluate.
284
+ ##
285
+ Then('the value of the response {response_attribute}{traversal} is{maybe_not} {assertion}') do
286
+ |attribute, traversal, negated, assertion|
287
+ perform do
288
+ select(traversal.visit(response_attribute(attribute)), negated)
289
+ step "it is #{assertion}"
290
+ end
291
+ end
292
+
293
+
294
+ ##
295
+ # Evaluate an assertion against a response attribute and provide a docstring parameter.
296
+ #
297
+ # @param attribute [String] Indicate from which response attribute the value will be selected.
298
+ # @param traversal [Traversal] Provide a Traversal to retrieve the value from the attribute.
299
+ # @param negated [Boolean] Specify whether the assertion should be expected to fail (DEPRECATED).
300
+ # @param assertion [Object] Provide the assertion step to evaluate.
301
+ # @param multi [String] Pass a docstring parameter which will be forwarded to the assertion.
302
+ ##
303
+ Then('the value of the response {response_attribute}{traversal} is{maybe_not} {assertion}:') do
304
+ |attribute, traversal, negated, assertion, multi|
305
+ perform do
306
+ select(traversal.visit(response_attribute(attribute)), negated)
307
+ step "it is #{assertion}:", multi
308
+ end
309
+ end
310
+
311
+ ##
312
+ # Evaluate an assertion against at least one matched value from the response attribute.
313
+ #
314
+ # @param attribute [String] Indicate from which response attribute the values will be selected.
315
+ # @param traversal [Traversal] Provide a Traversal to retrieve values from the attribute.
316
+ # @param negated [Boolean] Specify whether the assertion should be expected to fail (DEPRECATED).
317
+ # @param assertion [Object] Provide the assertion step to evaluate.
318
+ ##
319
+ Then('the value of the response {response_attribute}{traversal} does{maybe_not} have any element that is {assertion}') do
320
+ |attribute, traversal, negated, assertion|
321
+ perform do
322
+ select_any(traversal.visit(response_attribute(attribute)), negated)
323
+ step "it is #{assertion}"
324
+ end
325
+ end
326
+
327
+ ##
328
+ # Evaluate an assertion against at least one matched value from the response attribute and provide a docstring parameter.
329
+ #
330
+ # @param attribute [String] Indicate from which response attribute the values will be selected.
331
+ # @param traversal [Traversal] Provide a Traversal to retrieve values from the attribute.
332
+ # @param negated [Boolean] Specify whether the assertion should be expected to fail (DEPRECATED).
333
+ # @param assertion [Object] Provide the assertion step to evaluate.
334
+ # @param multi [String] Pass a docstring parameter which will be forwarded to the assertion.
335
+ ##
336
+ Then('the value of the response {response_attribute}{traversal} does{maybe_not} have any element that is {assertion}:') do
337
+ |attribute, traversal, negated, assertion, multi|
338
+ perform do
339
+ select_any(traversal.visit(response_attribute(attribute)), negated)
340
+ step "it is #{assertion}", multi
341
+ end
342
+ end
343
+
344
+ ##
345
+ # Evaluate an assertion against ALL matched value from the response attribute.
346
+ #
347
+ # @param attribute [String] Indicate from which response attribute the values will be selected.
348
+ # @param traversal [Traversal] Provide a Traversal to retrieve values from the attribute.
349
+ # @param assertion [Object] Provide the assertion step to evaluate.
350
+ ##
351
+ Then('the value of the response {response_attribute}{traversal} has elements which are all {assertion}') do
352
+ |attribute, traversal, assertion|
353
+ perform do
354
+ select_all(traversal.visit(response_attribute(attribute)), false)
355
+ step "it is #{assertion}"
356
+ end
164
357
  end
165
358
 
166
- def dig_from_response(attribute, path=nil, plural=false)
167
- root = response.send(attribute.to_sym)
168
- return root if !path
169
- JsonPath.new("$.#{path}").send(plural ? :on : :first, expand(root))
359
+ ##
360
+ # Evaluate an assertion against ALL matched value from the response attribute and provide a docstring argument.
361
+ #
362
+ # @param attribute [String] Indicate from which response attribute the values will be selected.
363
+ # @param traversal [Traversal] Provide a Traversal to retrieve values from the attribute.
364
+ # @param assertion [Object] Provide the assertion step to evaluate.
365
+ # @param multi [String] Pass a docstring parameter which will be forwarded to the assertion.
366
+ ##
367
+ Then('the value of the response {response_attribute}{traversal} has elements which are all {assertion}:') do
368
+ |attribute, traversal, assertion, multi|
369
+ perform do
370
+ select_all(traversal.visit(response_attribute(attribute)), false)
371
+ step "it is #{assertion}", multi.to_json
372
+ end
170
373
  end
374
+
@@ -3,7 +3,8 @@ require 'rspec'
3
3
  # Steps used to test this library
4
4
  # Not loaded by default (except in the tests)
5
5
  #
6
- HTTP_METHOD='GET|POST|PATCH|PUT|DELETE|HEAD|OPTIONS'
6
+
7
+ require 'brine/selecting'
7
8
 
8
9
  ENV['BRINE_DURATION_SECONDS_short'] = '3'
9
10
  ENV['BRINE_DURATION_SECONDS_long'] = '6'
@@ -105,60 +106,56 @@ Before do
105
106
  end
106
107
  end
107
108
 
108
- Given(/^expected response status of `([^`]*)`$/) do |status|
109
+ Given('expected response status of {grave_param}') do |status|
109
110
  stub.response.status = status
110
111
  end
111
112
 
112
- Given(/^expected response status sequence of `([^`]*)`$/) do |seq|
113
+ Given('expected response status sequence of {grave_param}') do |seq|
113
114
  @stub = ResponseStatusSequenceStubBuilder.new(stub, seq)
114
115
  end
115
116
 
116
- Given(/^expected request body:$/) do |body|
117
+ Given('expected request body:') do |body|
117
118
  stub.request.body = body
118
119
  end
119
120
 
120
- Given(/^expected request headers:$/) do |headers|
121
- stub.request.headers = headers
121
+ Given('expected request headers:') do |headers|
122
+ stub.request.headers = transformed_parameter(headers)
122
123
  end
123
124
 
124
- Given(/^expected (#{HTTP_METHOD}) sent to `([^`]*)`/) do |method, path|
125
+ Given('expected {http_method} sent to {grave_param}') do |method, path|
125
126
  stub.request.method = method
126
127
  stub.request.path = path
127
128
  build_stub
128
129
  end
129
130
 
130
- When(/^the response body is assigned:$/) do |input|
131
+ When('the response body is assigned:') do |input|
131
132
  @response ||= StubResponse.new
132
- @response.body = input
133
+ @response.body = transformed_parameter(input)
133
134
  end
134
135
 
135
- When(/^the response headers is assigned `([^`]*)`$/) do |input|
136
+ When('the response headers is assigned {grave_param}') do |input|
136
137
  @response ||= StubResponse.new
137
138
  @response.headers = input
138
139
  end
139
140
 
140
- When(/^the response body is assigned `([^`]*)`/) do |input|
141
+ When('the response body is assigned {grave_param}') do |input|
141
142
  @response ||= StubResponse.new
142
143
  @response.body = input
143
144
  end
144
145
 
145
- When(/^the response body is:$/) do |input|
146
- replaced_with('When', 'the response body is assigned:', '1.0.0', input.to_json)
147
- end
148
-
149
- When /^the response status is assigned `([^`]*)`$/ do |status|
146
+ When('the response status is assigned {grave_param}') do |status|
150
147
  @response ||= StubResponse.new
151
148
  @response.status = status.to_i # this coercion isn't needed but is a guarantee
152
149
  end
153
150
 
154
- When /^the response is delayed `([^`]*)` seconds$/ do |seconds|
151
+ When('the response is delayed {int} seconds') do |seconds|
155
152
  @response = DelayedStubResponse.new(seconds)
156
153
  end
157
154
 
158
- Then(/^the response body as JSON is:$/) do |text|
155
+ Then('the response body as JSON is:') do |text|
159
156
  expect(response.body.to_json).to eq text
160
157
  end
161
158
 
162
- Then(/^expected calls are verified$/) do
159
+ Then('expected calls are verified') do
163
160
  $stubs.verify_stubbed_calls
164
161
  end