alinta-cucumber-rest-bdd 0.5.4

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.
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: []