cucumber-rest-bdd 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/cucumber-rest-bdd/data.rb +66 -40
- data/lib/cucumber-rest-bdd/hash.rb +6 -5
- data/lib/cucumber-rest-bdd/level.rb +37 -36
- data/lib/cucumber-rest-bdd/list.rb +124 -112
- data/lib/cucumber-rest-bdd/steps/functional.rb +39 -14
- data/lib/cucumber-rest-bdd/steps/resource.rb +102 -80
- data/lib/cucumber-rest-bdd/steps/response.rb +131 -88
- data/lib/cucumber-rest-bdd/steps/status.rb +57 -51
- data/lib/cucumber-rest-bdd/types.rb +150 -141
- data/lib/cucumber-rest-bdd/url.rb +6 -5
- metadata +17 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 51002c5c754272c8adb1452953d14ffba1a92141d11cf07b3a817c098b82bf7d
|
4
|
+
data.tar.gz: 519573e43b60171abb392429f9872e32c9377b66470dc51043ea7af6fbb1c068
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 179a7d97d8cf964fbc123ab72eb71e12111054e1bde7f9c05a443b3a191beb61d4142447980307400eea099f2140db3a10fd91105c650a0c0a0811cd5854c6f7
|
7
|
+
data.tar.gz: 48a01c37577e37bf9992bbadef807e10982ddfcc21b204278b11d9d9d5c8592bfd7a85fc27d41e757a2ab2cc50de3ead89ba9e491b7205a720cd531554996fd1
|
@@ -2,51 +2,77 @@ require 'cucumber-rest-bdd/types'
|
|
2
2
|
|
3
3
|
# gets the relevant key for the response based on the first key element
|
4
4
|
def get_key(grouping)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
error_key = ENV['error_key']
|
6
|
+
if error_key && !error_key.empty? && grouping.count > 1 && grouping[-2][:key].singularize == error_key
|
7
|
+
'$.'
|
8
|
+
else
|
9
|
+
root_data_key
|
10
|
+
end
|
11
11
|
end
|
12
12
|
|
13
|
-
# top level has 2 children
|
13
|
+
# top level has 2 children
|
14
|
+
# with an item containing
|
15
|
+
# at most three fish with attributes:
|
14
16
|
#
|
15
|
-
# nesting = [
|
17
|
+
# nesting = [
|
18
|
+
# {key=fish,count=3,count_mod='<=',type=multiple},
|
19
|
+
# {key=item,type=single},
|
20
|
+
# {key=children,type=multiple,count=2,count_mod='='},
|
21
|
+
# {root=true,type=single}
|
22
|
+
# ]
|
16
23
|
#
|
17
|
-
# returns true if the expected data is contained within the data based on the
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
24
|
+
# returns true if the expected data is contained within the data based on the
|
25
|
+
# nesting information
|
26
|
+
def nest_match_attributes(data, nesting, expected, match_value)
|
27
|
+
# puts data.inspect, nesting.inspect, expected.inspect, match_value.inspect
|
28
|
+
return false unless data
|
29
|
+
return data.deep_include?(expected) if !match_value && nesting.empty?
|
30
|
+
return data.include?(expected) if match_value && nesting.empty?
|
31
|
+
|
32
|
+
local_nesting = nesting.dup
|
33
|
+
level = local_nesting.pop
|
34
|
+
child_data = get_child_data(level, data)
|
35
|
+
|
36
|
+
nest_get_match(level, child_data, local_nesting, expected, match_value)
|
37
|
+
end
|
38
|
+
|
39
|
+
# nest_get_match returns true if the child data matches the expected data
|
40
|
+
def nest_get_match(level, child_data, local_nesting, expected, match_value)
|
41
|
+
case level[:type]
|
42
|
+
when 'single' then
|
43
|
+
nest_match_attributes(child_data, local_nesting, expected, match_value)
|
44
|
+
when 'multiple' then
|
45
|
+
child_check(level, child_data, local_nesting, expected, match_value)
|
46
|
+
when 'list' then
|
47
|
+
child_is_list(level, child_data)
|
48
|
+
else
|
49
|
+
raise %(Unknown nested data type: #{level[:type]})
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# check that all the children in child_data match the expected values
|
54
|
+
def child_check(level, child_data, local_nesting, expected, match_value)
|
55
|
+
matched = child_data.select do |item|
|
56
|
+
nest_match_attributes(item, local_nesting, expected, match_value)
|
57
|
+
end
|
58
|
+
level[:comparison].compare(matched.count)
|
59
|
+
end
|
60
|
+
|
61
|
+
# is the child a list, and does it match the comparison?
|
62
|
+
def child_is_list(level, child_data)
|
63
|
+
child_data.is_a?(Array) \
|
64
|
+
&& (!level.key?(:comparison) \
|
65
|
+
|| level[:comparison].compare(child_data.count))
|
38
66
|
end
|
39
67
|
|
68
|
+
# parse the field and get the data for a given child
|
40
69
|
def get_child_data(level, data)
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
raise %/Key not found: #{level[:key]} as #{levelKey} in #{data}/ if data.is_a?(Array) || !data[levelKey]
|
50
|
-
return data[levelKey]
|
51
|
-
end
|
70
|
+
return data.dup if level[:root]
|
71
|
+
level_key = case level[:type]
|
72
|
+
when 'single' then parse_field(level[:key])
|
73
|
+
when 'multiple', 'list' then parse_list_field(level[:key])
|
74
|
+
end
|
75
|
+
raise %(Key not found: #{level[:key]} as #{level_key} in #{data}) \
|
76
|
+
if data.is_a?(Array) || !data[level_key]
|
77
|
+
data[level_key]
|
52
78
|
end
|
@@ -1,9 +1,10 @@
|
|
1
1
|
require 'easy_diff'
|
2
2
|
|
3
|
+
# Adds deep_include? to the Hash class
|
3
4
|
class Hash
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
def deep_include?(other)
|
6
|
+
diff = other.easy_diff(self)
|
7
|
+
diff[0].delete_if { |_k, v| v.empty? if v.is_a?(::Hash) }
|
8
|
+
diff[0].empty?
|
9
|
+
end
|
9
10
|
end
|
@@ -2,51 +2,52 @@ require 'cucumber-rest-bdd/types'
|
|
2
2
|
require 'active_support/inflector'
|
3
3
|
|
4
4
|
ParameterType(
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
name: 'levels',
|
6
|
+
regexp: /((?: (?:for|in|on) (?:#{RESOURCE_NAME_SYNONYM})(?: with (?:key|id))? "[^"]*")*)/,
|
7
|
+
transformer: ->(levels) { Level.new(levels) },
|
8
|
+
use_for_snippets: false
|
9
9
|
)
|
10
10
|
|
11
|
+
# Helper class when creating nested resources
|
11
12
|
class Level
|
12
|
-
|
13
|
+
@urls = []
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
item[:id] = item[:id].to_i if item[:id].match(/^\d+$/)
|
23
|
-
arr.append(item)
|
24
|
-
end
|
25
|
-
@urls = arr.reverse
|
15
|
+
def initialize(levels)
|
16
|
+
arr = []
|
17
|
+
while (matches = /^ (?:for|in|on) ([^"]+?)(?: with (?:key|id))? "([^"]*)"/
|
18
|
+
.match(levels))
|
19
|
+
levels = levels[matches[0].length, levels.length]
|
20
|
+
item = { resource: get_resource(matches[1]), id: matches[2] }
|
21
|
+
item[:id] = item[:id].to_i if item[:id] =~ /^\d+$/
|
22
|
+
arr.append(item)
|
26
23
|
end
|
24
|
+
@urls = arr.reverse
|
25
|
+
end
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
def url
|
28
|
+
@urls.map { |l| "#{l[:resource]}/#{l[:id]}/" }.join
|
29
|
+
end
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
def hash
|
32
|
+
hash = {}
|
33
|
+
@urls.each do |l|
|
34
|
+
hash[parse_field("#{parse_field(l[:resource]).singularize}_id")] = l[:id]
|
36
35
|
end
|
36
|
+
hash
|
37
|
+
end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
46
|
-
return {}
|
39
|
+
def last_hash
|
40
|
+
last = @urls.last
|
41
|
+
unless last.nil?
|
42
|
+
key = parse_field("#{parse_field(last[:resource]).singularize}_id")
|
43
|
+
return {
|
44
|
+
key => last[:id]
|
45
|
+
}
|
47
46
|
end
|
47
|
+
{}
|
48
|
+
end
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
def to_s
|
51
|
+
url
|
52
|
+
end
|
52
53
|
end
|
@@ -1,144 +1,156 @@
|
|
1
1
|
require 'cucumber-rest-bdd/types'
|
2
2
|
|
3
|
-
FEWER_MORE_THAN_SYNONYM =
|
4
|
-
INT_AS_WORDS_SYNONYM =
|
3
|
+
FEWER_MORE_THAN_SYNONYM = '(?:fewer|less|more)\sthan|at\s(?:least|most)'.freeze
|
4
|
+
INT_AS_WORDS_SYNONYM = 'zero|one|two|three|four|five|six|seven|eight|nine|ten'
|
5
|
+
.freeze
|
5
6
|
|
6
7
|
ParameterType(
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
name: 'list_has_count',
|
9
|
+
regexp: /a|an|(?:(?:#{FEWER_MORE_THAN_SYNONYM})\s+)?(?:#{INT_AS_WORDS_SYNONYM}|\d+)/,
|
10
|
+
transformer: lambda { |match|
|
11
|
+
matches = /(?:(#{FEWER_MORE_THAN_SYNONYM})\s+)?
|
12
|
+
(#{INT_AS_WORDS_SYNONYM}|\d+)/x.match(match)
|
13
|
+
return ListCountComparison.new(matches[1], matches[2])
|
14
|
+
},
|
15
|
+
use_for_snippets: false
|
14
16
|
)
|
15
|
-
|
16
17
|
ParameterType(
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
name: 'list_nesting',
|
19
|
+
# rubocop:disable Metrics/LineLength
|
20
|
+
regexp: /(?:(?:#{HAVE_ALTERNATION.split('/').join('|')})?\s*(?:a list of\s+)?(?:a|an|(?:(?:#{FEWER_MORE_THAN_SYNONYM})\s+)?(?:#{INT_AS_WORDS_SYNONYM}|\d+))\s+(?:#{FIELD_NAME_SYNONYM})\s*)+/,
|
21
|
+
# rubocop:enable Metrics/LineLength
|
22
|
+
transformer: ->(match) { ListNesting.new(match) },
|
23
|
+
use_for_snippets: false
|
21
24
|
)
|
22
25
|
|
26
|
+
# Handle many children within objects or lists
|
23
27
|
class ListNesting
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
28
|
+
def initialize(match)
|
29
|
+
@match = match
|
30
|
+
# gets an array in the nesting format that nest_match_attributes understands
|
31
|
+
# to interrogate nested object and array data
|
32
|
+
grouping = []
|
33
|
+
nesting = match
|
34
|
+
|
35
|
+
minimal_list = /(?:#{HAVE_ALTERNATION.split('/').join('|')})?\s*
|
36
|
+
(?:(a\slist\sof)\s+)?(?:a|an|(?:(#{FEWER_MORE_THAN_SYNONYM})
|
37
|
+
\s+)?(#{INT_AS_WORDS_SYNONYM}|\d+))\s+
|
38
|
+
(#{FIELD_NAME_SYNONYM})/x
|
39
|
+
maximal_list = /(?:#{HAVE_ALTERNATION.split('/').join('|')})?\s*
|
40
|
+
(?:(a\slist\sof)\s+)?(?:a|an|(?:(#{FEWER_MORE_THAN_SYNONYM})
|
41
|
+
\s+)?(#{INT_AS_WORDS_SYNONYM}|\d+))\s+
|
42
|
+
(#{MAXIMAL_FIELD_NAME_SYNONYM})/x
|
43
|
+
while (matches = minimal_list.match(nesting))
|
44
|
+
next_matches = minimal_list.match(nesting[matches.end(0), nesting.length])
|
45
|
+
to_match = if next_matches.nil?
|
46
|
+
nesting
|
47
|
+
else
|
48
|
+
nesting[0, matches.end(0) + next_matches.begin(0)]
|
49
|
+
end
|
50
|
+
matches = maximal_list.match(to_match)
|
51
|
+
nesting = nesting[matches.end(0), nesting.length]
|
52
|
+
|
53
|
+
level = if matches[1].nil?
|
54
|
+
if matches[3].nil?
|
55
|
+
{
|
56
|
+
type: 'single',
|
57
|
+
key: matches[4],
|
58
|
+
root: false
|
59
|
+
}
|
44
60
|
else
|
45
|
-
|
46
|
-
|
47
|
-
key: matches[4],
|
48
|
-
comparison: ListCountComparison.new(matches[2], matches[3]),
|
49
|
-
root: false
|
50
|
-
}
|
51
|
-
end
|
52
|
-
else
|
53
|
-
level = {
|
54
|
-
type: 'list',
|
61
|
+
{
|
62
|
+
type: 'multiple',
|
55
63
|
key: matches[4],
|
56
64
|
comparison: ListCountComparison.new(matches[2], matches[3]),
|
57
65
|
root: false
|
66
|
+
}
|
67
|
+
end
|
68
|
+
else
|
69
|
+
{
|
70
|
+
type: 'list',
|
71
|
+
key: matches[4],
|
72
|
+
comparison: ListCountComparison.new(matches[2], matches[3]),
|
73
|
+
root: false
|
58
74
|
}
|
59
|
-
|
60
|
-
|
61
|
-
end
|
62
|
-
@grouping = grouping.reverse
|
75
|
+
end
|
76
|
+
grouping.push(level)
|
63
77
|
end
|
78
|
+
@grouping = grouping.reverse
|
79
|
+
end
|
64
80
|
|
65
|
-
|
66
|
-
|
67
|
-
|
81
|
+
def push(node)
|
82
|
+
@grouping.push(node)
|
83
|
+
end
|
68
84
|
|
69
|
-
|
70
|
-
return @match
|
71
|
-
end
|
85
|
+
attr_reader :match
|
72
86
|
|
73
|
-
|
74
|
-
@grouping
|
75
|
-
end
|
87
|
+
attr_reader :grouping
|
76
88
|
end
|
77
89
|
|
90
|
+
# Store a value and a comparison operator to determine if a provided number
|
91
|
+
# validates against it
|
78
92
|
class ListCountComparison
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
93
|
+
def initialize(type, amount)
|
94
|
+
@type = type.nil? ? CMP_EQUALS : to_compare(type)
|
95
|
+
@amount = amount.nil? ? 1 : to_num(amount)
|
96
|
+
end
|
97
|
+
|
98
|
+
def compare(actual)
|
99
|
+
case @type
|
100
|
+
when CMP_LESS_THAN then actual < @amount
|
101
|
+
when CMP_MORE_THAN then actual > @amount
|
102
|
+
when CMP_AT_MOST then actual <= @amount
|
103
|
+
when CMP_AT_LEAST then actual >= @amount
|
104
|
+
when CMP_EQUALS then actual == @amount
|
105
|
+
else actual == @amount
|
83
106
|
end
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
107
|
+
end
|
108
|
+
|
109
|
+
attr_reader :type
|
110
|
+
|
111
|
+
def amount
|
112
|
+
amount
|
113
|
+
end
|
114
|
+
|
115
|
+
# turn a comparison into a string
|
116
|
+
def compare_to_string
|
117
|
+
case @type
|
118
|
+
when CMP_LESS_THAN then 'fewer than '
|
119
|
+
when CMP_MORE_THAN then 'more than '
|
120
|
+
when CMP_AT_LEAST then 'at least '
|
121
|
+
when CMP_AT_MOST then 'at most '
|
122
|
+
when CMP_EQUALS then 'exactly '
|
123
|
+
else ''
|
94
124
|
end
|
125
|
+
end
|
95
126
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
def amount
|
101
|
-
return amount
|
102
|
-
end
|
103
|
-
|
104
|
-
# turn a comparison into a string
|
105
|
-
def compare_to_string()
|
106
|
-
case @type
|
107
|
-
when CMP_LESS_THAN then 'fewer than '
|
108
|
-
when CMP_MORE_THAN then 'more than '
|
109
|
-
when CMP_AT_LEAST then 'at least '
|
110
|
-
when CMP_AT_MOST then 'at most '
|
111
|
-
when CMP_EQUALS then 'exactly '
|
112
|
-
else ''
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def to_string()
|
117
|
-
return compare_to_string() + ' ' + @amount.to_s
|
118
|
-
end
|
127
|
+
def to_string
|
128
|
+
compare_to_string + ' ' + @amount.to_s
|
129
|
+
end
|
119
130
|
end
|
120
131
|
|
121
|
-
CMP_LESS_THAN = '<'
|
122
|
-
CMP_MORE_THAN = '>'
|
123
|
-
CMP_AT_LEAST = '>='
|
124
|
-
CMP_AT_MOST = '<='
|
125
|
-
CMP_EQUALS = '='
|
132
|
+
CMP_LESS_THAN = '<'.freeze
|
133
|
+
CMP_MORE_THAN = '>'.freeze
|
134
|
+
CMP_AT_LEAST = '>='.freeze
|
135
|
+
CMP_AT_MOST = '<='.freeze
|
136
|
+
CMP_EQUALS = '='.freeze
|
126
137
|
|
127
|
-
# take a number modifier string (fewer than, less than, etc) and return an
|
138
|
+
# take a number modifier string (fewer than, less than, etc) and return an
|
139
|
+
# operator '<', etc
|
128
140
|
def to_compare(compare)
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
141
|
+
case compare
|
142
|
+
when 'fewer than' then CMP_LESS_THAN
|
143
|
+
when 'less than' then CMP_LESS_THAN
|
144
|
+
when 'more than' then CMP_MORE_THAN
|
145
|
+
when 'at least' then CMP_AT_LEAST
|
146
|
+
when 'at most' then CMP_AT_MOST
|
147
|
+
else CMP_EQUALS
|
148
|
+
end
|
137
149
|
end
|
138
150
|
|
139
151
|
def to_num(num)
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
152
|
+
if num =~ /^(?:zero|one|two|three|four|five|six|seven|eight|nine|ten)$/
|
153
|
+
return %w[zero one two three four five six seven eight nine ten].index(num)
|
154
|
+
end
|
155
|
+
num.to_i
|
144
156
|
end
|