brine-dsl 0.5.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 (72) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +11 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +123 -0
  7. data/Guardfile +12 -0
  8. data/LICENSE +21 -0
  9. data/README.md +137 -0
  10. data/Rakefile +32 -0
  11. data/brine-dsl.gemspec +32 -0
  12. data/config/cucumber.yml +2 -0
  13. data/docs/build.gradle +19 -0
  14. data/docs/cookbook.html +567 -0
  15. data/docs/gradle/wrapper/gradle-wrapper.jar +0 -0
  16. data/docs/gradle/wrapper/gradle-wrapper.properties +6 -0
  17. data/docs/gradlew +172 -0
  18. data/docs/gradlew.bat +84 -0
  19. data/docs/guide.html +1149 -0
  20. data/docs/index.html +472 -0
  21. data/docs/specs.html +1672 -0
  22. data/docs/src/cookbook.adoc +87 -0
  23. data/docs/src/guide.adoc +427 -0
  24. data/docs/src/index.adoc +16 -0
  25. data/docs/src/spec.erb +121 -0
  26. data/docs/src/specs.adoc +24 -0
  27. data/features/argument_transforms/boolean.feature +37 -0
  28. data/features/argument_transforms/datetime.feature +45 -0
  29. data/features/argument_transforms/integer.feature +41 -0
  30. data/features/argument_transforms/list.feature +46 -0
  31. data/features/argument_transforms/object.feature +66 -0
  32. data/features/argument_transforms/quoted.feature +41 -0
  33. data/features/argument_transforms/regex.feature +40 -0
  34. data/features/argument_transforms/template.feature +46 -0
  35. data/features/argument_transforms/whitespace.feature +51 -0
  36. data/features/assertions/is_a_valid.feature +184 -0
  37. data/features/assertions/is_equal_to.feature +60 -0
  38. data/features/assertions/is_including.feature +29 -0
  39. data/features/assertions/is_matching.feature +35 -0
  40. data/features/deprecations/replaced_with.feature +35 -0
  41. data/features/request_construction/basic.feature +29 -0
  42. data/features/request_construction/body.feature +26 -0
  43. data/features/request_construction/clearing.feature +46 -0
  44. data/features/request_construction/headers.feature +94 -0
  45. data/features/request_construction/params.feature +60 -0
  46. data/features/resource_cleanup/cleanup.feature +86 -0
  47. data/features/selectors/all.feature +55 -0
  48. data/features/selectors/any.feature +48 -0
  49. data/features/step_definitions/test_steps.rb +5 -0
  50. data/features/support/env.rb +10 -0
  51. data/lib/brine/cleaner_upper.rb +62 -0
  52. data/lib/brine/coercer.rb +18 -0
  53. data/lib/brine/hooks.rb +4 -0
  54. data/lib/brine/mustache_binder.rb +25 -0
  55. data/lib/brine/requester.rb +125 -0
  56. data/lib/brine/rest_steps.rb +138 -0
  57. data/lib/brine/selector.rb +66 -0
  58. data/lib/brine/step_definitions/assertions.rb +37 -0
  59. data/lib/brine/step_definitions/assignment.rb +13 -0
  60. data/lib/brine/step_definitions/cleanup.rb +4 -0
  61. data/lib/brine/step_definitions/request_construction.rb +19 -0
  62. data/lib/brine/step_definitions/selection.rb +37 -0
  63. data/lib/brine/test_steps.rb +138 -0
  64. data/lib/brine/transforms.rb +81 -0
  65. data/lib/brine/type_checks.rb +35 -0
  66. data/lib/brine/util.rb +35 -0
  67. data/lib/brine.rb +39 -0
  68. data/tutorial/missing.feature +5 -0
  69. data/tutorial/post_matching.feature +12 -0
  70. data/tutorial/post_status.feature +10 -0
  71. data/tutorial/support/env.rb +2 -0
  72. metadata +306 -0
@@ -0,0 +1,125 @@
1
+ # requester.rb - Provide request construction and response storage
2
+
3
+ require 'oauth2'
4
+ require 'faraday_middleware'
5
+
6
+ # Parameter object used to configure OAuth2 middleware
7
+ # Also used to provide basic DSL for configuration
8
+ class OAuth2Params
9
+ attr_accessor :token, :token_type
10
+
11
+ def initialize
12
+ @token_type = 'bearer'
13
+ end
14
+
15
+ def fetch_from(id, secret, opts)
16
+ @token = OAuth2::Client.new(id, secret, opts)
17
+ .client_credentials.get_token.token
18
+ end
19
+ end
20
+
21
+ # Construct a Faraday client to be used to send built requests
22
+ module ClientBuilding
23
+
24
+ # authenticate using provided info and save token for use in later requests
25
+ def use_oauth2_token(&block)
26
+ @oauth2 = OAuth2Params.new
27
+ @oauth2.instance_eval(&block)
28
+ end
29
+
30
+ def with_oauth2_token(&block)
31
+ use_oauth2_token(&block)
32
+ self
33
+ end
34
+
35
+ def client_for_host(host, logging: ENV['BRINE_LOG_HTTP'])
36
+ Faraday.new(host) do |conn|
37
+ conn.request :json
38
+
39
+ if @oauth2
40
+ conn.request :oauth2, @oauth2.token, :token_type => @oauth2.token_type
41
+ end
42
+
43
+ if logging
44
+ conn.response :logger, nil, :bodies => (logging.casecmp('DEBUG') == 0)
45
+ end
46
+
47
+ conn.response :json, :content_type => /\bjson$/
48
+
49
+ conn.adapter Faraday.default_adapter
50
+ end
51
+ end
52
+ end
53
+
54
+ class ClientBuilder
55
+ include ClientBuilding
56
+ end
57
+
58
+ # Module in charge of constructing requests and saving responses
59
+ module Requesting
60
+ include ClientBuilding
61
+
62
+ # Utility Methods
63
+ #
64
+ # Normalize an HTTP method passed from a specification into a form
65
+ # expected by the HTTP client library (lowercased symbol)
66
+ def parse_method(method)
67
+ method.downcase.to_sym
68
+ end
69
+
70
+ def set_client(client)
71
+ @client = client
72
+ end
73
+
74
+ # return Faraday client object so that it could be used directly
75
+ # or passed to another object
76
+ def client
77
+ @client ||= client_for_host((ENV['ROOT_URL'] || 'http://localhost:8080'),
78
+ logging: ENV['BRINE_LOG_HTTP'])
79
+ end
80
+
81
+ # clear out any previously built request state and set defaults
82
+ def reset_request
83
+ @params = @headers = @body = nil
84
+ end
85
+
86
+ # store the provided body in the request options being built
87
+ # overriding any previously provided object
88
+ def set_request_body(obj)
89
+ @body = obj
90
+ end
91
+
92
+ # send a request using method to url using whatever options
93
+ # have been built for the present request
94
+ def send_request(method, url)
95
+ @response = client.run_request(method, url, @body, headers) do |req|
96
+ req.params = params
97
+ end
98
+ end
99
+
100
+ # getter for the latest response returned
101
+ def response
102
+ @response
103
+ end
104
+
105
+ def headers
106
+ @headers ||= {content_type: 'application/json'}
107
+ end
108
+
109
+ def add_header(k, v)
110
+ headers[k] = v
111
+ end
112
+
113
+ def params
114
+ @params ||= {}
115
+ end
116
+
117
+ def add_request_param(k, v)
118
+ params[k] = v
119
+ end
120
+
121
+ end
122
+
123
+ class Requester
124
+ include Requesting
125
+ end
@@ -0,0 +1,138 @@
1
+ require 'rspec'
2
+ require 'brine/util'
3
+ require 'brine/selector'
4
+ require 'jsonpath'
5
+
6
+ # Chopping Block
7
+ When(/^the request parameter `([^`]*)` is set to `([^`]*)`$/) do |param, value|
8
+ replaced_with('When', "the request query paramter `#{param}` is assigned `#{value}`", '0.6')
9
+ end
10
+ Then(/^the response body is the list:$/) do |table|
11
+ replaced_with('Then', "the value of the response body is equal to:\n\"\"\"#{table.hashes.to_json}\"\"\"", '0.6')
12
+ end
13
+ Then(/^the raw response body is:$/) do |text|
14
+ warn 'DEPRECATION: This step will be removed in version 0.6'
15
+ expect(response.body).to eq text
16
+ end
17
+ Then(/^the response body has `([^`]*)` with a value equal to `([^`]*)`$/) do |child, value|
18
+ replaced_with('Then', "the value of the response body child `#{child}` is equal to `#{value}`", '0.6')
19
+ end
20
+
21
+ Then(/^the response #{RESPONSE_ATTRIBUTES} has `([^`]*)` with a value including `([^`]*)`$/) do
22
+ |attribute, member, value|
23
+ replaced_with('Then', "the value of the response body child `#{child}` is including `#{value}`", '0.6')
24
+ end
25
+
26
+ # This file is legacy or unsorted steps which will be deprecated or moved into
27
+ # more appropriate homes
28
+
29
+ def not_if(val) val ? :not_to : :to end
30
+
31
+ # Return a table that that is a key value pair in a format ready for consumption
32
+ def kv_table(table)
33
+ transform_table!(table).rows_hash
34
+ end
35
+
36
+ Then(/^the response body is a list which all are (\w+)$/) do |matcher|
37
+ pass_it = method(matcher.to_sym).call
38
+ expect(response_body_child.first).to all(pass_it)
39
+ end
40
+
41
+ #TODO: The binding environment should be able to be accessed directly
42
+ # without requiring a custom step
43
+ When(/^`([^`]*)` is bound to `([^`]*)` from the response body$/) do |name, path|
44
+ binding[name] = response_body_child(path).first
45
+ end
46
+
47
+ #
48
+ # Response attribute (non-body) assertions
49
+ #
50
+ Then(/^the response #{RESPONSE_ATTRIBUTES} has `([^`]*)` with a value that is not empty$/) do
51
+ |attribute, member|
52
+ expect(response).to have_attributes(attribute.to_sym => include(member.to_sym => be_not_empty))
53
+ end
54
+
55
+ Then(/^the response #{RESPONSE_ATTRIBUTES} includes? the entries:$/) do |attribute, table|
56
+ expect(response).to have_attributes(attribute.to_sym => include(kv_table(table)))
57
+ end
58
+
59
+ Then(/^the response #{RESPONSE_ATTRIBUTES} contains? null fields:$/) do |attribute, table|
60
+ expect(response)
61
+ .to have_attributes(attribute.to_sym =>
62
+ include(table.raw.flatten.collect{|v| [v, be_nil]}.to_h))
63
+ end
64
+
65
+ Then(/^the response #{RESPONSE_ATTRIBUTES} contains? non null fields:$/) do |attribute, table|
66
+ expect(response)
67
+ .to have_attributes(attribute.to_sym =>
68
+ include(table.raw.flatten.collect{|v| [v, be_not_nil]}.to_h))
69
+ end
70
+
71
+ #
72
+ # Response body assertions
73
+ #
74
+ Then(/^the response body does not contain fields:$/) do |table|
75
+ expect(response_body_child.first.keys).to_not include(*table.raw.flatten)
76
+ end
77
+
78
+ Then(/^the response body has `([^`]*)` which (in|ex)cludes? the entries:$/) do
79
+ |child, in_or_ex, table|
80
+ expect(response_body_child(child).first)
81
+ .send(not_if(in_or_ex=='ex'),
82
+ include(kv_table(table)))
83
+ end
84
+
85
+ Then(/^the response body is a list of length (\d+)$/) do |length|
86
+ expect(response_body_child.first).to have_attributes(length: length)
87
+ end
88
+
89
+ #TODO: Maybe worth optimizing these 2 to O(n) after tests are in place
90
+ Then(/^the response body is a list sorted by `([^`]*)` ascending$/) do |path|
91
+ values = response_body_child(path)
92
+ expect(values).to eq values.sort{|a,b| a.to_s.downcase <=> b.to_s.downcase}
93
+ end
94
+
95
+ Then(/^the response body is a list sorted by `([^`]*)` descending$/) do |path|
96
+ values = response_body_child(path)
97
+ expect(values).to eq values.sort{|a,b| b.to_s.downcase <=> a.to_s.downcase}
98
+ end
99
+
100
+ Then(/^the response body is a list (with|without) an entry containing:$/) do |with, data|
101
+ expect(response_body_child.first)
102
+ .send(not_if(with == 'without'),
103
+ include(include(kv_table(data))))
104
+ end
105
+
106
+ Then(/^the response body is (\w+)$/) do |matcher|
107
+ pass_it = method(matcher.to_sym).call
108
+ expect(response_body_child.first).to pass_it
109
+ end
110
+
111
+ #
112
+ # Polling assertions
113
+ #
114
+ Then(/^a non\-empty list is eventually returned at `([^`]*)`$/) do |path|
115
+ retry_for(120, 3) do
116
+ send_request(parse_method('GET'), path)
117
+ expect(response_body_child.first).to_not be_empty
118
+ end
119
+ end
120
+
121
+ Then(/^the resource is eventually available at `([^`]*)`$/) do |path|
122
+ retry_for(120, 3) do
123
+ send_request(parse_method('GET'), path)
124
+ expect(response).to have_attributes(:status => 200)
125
+ end
126
+ end
127
+
128
+ # TODO: Parameterize polling length
129
+ Then(/^the property `([^`]*)` is eventually `([^`]*)` at `([^`]*)`$/) do |field, value, path|
130
+ retry_for(180) do
131
+ send_request(parse_method('GET'), path)
132
+ expect(response_body_child.first).to include(field => value)
133
+ end
134
+ end
135
+
136
+ def response_body_child(path="")
137
+ JsonPath.new("$.#{path}").on(response.body.to_json)
138
+ end
@@ -0,0 +1,66 @@
1
+ require 'brine/coercer'
2
+ require 'rspec/expectations'
3
+ require 'jsonpath'
4
+
5
+ # Selectors here are small wrappers around RSpec
6
+ # expectation behavior to encapsulate variations in
7
+ # some expecation associated behavior in classes.
8
+ class Selector
9
+ include RSpec::Matchers
10
+ attr_accessor :coercer
11
+
12
+ def initialize(target, negated)
13
+ @target = target
14
+ @message = negated ? :to_not : :to
15
+ end
16
+
17
+ def filter_matcher(matcher)
18
+ matcher
19
+ end
20
+
21
+ def assert_that(value)
22
+ target, value = coercer.coerce(@target, value)
23
+ matcher = filter_matcher(yield(value))
24
+ expect(target).send(@message, matcher)
25
+ end
26
+ end
27
+
28
+ class AnySelector < Selector
29
+ def filter_matcher(matcher)
30
+ include(matcher)
31
+ end
32
+ end
33
+
34
+ class AllSelector < Selector
35
+ def filter_matcher(matcher)
36
+ all(matcher)
37
+ end
38
+ end
39
+
40
+ #
41
+ # Module
42
+ #
43
+ module Selection
44
+ include Coercion
45
+ attr_reader :selector
46
+
47
+ def use_selector(selector)
48
+ selector.coercer = coercer
49
+ @selector = selector
50
+ end
51
+ end
52
+
53
+ #
54
+ # Steps
55
+ #
56
+ RESPONSE_ATTRIBUTES='(status|headers|body)'
57
+ Then(/^the value of `([^`]*)` is( not)? (.*)$/) do |value, negated, assertion|
58
+ use_selector(Selector.new(value, (!negated.nil?)))
59
+ step "it is #{assertion}"
60
+ end
61
+
62
+ def dig_from_response(attribute, path=nil, plural=false)
63
+ root = response.send(attribute.to_sym)
64
+ return root if !path
65
+ JsonPath.new("$.#{path}").send(plural ? :on : :first, root)
66
+ end
@@ -0,0 +1,37 @@
1
+ # assertions.rb - General assertions to be used with a Selector
2
+
3
+ Then(/^it is equal to `([^`]*)`$/) do |value|
4
+ selector.assert_that(value) {|v| eq v}
5
+ end
6
+ Then(/^it is equal to:$/) do |value|
7
+ selector.assert_that(value) {|v| eq v}
8
+ end
9
+ Then(/^it is matching `([^`]*)`$/) do |value|
10
+ selector.assert_that(value) {|v| match v}
11
+ end
12
+ Then (/^it is matching:$/) do |value|
13
+ selector.assert_that(value) {|v| match v}
14
+ end
15
+ Then(/^it is greater than `([^`]*)`$/) do |value|
16
+ selector.assert_that(value) {|v| be > v}
17
+ end
18
+ Then(/^it is greater than or equal to `([^`]*)`$/) do |value|
19
+ selector.assert_that(value) {|v| be >= v}
20
+ end
21
+ Then(/^it is less than `([^`]*)`$/) do |value|
22
+ selector.assert_that(value) {|v| be < v}
23
+ end
24
+ Then(/^it is less than or equal to `([^`]*)`$/) do |value|
25
+ selector.assert_that(value) {|v| be <= v}
26
+ end
27
+
28
+ Then(/^it is including `([^`]*)`$/) do |value|
29
+ selector.assert_that(value) {|v| include v }
30
+ end
31
+ Then(/^it is including:$/) do |value|
32
+ selector.assert_that(value) {|v| include v }
33
+ end
34
+
35
+ Then(/^it is a valid `([^`]*)`$/) do |type|
36
+ selector.assert_that(type) {|t| type_check_for(t) }
37
+ end
@@ -0,0 +1,13 @@
1
+ # assignment.rb -- assignment related steps
2
+
3
+ When(/^`([^`]*)` is assigned a random string$/) do |name|
4
+ bind(name, SecureRandom.uuid)
5
+ end
6
+
7
+ When(/^`([^`]*)` is assigned `([^`]*)`$/) do |name, value|
8
+ bind(name, value)
9
+ end
10
+
11
+ When(/^`([^`]*)` is assigned a timestamp$/) do |name|
12
+ bind(name, DateTime.now)
13
+ end
@@ -0,0 +1,4 @@
1
+ # cleanup.rb - Track information needed for Resource Cleanup
2
+ When(/^a resource is created at `([^`]*)`$/) do |path|
3
+ track_created_resource path
4
+ end
@@ -0,0 +1,19 @@
1
+ # request_construction.rb - Build and send requests
2
+
3
+ When(/^the request body is assigned:$/) do |input|
4
+ set_request_body(input)
5
+ end
6
+
7
+ When(/^the request query parameter `([^`]*)` is assigned `([^`]*)`$/) do |param, value|
8
+ add_request_param(param, value)
9
+ end
10
+
11
+ When(/^the request header `([^`]*)` is assigned `([^`]*)`$/) do |header, value|
12
+ add_header(header, value)
13
+ end
14
+
15
+ When(/^an? (GET|POST|PATCH|PUT|DELETE|HEAD|OPTIONS) is sent to `([^`]*)`$/) do |method, url|
16
+ send_request(parse_method(method), URI.escape(url))
17
+ bind('response', response)
18
+ reset_request
19
+ end
@@ -0,0 +1,37 @@
1
+ #RESPONSE_ATTRIBUTES='(status|headers|body)'
2
+ Then(/^the value of the response #{RESPONSE_ATTRIBUTES}(?: child(ren)? `([^`]*)`)? is( not)? (.*)(?<!:)$/) do
3
+ |attribute, plural, path, negated, assertion|
4
+ use_selector(Selector.new(dig_from_response(attribute, path, !plural.nil?), (!negated.nil?)))
5
+ step "it is #{assertion}"
6
+ end
7
+
8
+ Then(/^the value of the response #{RESPONSE_ATTRIBUTES}(?: child(ren)? `([^`]*)`)? is( not)? (.*)(?<=:)$/) do
9
+ |attribute, plural, path, negated, assertion, multi|
10
+ use_selector(Selector.new(dig_from_response(attribute, path, !plural.nil?), (!negated.nil?)))
11
+ step "it is #{assertion}", multi.to_json
12
+ end
13
+
14
+ Then(/^the value of the response #{RESPONSE_ATTRIBUTES}(?: child(ren)? `([^`]*)`)? does( not)? have any element that is (.*)(?<!:)$/) do
15
+ |attribute, plural, path, negated, assertion|
16
+ use_selector(AnySelector.new(dig_from_response(attribute, path, !plural.nil?), (!negated.nil?)))
17
+ step "it is #{assertion}"
18
+ end
19
+
20
+ Then(/^the value of the response #{RESPONSE_ATTRIBUTES}(?: child(ren)? `([^`]*)`)? does( not)? have any element that is (.*)(?<=:)$/) do
21
+ |attribute, plural, path, negated, assertion, multi|
22
+ use_selector(AnySelector.new(dig_from_response(attribute, path, !plural.nil?), (!negated.nil?)))
23
+ step "it is #{assertion}", multi.to_json
24
+ end
25
+
26
+ #Would be negated with `not all' which would be equivalent to any(not ) but that's not readily supported
27
+ Then(/^the value of the response #{RESPONSE_ATTRIBUTES}(?: child(ren)? `([^`]*)`)? has elements which are all (.*)(?<!:)$/) do
28
+ |attribute, plural, path, assertion|
29
+ use_selector(AllSelector.new(dig_from_response(attribute, path, !plural.nil?), false))
30
+ step "it is #{assertion}"
31
+ end
32
+
33
+ Then(/^the value of the response #{RESPONSE_ATTRIBUTES}(?: child(ren)? `([^`]*)`)? has elements which are all (.*)(?<=:)$/) do
34
+ |attribute, plural, path, assertion, multi|
35
+ use_selector(AllSelector.new(dig_from_response(attribute, path, !plural.nil?), false))
36
+ step "it is #{assertion}", multi.to_json
37
+ end
@@ -0,0 +1,138 @@
1
+ require 'rspec'
2
+ #
3
+ # Steps used to test this library
4
+ # Not loaded by default (except in the tests)
5
+ #
6
+ HTTP_METHOD='GET|POST|PATCH|PUT|DELETE|HEAD|OPTIONS'
7
+
8
+ class StubResponse
9
+ attr_accessor :body, :status
10
+
11
+ def initialize
12
+ @body = ''
13
+ @status = 200
14
+ end
15
+ end
16
+
17
+ class StubRequest
18
+ attr_accessor :method, :path, :headers, :body
19
+
20
+ def initialize
21
+ @headers = {}
22
+ end
23
+
24
+ def method=(value)
25
+ @method=value.downcase.to_sym
26
+ end
27
+ end
28
+
29
+ class StubBuilder
30
+ attr_reader :request, :response
31
+
32
+ def initialize
33
+ @request = StubRequest.new
34
+ @response = StubResponse.new
35
+ end
36
+
37
+ def make_response()
38
+ [@response.status, {}, @response.body]
39
+ end
40
+
41
+ def build(stubs)
42
+ # Currently the Faraday stub code provides one method per HTTP method (which then
43
+ # calls a generalized protected method), so this block grabs the method to use
44
+ # and passes the right args based on the signature. The last arg is normally a block
45
+ # but we're using the make_response method to avoid duplication and allow overriding.
46
+ m = stubs.method(@request.method)
47
+ case m.parameters.length
48
+ when 3
49
+ m.call(@request.path, @request.headers, &method(:make_response))
50
+ when 4
51
+ m.call(@request.path, @request.body, @request.headers, &method(:make_response))
52
+ else
53
+ raise "I don't know how to call #{m}"
54
+ end
55
+ end
56
+ end
57
+
58
+ class ResponseStatusSequenceStubBuilder < StubBuilder
59
+ def initialize(stub, seq)
60
+ @request = stub.request
61
+ @response = stub.response
62
+ @enum = seq.to_enum
63
+ end
64
+
65
+ def make_response()
66
+ begin
67
+ @val = @enum.next
68
+ end
69
+ [@val, {}, @response.body]
70
+ end
71
+ end
72
+
73
+ def stub
74
+ @stub ||= StubBuilder.new
75
+ end
76
+
77
+ def build_stub
78
+ stub.build($stubs)
79
+ @stub = nil
80
+ end
81
+
82
+ Before do
83
+ $stubs = Faraday::Adapter::Test::Stubs.new
84
+ @client = Faraday.new(url: ENV['ROOT_URL'] ||
85
+ 'http://localhost:8080') do |conn|
86
+ conn.response :logger, nil
87
+ conn.adapter :test, $stubs
88
+ end
89
+ end
90
+
91
+ Given(/^expected response status of `([^`]*)`$/) do |status|
92
+ stub.response.status = status
93
+ end
94
+
95
+ Given(/^expected response status sequence of `([^`]*)`$/) do |seq|
96
+ @stub = ResponseStatusSequenceStubBuilder.new(stub, seq)
97
+ end
98
+
99
+ Given(/^expected request body:$/) do |body|
100
+ stub.request.body = body
101
+ end
102
+
103
+ Given(/^expected request headers:$/) do |headers|
104
+ stub.request.headers = headers
105
+ end
106
+
107
+ Given(/^expected (#{HTTP_METHOD}) sent to `([^`]*)`/) do |method, path|
108
+ stub.request.method = method
109
+ stub.request.path = path
110
+ build_stub
111
+ end
112
+
113
+ When(/^the response body is assigned:$/) do |input|
114
+ @response ||= StubResponse.new
115
+ @response.body = input
116
+ end
117
+
118
+ When(/^the response body is assigned `([^`]*)`/) do |input|
119
+ @response ||= StubResponse.new
120
+ @response.body = input
121
+ end
122
+
123
+ When(/^the response body is:$/) do |input|
124
+ replaced_with('When', 'the response body is assigned:', '1.0.0', input.to_json)
125
+ end
126
+
127
+ When /^the response status is assigned `([^`]*)`$/ do |status|
128
+ @response ||= StubResponse.new
129
+ @response.status = status.to_i # this coercion isn't needed but is a guarantee
130
+ end
131
+
132
+ Then(/^the response body as JSON is:$/) do |text|
133
+ expect(response.body.to_json).to eq text
134
+ end
135
+
136
+ Then(/^expected calls are verified$/) do
137
+ $stubs.verify_stubbed_calls
138
+ end
@@ -0,0 +1,81 @@
1
+ # = transformers.rb:: Argument Transforms for Brine
2
+ #
3
+ # The Transforms that convert provided inputs to support richer
4
+ # functionaliy than the simple strings which Cucumber provides.
5
+
6
+ # == Scalar transforms
7
+ # Convert inputs into basic Ruby data types which represent a single value
8
+
9
+ # Integers
10
+ Transform /\A(-?\d+)\z/ do |number|
11
+ number.to_i
12
+ end
13
+
14
+ # Booleans
15
+ Transform /\A(?:true|false)\z/ do |boolean|
16
+ boolean.to_s == "true"
17
+ end
18
+
19
+ # Regexp
20
+ # This presently does not support flags after the closing slash, support for these should be added as needed
21
+ Transform /\A\/(.*)\/\z/ do |pattern|
22
+ Regexp.new(pattern)
23
+ end
24
+
25
+ # Temporal
26
+ DATE='\d{4}-\d{2}-\d{2}'
27
+ TIME='\d{2}:\d{2}:\d{2}'
28
+ MILLIS='(?:\.\d{3})?'
29
+ TZ='(?:Z|(?:[+-]\d{2}:\d{2}))'
30
+ Transform /^#{DATE}T#{TIME}#{MILLIS}#{TZ}$/ do |date|
31
+ Time.parse(date)
32
+ end
33
+
34
+ # == Structure transforms
35
+ # Converts inputs to general data structures
36
+
37
+ # Lists
38
+ Transform /\A\[.*\]\z/m do |input|
39
+ JSON.parse(input)
40
+ end
41
+
42
+ # Objects
43
+ # Rely on templates being registered later and therefore given higher priority.
44
+ # Lookarounds could avoid the ambiguity but are a nastier pattern.
45
+ Transform /\A{.*}\z$/m do |input|
46
+ JSON.parse(input)
47
+ end
48
+
49
+ # == Atypical transforms
50
+ # Transforms for which data type is not the primary focus
51
+
52
+ # Whitespace removal transforms
53
+ # Handle stripping leading and trailing whitespace.
54
+ # These are split out from the transforms to consolidate the behavior.
55
+ # They call transform on the stripped value so that subsequent transforms no longer
56
+ # have to deal with such whitespace.
57
+ #
58
+ # Note that these need to deal with multiline string arguments which require
59
+ # the multiline flag and \A/\z anchors to properly operate on the full string rather than
60
+ # being line oriented. The calls to +#strip+ are also not likely to properly clean up
61
+ # multiline strings but is just meant as a (potentially ineffective) optimization over
62
+ # recursive calls and capturing.
63
+ Transform /\A\s+(.*)\z/m do |input|
64
+ Transform(input.strip)
65
+ end
66
+ Transform /\A(.*)\s+\z/m do |input|
67
+ Transform(input.strip)
68
+ end
69
+
70
+ # Quotes
71
+ Transform /\A".*"\z/ do |quoted|
72
+ quoted[1..-2]
73
+ end
74
+ Transform /\A'.*'\z/ do |quoted|
75
+ quoted[1..-2]
76
+ end
77
+
78
+ # Template Expansion
79
+ Transform /.*{{.*}}.*/ do |template|
80
+ Transform(shave_value(template))
81
+ end
@@ -0,0 +1,35 @@
1
+ # type_checks.rb -- checks whether provided is a valid instance of a specified type
2
+ #
3
+ # Provides validation for standard JSON type
4
+
5
+ require 'rspec/expectations'
6
+
7
+ # This will be made extensible so it can replace current domain specific check logic
8
+ class TypeChecks
9
+ include RSpec::Matchers
10
+
11
+ def initialize
12
+ @map = {
13
+ Object: be_a_kind_of(Hash),
14
+ String: be_a_kind_of(String),
15
+ Number: be_a_kind_of(Numeric),
16
+ Integer: be_a_kind_of(Integer),
17
+ Array: be_a_kind_of(Array),
18
+ Boolean: satisfy{|it| it == true || it == false }
19
+ }
20
+ end
21
+
22
+ def for_type(type)
23
+ @map[type.to_sym] || raise("Unsupported type #{type}")
24
+ end
25
+ end
26
+
27
+ module TypeChecking
28
+ def type_checks
29
+ @type_check ||= TypeChecks.new
30
+ end
31
+
32
+ def type_check_for(type)
33
+ type_checks.for_type(type)
34
+ end
35
+ end