cucumber-rest-bdd 0.6.0 → 0.6.1
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 +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
|