brine-dsl 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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