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 +7 -0
- data/lib/cucumber-rest-bdd.rb +1 -0
- data/lib/cucumber-rest-bdd/hash.rb +9 -0
- data/lib/cucumber-rest-bdd/level.rb +47 -0
- data/lib/cucumber-rest-bdd/steps.rb +4 -0
- data/lib/cucumber-rest-bdd/steps/functional.rb +32 -0
- data/lib/cucumber-rest-bdd/steps/resource.rb +152 -0
- data/lib/cucumber-rest-bdd/steps/response.rb +229 -0
- data/lib/cucumber-rest-bdd/steps/status.rb +63 -0
- data/lib/cucumber-rest-bdd/types.rb +143 -0
- data/lib/cucumber-rest-bdd/url.rb +4 -0
- metadata +98 -0
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,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,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
|
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: []
|