alinta-cucumber-rest-bdd 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d6a3128ba01da04df49abe07ca69add1a49e180d
4
+ data.tar.gz: '0590435deb022044b88e96c93cbddff01bd04017'
5
+ SHA512:
6
+ metadata.gz: 14163134b5325de4b1fd3e65133cd4796109dd959a8d811af2385c1acdc6517e9268486b73487e52ca13d007775ea408cf58bd445246baffb060c184642adf39
7
+ data.tar.gz: 533e16434ca8ab1df0978982e6422f00339744e2a76f57ca5e8065d49443823148238dbeabe2d62d16da9cd2c60bb933329b0ca94b9f04f0b1df3b6248841d2c
@@ -0,0 +1 @@
1
+ require 'cucumber-rest-bdd/steps'
@@ -0,0 +1,9 @@
1
+ require 'easy_diff'
2
+
3
+ class Hash
4
+ def deep_include?(other)
5
+ diff = other.easy_diff(self)
6
+ diff[0].delete_if { |k, v| v.empty? if v.is_a?(::Hash) }
7
+ diff[0].empty?
8
+ end
9
+ end
@@ -0,0 +1,47 @@
1
+ require 'cucumber-rest-bdd/types'
2
+ require 'active_support/inflector'
3
+
4
+ LEVELS = %{(?: (?:for|in|on) [^"]+?(?: with (?:key|id))? "[^"]+")*}%
5
+
6
+ class Level
7
+ @urls = []
8
+
9
+ def initialize(levels)
10
+ arr = []
11
+ while matches = /^ (?:for|in|on) ([^"]+?)(?: with (?:key|id))? "([^"]*)"/.match(levels)
12
+ levels = levels[matches[0].length, levels.length]
13
+ item = {
14
+ resource: get_resource(matches[1]),
15
+ id: matches[2]
16
+ }
17
+ item[:id] = item[:id].to_i if item[:id].match(/^\d+$/)
18
+ arr.append(item)
19
+ end
20
+ @urls = arr.reverse
21
+ end
22
+
23
+ def url
24
+ @urls.map{ |l| "#{l[:resource]}/#{l[:id]}/"}.join()
25
+ end
26
+
27
+ def hash
28
+ hash = {}
29
+ @urls.each{ |l| hash[get_parameter("#{get_parameter(l[:resource]).singularize}_id")] = l[:id] }
30
+ hash
31
+ end
32
+
33
+ def last_hash
34
+ last = @urls.last
35
+ if !last.nil?
36
+ key = get_parameter("#{get_parameter(last[:resource]).singularize}_id")
37
+ return {
38
+ key => last[:id]
39
+ }
40
+ end
41
+ return {}
42
+ end
43
+
44
+ def to_s
45
+ self.url
46
+ end
47
+ end
@@ -0,0 +1,4 @@
1
+ require 'cucumber-rest-bdd/steps/functional'
2
+ require 'cucumber-rest-bdd/steps/response'
3
+ require 'cucumber-rest-bdd/steps/resource'
4
+ require 'cucumber-rest-bdd/steps/status'
@@ -0,0 +1,32 @@
1
+ require 'cucumber-api/response'
2
+ require 'cucumber-api/steps'
3
+ require 'cucumber-rest-bdd/types'
4
+
5
+ Then(/^the response (?:should have|has a|has the) header "([^"]*)" with (?:a |the )?value "([^"]*)"$/) do |header, value|
6
+ p_value = resolve(value)
7
+ p_header = header.parameterize
8
+ raise %/Required header: #{header} not found\n#{@response.raw_headers.inspect}/ if !@response.raw_headers.key?(p_header)
9
+ exists = @response.raw_headers[p_header].include? p_value
10
+ raise %/Expect #{p_value} in #{header} (#{p_header})\n#{@response.raw_headers.inspect}/ if !exists
11
+ end
12
+
13
+ Then(/^the JSON response should have "([^"]*)" of type array with (\d+) entr(?:y|ies)$/) do |json_path, number|
14
+ list = @response.get_as_type json_path, 'array'
15
+ raise %/Expected #{number} items in array for path '#{json_path}', found: #{list.count}\n#{@response.to_json_s}/ if list.count != number.to_i
16
+ end
17
+
18
+ Then(/^the JSON response should have "([^"]*)" of type array with (#{FEWER_MORE_THAN}) (\d+) entr(?:y|ies)$/) do |json_path, count_mod, number|
19
+ list = @response.get_as_type json_path, 'array'
20
+ raise %/Expected #{count_mod} #{number} items in array for path '#{json_path}', found: #{list.count}\n#{@response.to_json_s}/ \
21
+ if !num_compare(count_mod, list.count, number.to_i)
22
+ end
23
+
24
+ Then(/^the JSON response should have "([^"]*)" of type (.+) that matches "(.+)"$/) do |json_path, type, regex|
25
+ value = @response.get_as_type json_path, type
26
+ raise %/Expected #{json_path} value '#{value}' to match regex: #{regex}\n#{@response.to_json_s}/ if (Regexp.new(regex) =~ value).nil?
27
+ end
28
+
29
+ Then(/^the JSON response should have "([^"]*)" of type (?:nill|null|nil)$/) do |json_path|
30
+ value = @response.get_as_type_or_null json_path, 'string'
31
+ raise %/Expected #{json_path} to be nil, was: #{value.class}\n#{@response.to_json_s}/ if !value.nil?
32
+ end
@@ -0,0 +1,152 @@
1
+ require 'cucumber-api/response'
2
+ require 'cucumber-api/steps'
3
+ require 'active_support/inflector'
4
+ require 'cucumber-rest-bdd/url'
5
+ require 'cucumber-rest-bdd/types'
6
+ require 'cucumber-rest-bdd/level'
7
+ require 'cucumber-rest-bdd/hash'
8
+ require 'easy_diff'
9
+
10
+ Given(/^I am a client$/) do
11
+ steps %Q{
12
+ Given I send "application/json" and accept JSON
13
+ }
14
+ end
15
+
16
+ # GET
17
+
18
+ When(/^I request (?:an?(?! list)|the) ([^"]+?)(?: with (?:key|id))? "([^"]*)"(#{LEVELS})?$/) do |resource, id, levels|
19
+ resource_name = get_resource(resource)
20
+ url = get_url("#{Level.new(levels).url}#{resource_name}/#{id}")
21
+ steps %Q{When I send a GET request to "#{url}"}
22
+ end
23
+
24
+ When(/^I request (?:an?(?! list)|the) (.+?)(?: with (?:key|id))? "([^"]*)"(#{LEVELS})? with:$/) do |resource, id, levels, params|
25
+ resource_name = get_resource(resource)
26
+ url = get_url("#{Level.new(levels).url}#{resource_name}/#{id}")
27
+ unless params.raw.empty?
28
+ query = params.raw.map{|key, value| %/#{get_parameter(key)}=#{resolve(value)}/}.join("&")
29
+ url = "#{url}?#{query}"
30
+ end
31
+ steps %Q{When I send a GET request to "#{url}"}
32
+ end
33
+
34
+ When(/^I request a list of ([^:]+?)(#{LEVELS})?$/) do |resource, levels|
35
+ resource_name = get_resource(resource)
36
+ url = get_url("#{Level.new(levels).url}#{resource_name}")
37
+ steps %Q{When I send a GET request to "#{url}"}
38
+ end
39
+
40
+ When(/^I request a list of (.+?)(#{LEVELS})? with:$/) do |resource, levels, params|
41
+ resource_name = get_resource(resource)
42
+ url = get_url("#{Level.new(levels).url}#{resource_name}")
43
+ unless params.raw.empty?
44
+ query = params.raw.map{|key, value| %/#{get_parameter(key)}=#{resolve(value)}/}.join("&")
45
+ url = "#{url}?#{query}"
46
+ end
47
+ steps %Q{When I send a GET request to "#{url}"}
48
+ end
49
+
50
+ # DELETE
51
+
52
+ When(/^I request to (?:delete|remove) the ([^"]+?) "([^"]*)"(#{LEVELS})?$/) do |resource, id, levels|
53
+ resource_name = get_resource(resource)
54
+ url = get_url("#{Level.new(levels).url}#{resource_name}/#{id}")
55
+ steps %Q{When I send a DELETE request to "#{url}"}
56
+ end
57
+
58
+ # POST
59
+
60
+ When(/^I request to create an? ([^:]+?)(#{LEVELS})?$/) do |resource, levels|
61
+ resource_name = get_resource(resource)
62
+ level = Level.new(levels)
63
+ if ENV['set_parent_id'] == 'true'
64
+ json = MultiJson.dump(level.last_hash)
65
+ steps %Q{
66
+ When I set JSON request body to:
67
+ """
68
+ #{json}
69
+ """
70
+ }
71
+ end
72
+ url = get_url("#{level.url}#{resource_name}")
73
+ steps %Q{When I send a POST request to "#{url}"}
74
+ end
75
+
76
+ When(/^I request to create an? ((?!<.+?(?: for | in | on ))[^"]+?)(#{LEVELS})? with:$/) do |resource, levels, params|
77
+ resource_name = get_resource(resource)
78
+ request_hash = get_attributes(params.hashes)
79
+ level = Level.new(levels)
80
+ request_hash = request_hash.merge(level.last_hash) if ENV['set_parent_id'] == 'true'
81
+ json = MultiJson.dump(request_hash)
82
+ url = get_url("#{level.url}#{resource_name}")
83
+ steps %Q{
84
+ When I set JSON request body to:
85
+ """
86
+ #{json}
87
+ """
88
+ And I send a POST request to "#{url}"
89
+ }
90
+ end
91
+
92
+ # PUT
93
+
94
+ When(/^I request to (?:create|replace|set) (?:an?|the) ((?![^"]+?(?: for | in | on ))[^"]+?)(?: with (?:key|id))? "([^"]+)"(#{LEVELS})?$/) do |resource, id, levels|
95
+ resource_name = get_resource(resource)
96
+ level = Level.new(levels)
97
+ if ENV['set_parent_id'] == 'true'
98
+ json = MultiJson.dump(level.last_hash)
99
+ steps %Q{
100
+ When I set JSON request body to:
101
+ """
102
+ #{json}
103
+ """
104
+ }
105
+ end
106
+ url = get_url("#{level.url}#{resource_name}/#{id}")
107
+ steps %Q{
108
+ When I send a PUT request to "#{url}"
109
+ }
110
+ end
111
+
112
+ When(/^I request to (?:create|replace|set) (?:an?|the) ((?![^"]+?(?: for | in | on ))[^"]+?)(?: with (?:key|id))? "([^"]+)"(#{LEVELS})? (?:with|to):$/) do |resource, id, levels, params|
113
+ resource_name = get_resource(resource)
114
+ request_hash = get_attributes(params.hashes)
115
+ level = Level.new(levels)
116
+ request_hash = request_hash.merge(level.last_hash) if ENV['set_parent_id'] == 'true'
117
+ json = MultiJson.dump(request_hash)
118
+ url = get_url("#{level.url}#{resource_name}/#{id}")
119
+ steps %Q{
120
+ When I set JSON request body to:
121
+ """
122
+ #{json}
123
+ """
124
+ And I send a PUT request to "#{url}"
125
+ }
126
+ end
127
+
128
+ # PATCH
129
+
130
+ When(/^I request to modify the ((?![^"]+?(?: for | in | on ))[^"]+?)(?: with (?:key|id))? "([^"]+)"(#{LEVELS})? with:$/) do |resource, id, levels, params|
131
+ resource_name = get_resource(resource)
132
+ request_hash = get_attributes(params.hashes)
133
+ json = MultiJson.dump(request_hash)
134
+ url = get_url("#{Level.new(levels).url}#{resource_name}/#{id}")
135
+ steps %Q{
136
+ When I set JSON request body to:
137
+ """
138
+ #{json}
139
+ """
140
+ And I send a PATCH request to "#{url}"
141
+ }
142
+ end
143
+
144
+ # value capture
145
+
146
+ When(/^I save (?:attribute )?"([^"]+)"$/) do |attribute|
147
+ steps %Q{When I grab "#{get_json_path(attribute)}" as "#{attribute}"}
148
+ end
149
+
150
+ When(/^I save (?:attribute )?"([^"]+)" to "([^"]+)"$/) do |attribute, ref|
151
+ steps %Q{When I grab "#{get_json_path(attribute)}" as "#{ref}"}
152
+ end
@@ -0,0 +1,229 @@
1
+ require 'cucumber-rest-bdd/steps/resource'
2
+ require 'cucumber-rest-bdd/types'
3
+
4
+ Then(/^print the response$/) do
5
+ puts %/The response:\n#{@response.to_json_s}/
6
+ end
7
+
8
+ # response interrogation
9
+
10
+ Then(/^the response #{HAVE_SYNONYM} ([\w\s]+|`[^`]*`) of type (datetime|guid)$/) do |names, type|
11
+ regex = case type
12
+ when 'datetime' then /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:[+|-]\d{2}:\d{2})?$/i
13
+ when 'guid' then /^[{(]?[0-9A-F]{8}[-]?([0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$/i
14
+ else 'UNKNOWN'
15
+ end
16
+ validate_value(names, 'string', regex)
17
+ end
18
+
19
+ Then(/^the response #{HAVE_SYNONYM} ([\w\s]+|`[^`]*`) of type (\w+) that matches "(.+)"$/) do |names, type, regex|
20
+ validate_value(names, type, Regexp.new(regex))
21
+ end
22
+
23
+ def validate_value(names, type, regex)
24
+ json_path = get_json_path(names)
25
+ type = parse_type(type)
26
+ value = @response.get_as_type json_path, type
27
+ raise %/Expected #{json_path} value '#{value}' to match regex: #{regex}\n#{@response.to_json_s}/ if (regex =~ value).nil?
28
+ end
29
+
30
+ Then(/^the response is a list (?:of|containing) (#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT}|\d+) .*?$/) do |count_mod, count|
31
+ list = @response.get_as_type get_root_data_key(), 'array'
32
+ raise %/Expected at least #{count} items in array for path '#{get_root_data_key()}', found: #{list.count}\n#{@response.to_json_s}/ if !num_compare(count_mod, list.count, count.to_i)
33
+ end
34
+
35
+ Then(/^the response ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:[\w\s]+|`[^`]*`) )*)#{HAVE_SYNONYM} (?:the )?(?:following )?(?:data|error )?attributes:$/) do |nesting, attributes|
36
+ expected = get_attributes(attributes.hashes)
37
+ groups = nesting
38
+ grouping = get_grouping(groups)
39
+ grouping.push({
40
+ root: true,
41
+ type: 'single'
42
+ })
43
+ data = @response.get get_key(grouping)
44
+ raise %/Could not find a match for: #{nesting}\n#{expected.inspect}\n#{@response.to_json_s}/ if data.empty? || !nest_match_attributes(data, grouping, expected)
45
+ end
46
+
47
+ Then(/^the response ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:[\w\s]+|`[^`]*`) )*)#{HAVE_SYNONYM} (?:the )?(?:following )?value "([^"]*)"$/) do |nesting, value|
48
+ expected = value
49
+ groups = nesting
50
+ grouping = get_grouping(groups)
51
+ grouping.push({
52
+ root: true,
53
+ type: 'single'
54
+ })
55
+ data = @response.get get_key(grouping)
56
+ raise %/Could not find a match for: #{nesting}\n#{expected}\n#{@response.to_json_s}/ if data.empty? || !nest_match_value(data, grouping, expected)
57
+ end
58
+
59
+ Then(/^the response ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:[\w\s]+|`[^`]*`)\s?)+)$/) do |nesting|
60
+ groups = nesting
61
+ grouping = get_grouping(groups)
62
+ grouping.push({
63
+ root: true,
64
+ type: 'single'
65
+ })
66
+ data = @response.get get_key(grouping)
67
+ raise %/Could not find a match for: #{nesting}\n#{@response.to_json_s}/ if data.empty? || !nest_match_attributes(data, grouping, {})
68
+ end
69
+
70
+ Then(/^(#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT}|\d+) (?:.*?) ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:[\w\s]+|`[^`]*`) )*)#{HAVE_SYNONYM} (?:the )?(?:following )?(?:data )?attributes:$/) do |count_mod, count, nesting, attributes|
71
+ expected = get_attributes(attributes.hashes)
72
+ groups = nesting
73
+ grouping = get_grouping(groups)
74
+ grouping.push({
75
+ root: true,
76
+ type: 'multiple',
77
+ count: count.to_i,
78
+ count_mod: count_mod
79
+ })
80
+ data = @response.get get_key(grouping)
81
+ raise %/Expected #{compare_to_string(count_mod)}#{count} items in array with attributes for: #{nesting}\n#{expected.inspect}\n#{@response.to_json_s}/ if !nest_match_attributes(data, grouping, expected)
82
+ end
83
+
84
+ Then(/^(#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT}|\d+) (?:.*?) ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:[\w\s]+|`[^`]*`)\s?)+)$/) do |count_mod, count, nesting|
85
+ groups = nesting
86
+ grouping = get_grouping(groups)
87
+ grouping.push({
88
+ root: true,
89
+ type: 'multiple',
90
+ count: count.to_i,
91
+ count_mod: count_mod
92
+ })
93
+ data = @response.get get_key(grouping)
94
+ raise %/Expected #{compare_to_string(count_mod)}#{count} items in array with: #{nesting}\n#{@response.to_json_s}/ if !nest_match_attributes(data, grouping, {})
95
+ end
96
+
97
+ Then(/^the response ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:[\w\s]+|`[^`]*`) )*)#{HAVE_SYNONYM} a list of (#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT} |\d+ )?(\w+)$/) do |nesting, num_mod, num, item|
98
+ groups = nesting
99
+ list = {
100
+ type: 'list',
101
+ key: get_resource(item)
102
+ }
103
+ if (num) then
104
+ list[:count] = num.to_i
105
+ list[:count_mod] = num_mod
106
+ end
107
+ grouping = [list]
108
+ grouping.concat(get_grouping(groups))
109
+ grouping.push({
110
+ root: true,
111
+ type: 'single'
112
+ })
113
+ data = @response.get get_key(grouping)
114
+ raise %/Could not find a match for #{nesting}#{compare_to_string(num_mod)}#{num} #{item}\n#{@response.to_json_s}/ if !nest_match_attributes(data, grouping, {})
115
+ end
116
+
117
+ Then(/^(#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT}|\d+) (?:.*?) ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:[\w\s]+|`[^`]*`) )*)#{HAVE_SYNONYM} a list of (#{FEWER_MORE_THAN})?\s*(?:(#{CAPTURE_INT}|\d+) )?(\w+)$/) do |count_mod, count, nesting, num_mod, num, item|
118
+ groups = nesting
119
+ list = {
120
+ type: 'list',
121
+ key: get_resource(item)
122
+ }
123
+ if (num) then
124
+ list[:count] = num.to_i
125
+ list[:count_mod] = num_mod
126
+ end
127
+ grouping = [list]
128
+ grouping.concat(get_grouping(groups))
129
+ grouping.push({
130
+ root: true,
131
+ type: 'multiple',
132
+ count: count.to_i,
133
+ count_mod: count_mod
134
+ })
135
+ data = @response.get get_key(grouping)
136
+ raise %/Expected #{compare_to_string(count_mod)}#{count} items with #{nesting}#{compare_to_string(num_mod)}#{num}#{item}\n#{@response.to_json_s}/ if !nest_match_attributes(data, grouping, {})
137
+ end
138
+
139
+ # gets the relevant key for the response based on the first key element
140
+ def get_key(grouping)
141
+ if ENV['error_key'] && !ENV['error_key'].empty? && grouping.count > 1 && grouping[-2][:key].singularize == ENV['error_key'] then
142
+ get_root_error_key()
143
+ else
144
+ get_root_data_key()
145
+ end
146
+ end
147
+
148
+ # gets an array in the nesting format that nest_match_attributes understands to interrogate nested object and array data
149
+ def get_grouping(nesting)
150
+ grouping = []
151
+ while matches = /^#{HAVE_SYNONYM} (?:a|an|(?:(#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT}|\d+))) ([\w\s]+|`[^`]*`)\s?+/.match(nesting)
152
+ nesting = nesting[matches[0].length, nesting.length]
153
+ if matches[2].nil? then
154
+ level = {
155
+ type: 'single',
156
+ key: matches[3],
157
+ root: false
158
+ }
159
+ else
160
+ level = {
161
+ type: 'multiple',
162
+ key: matches[3],
163
+ count: to_num(matches[2]),
164
+ root: false,
165
+ count_mod: to_compare(matches[1])
166
+ }
167
+ end
168
+ grouping.push(level)
169
+ end
170
+ return grouping.reverse
171
+ end
172
+
173
+ # top level has 2 children with an item containing at most three fish with attributes:
174
+ #
175
+ # nesting = [{key=fish,count=3,count_mod='<=',type=multiple},{key=item,type=single},{key=children,type=multiple,count=2,count_mod='='},{root=true,type=single}]
176
+ #
177
+ # returns true if the expected data is contained within the data based on the nesting information
178
+ def nest_match_attributes(data, nesting, expected)
179
+ return false if !data
180
+ return data.deep_include?(expected) if nesting.size == 0
181
+
182
+ local_nesting = nesting.dup
183
+ level = local_nesting.pop
184
+ case level[:type]
185
+ when 'single' then
186
+ child_data = level[:root] ? data.dup : data[get_parameter(level[:key])]
187
+ return nest_match_attributes(child_data, local_nesting, expected)
188
+ when 'multiple' then
189
+ child_data = level[:root] ? data.dup : data[get_parameter(level[:key])]
190
+ matched = child_data.select { |item| nest_match_attributes(item, local_nesting, expected) }
191
+ return num_compare(level[:count_mod], matched.count, level[:count])
192
+ when 'list' then
193
+ child_data = level[:root] ? data.dup : data[get_resource(level[:key])]
194
+ return false if !child_data.is_a?(Array)
195
+ if level.has_key?(:count) then
196
+ return num_compare(level[:count_mod], child_data.count, level[:count])
197
+ end
198
+ return true
199
+ else
200
+ raise %/Unknown nested data type: #{level[:type]}/
201
+ end
202
+ end
203
+
204
+ def nest_match_value(data, nesting, expected)
205
+ return false if !data
206
+ return data.include?(expected) if nesting.size == 0
207
+
208
+ local_nesting = nesting.dup
209
+ level = local_nesting.pop
210
+ case level[:type]
211
+ when 'single' then
212
+ child_data = level[:root] ? data.dup : data[get_parameter(level[:key])]
213
+ return nest_match_value(child_data, local_nesting, expected)
214
+ when 'multiple' then
215
+ child_data = level[:root] ? data.dup : data[get_parameter(level[:key])]
216
+ raise %/Key not found: #{level[:key]} as #{get_parameter(level[:key])} in #{data}/ if !child_data
217
+ matched = child_data.select { |item| nest_match_value(item, local_nesting, expected) }
218
+ return num_compare(level[:count_mod], matched.count, level[:count])
219
+ when 'list' then
220
+ child_data = level[:root] ? data.dup : data[get_resource(level[:key])]
221
+ return false if !child_data.is_a?(Array)
222
+ if level.has_key?(:count) then
223
+ return num_compare(level[:count_mod], child_data.count, level[:count])
224
+ end
225
+ return true
226
+ else
227
+ raise %/Unknown nested data type: #{level[:type]}/
228
+ end
229
+ end
@@ -0,0 +1,63 @@
1
+ require 'cucumber-api/response'
2
+ require 'cucumber-api/steps'
3
+
4
+ Then(/^the request (?:is|was) successful$/) do
5
+ raise %/Expected Successful response code 2xx but was #{@response.code}/ if @response.code < 200 || @response.code >= 300
6
+ end
7
+
8
+ Then(/^the request (?:is|was) redirected$/) do
9
+ raise %/Expected redirected response code 3xx but was #{@response.code}/ if @response.code < 300 || @response.code >= 400
10
+ end
11
+
12
+ Then(/^(?:it|the request) fail(?:s|ed)$/) do
13
+ raise %/Expected failed response code 4xx\/5xx but was #{@response.code}/ if @response.code < 400 || @response.code >= 600
14
+ end
15
+
16
+ Then(/^the (?!request)(?:.+?) (?:is|was) created$/) do
17
+ steps %Q{Then the response status should be "201"}
18
+ end
19
+
20
+ Then(/^the request (?:is|was) successful and (?:a resource|.+) (?:is|was) created$/) do
21
+ steps %Q{Then the response status should be "201"}
22
+ end
23
+
24
+ Then(/^the request (?:is|was) successfully accepted$/) do
25
+ steps %Q{Then the response status should be "202"}
26
+ end
27
+
28
+ Then(/^the request (?:is|was) successful and (?:no|an empty) response body is returned$/) do
29
+ steps %Q{Then the response status should be "204"}
30
+ raise %/Expected the request body to be empty/ if !@response.body.empty?
31
+ end
32
+
33
+ Then(/^(?:it|the request) fail(?:s|ed) because it (?:is|was) invalid$/) do
34
+ steps %Q{Then the response status should be "400"}
35
+ end
36
+
37
+ Then(/^(?:it|the request) fail(?:s|ed) because (?:.+) (?:is|was|am|are) unauthori[sz]ed$/) do
38
+ steps %Q{Then the response status should be "401"}
39
+ end
40
+
41
+ Then(/^(?:it|the request) fail(?:s|ed) because (?:.+) (?:is|was) forbidden$/) do
42
+ steps %Q{Then the response status should be "403"}
43
+ end
44
+
45
+ Then(/^(?:it|the request) fail(?:s|ed) because the (?:.+) (?:is|was) not found$/) do
46
+ steps %Q{Then the response status should be "404"}
47
+ end
48
+
49
+ Then(/^(?:it|the request) fail(?:s|ed) because it (?:is|was) not allowed$/) do
50
+ steps %Q{Then the response status should be "405"}
51
+ end
52
+
53
+ Then(/^(?:it|the request) fail(?:s|ed) because there (?:is|was|has) a conflict(?: with .+)?$/) do
54
+ steps %Q{Then the response status should be "409"}
55
+ end
56
+
57
+ Then(/^(?:it|the request) fail(?:s|ed) because the (?:.+) (?:is|was|has) gone$/) do
58
+ steps %Q{Then the response status should be "410"}
59
+ end
60
+
61
+ Then(/^(?:it|the request) fail(?:s|ed) because the (?:.+) (?:is|was) not implemented$/) do
62
+ steps %Q{Then the response status should be "501"}
63
+ end
@@ -0,0 +1,143 @@
1
+ require 'active_support/inflector'
2
+
3
+ CAPTURE_INT = Transform(/^(?:zero|one|two|three|four|five|six|seven|eight|nine|ten)$/) do |v|
4
+ %w(zero one two three four five six seven eight nine ten).index(v)
5
+ end
6
+
7
+ FEWER_MORE_THAN = Transform(/^(?:(?:fewer|less|more) than|at (?:least|most))$/) do |v|
8
+ to_compare(v)
9
+ end
10
+
11
+ HAVE_SYNONYM = %{(?:has|have|having|contain|contains|containing|with)}
12
+
13
+ CMP_LESS_THAN = '<'
14
+ CMP_MORE_THAN = '>'
15
+ CMP_AT_LEAST = '>='
16
+ CMP_AT_MOST = '<='
17
+ CMP_EQUALS = '='
18
+
19
+ # take a number modifier string (fewer than, less than, etc) and return an operator '<', etc
20
+ def to_compare(compare)
21
+ return case compare
22
+ when 'fewer than' then CMP_LESS_THAN
23
+ when 'less than' then CMP_LESS_THAN
24
+ when 'more than' then CMP_MORE_THAN
25
+ when 'at least' then CMP_AT_LEAST
26
+ when 'at most' then CMP_AT_MOST
27
+ else CMP_EQUALS
28
+ end
29
+ end
30
+
31
+ # turn a comparison into a string
32
+ def compare_to_string(compare)
33
+ case compare
34
+ when CMP_LESS_THAN then 'fewer than '
35
+ when CMP_MORE_THAN then 'more than '
36
+ when CMP_AT_LEAST then 'at least '
37
+ when CMP_AT_MOST then 'at most '
38
+ when CMP_EQUALS then ''
39
+ else ''
40
+ end
41
+ end
42
+
43
+ # compare two numbers using the FEWER_MORE_THAN optional modifier
44
+ def num_compare(type, left, right)
45
+ case type
46
+ when CMP_LESS_THAN then left < right
47
+ when CMP_MORE_THAN then left > right
48
+ when CMP_AT_MOST then left <= right
49
+ when CMP_AT_LEAST then left >= right
50
+ when CMP_EQUALS then left == right
51
+ else left == right
52
+ end
53
+ end
54
+
55
+ def to_num(num)
56
+ if /^(?:zero|one|two|three|four|five|six|seven|eight|nine|ten)$/.match(num)
57
+ return %w(zero one two three four five six seven eight nine ten).index(num)
58
+ end
59
+ return num.to_i
60
+ end
61
+
62
+ module Boolean; end
63
+ class TrueClass; include Boolean; end
64
+ class FalseClass; include Boolean; end
65
+
66
+ module Enum; end
67
+ class String; include Enum; end
68
+
69
+ class String
70
+ def to_type(type)
71
+ # cannot use 'case type' which checks for instances of a type rather than type equality
72
+ if type == Boolean then !(self =~ /true|yes/i).nil?
73
+ elsif type == Enum then self.upcase.tr(" ", "_")
74
+ elsif type == Float then self.to_f
75
+ elsif type == Integer then self.to_i
76
+ elsif type == NilClass then nil
77
+ else self
78
+ end
79
+ end
80
+ end
81
+
82
+ def parse_type(type)
83
+ replacements = {
84
+ /^numeric$/i => 'integer',
85
+ /^int$/i => 'integer',
86
+ /^long$/i => 'integer',
87
+ /^number$/i => 'integer',
88
+ /^decimal$/i => 'float',
89
+ /^double$/i => 'float',
90
+ /^bool$/i => 'boolean',
91
+ /^null$/i => 'nil_class',
92
+ /^nil$/i => 'nil_class',
93
+ /^text$/i => 'string'
94
+ }
95
+ type.tr(' ', '_')
96
+ replacements.each { |k,v| type.gsub!(k, v) }
97
+ type
98
+ end
99
+
100
+ def get_resource(name)
101
+ name = name.parameterize
102
+ name = (ENV.has_key?('resource_single') && ENV['resource_single'] == 'true') ? name.singularize : name.pluralize
103
+ return name
104
+ end
105
+
106
+ def get_root_data_key()
107
+ return ENV.has_key?('data_key') && !ENV['data_key'].empty? ? "$.#{ENV['data_key']}." : "$."
108
+ end
109
+
110
+ def get_root_error_key()
111
+ return "$."
112
+ end
113
+
114
+ def get_json_path(names)
115
+ return "#{get_root_data_key()}#{get_parameters(names).join('.')}"
116
+ end
117
+
118
+ def get_parameters(names)
119
+ return names.split(':').map { |n| get_parameter(n) }
120
+ end
121
+
122
+ def get_parameter(name)
123
+ if name[0] == '`' && name[-1] == '`'
124
+ name = name[1..-2]
125
+ else
126
+ separator = ENV.has_key?('field_separator') ? ENV['field_separator'] : '_'
127
+ name = name.parameterize(separator: separator)
128
+ name = name.camelize(:lower) if (ENV.has_key?('field_camel') && ENV['field_camel'] == 'true')
129
+ end
130
+ return name
131
+ end
132
+
133
+ def get_attributes(hashes)
134
+ attributes = hashes.each_with_object({}) do |row, hash|
135
+ name, value, type = row["attribute"], row["value"], row["type"]
136
+ value = resolve(value)
137
+ value.gsub!(/\\n/, "\n")
138
+ type = parse_type(type)
139
+ names = get_parameters(name)
140
+ new_hash = names.reverse.inject(value.to_type(type.camelize.constantize)) { |a, n| { n => a } }
141
+ hash.deep_merge!(new_hash)
142
+ end
143
+ end
@@ -0,0 +1,4 @@
1
+ def get_url(path)
2
+ raise %/Please set an 'endpoint' environment variable provided with the url of the api/ if !ENV.has_key?('endpoint')
3
+ url = %/#{ENV['endpoint']}#{path}/
4
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: alinta-cucumber-rest-bdd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.4
5
+ platform: ruby
6
+ authors:
7
+ - Harry Bragg
8
+ - Matt Hosking
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2018-01-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: cucumber-api
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0.4'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '0.4'
28
+ - !ruby/object:Gem::Dependency
29
+ name: activesupport
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '5.1'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '5.1'
42
+ - !ruby/object:Gem::Dependency
43
+ name: easy_diff
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.0'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1.0'
56
+ description: Series of BDD cucumber rules for testing API endpoints
57
+ email:
58
+ - harry.bragg@graze.com
59
+ - Matt.Hosking@alintaenergy.com.au
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - lib/cucumber-rest-bdd.rb
65
+ - lib/cucumber-rest-bdd/hash.rb
66
+ - lib/cucumber-rest-bdd/level.rb
67
+ - lib/cucumber-rest-bdd/steps.rb
68
+ - lib/cucumber-rest-bdd/steps/functional.rb
69
+ - lib/cucumber-rest-bdd/steps/resource.rb
70
+ - lib/cucumber-rest-bdd/steps/response.rb
71
+ - lib/cucumber-rest-bdd/steps/status.rb
72
+ - lib/cucumber-rest-bdd/types.rb
73
+ - lib/cucumber-rest-bdd/url.rb
74
+ homepage: https://github.com/AlintaEnergy/cucumber-rest-bdd
75
+ licenses:
76
+ - MIT
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 2.6.13
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: BDD Rest API specifics for cucumber
98
+ test_files: []