brine-dsl 0.5.0

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