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