cucumber-rest-bdd 0.5.3 → 0.6.0.pre.183
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 +5 -5
- data/lib/cucumber-rest-bdd/data.rb +52 -0
- data/lib/cucumber-rest-bdd/level.rb +8 -3
- data/lib/cucumber-rest-bdd/list.rb +144 -0
- data/lib/cucumber-rest-bdd/steps/functional.rb +6 -11
- data/lib/cucumber-rest-bdd/steps/resource.rb +36 -43
- data/lib/cucumber-rest-bdd/steps/response.rb +107 -132
- data/lib/cucumber-rest-bdd/steps/status.rb +54 -15
- data/lib/cucumber-rest-bdd/types.rb +129 -83
- data/lib/cucumber-rest-bdd/url.rb +4 -1
- metadata +33 -31
- data/.gitignore +0 -50
- data/.travis.yml +0 -23
- data/Dockerfile +0 -24
- data/LICENSE +0 -21
- data/Makefile +0 -15
- data/README.md +0 -338
- data/STEPS.md +0 -257
- data/cucumber-rest-bdd.gemspec +0 -19
- data/docker-compose.yml +0 -23
- data/features/errors.feature +0 -23
- data/features/functional.feature +0 -25
- data/features/grabs.feature +0 -21
- data/features/headers.feature +0 -16
- data/features/levels.feature +0 -121
- data/features/methods.feature +0 -101
- data/features/response.feature +0 -103
- data/features/status.feature +0 -18
- data/features/support/env.rb +0 -2
- data/features/types.feature +0 -48
- data/server/db.json +0 -41044
- data/server/error.js +0 -14
- data/server/routes.json +0 -5
@@ -1,170 +1,145 @@
|
|
1
1
|
require 'cucumber-rest-bdd/steps/resource'
|
2
2
|
require 'cucumber-rest-bdd/types'
|
3
|
+
require 'cucumber-rest-bdd/list'
|
4
|
+
require 'cucumber-rest-bdd/data'
|
3
5
|
|
4
|
-
|
6
|
+
ParameterType(
|
7
|
+
name: 'item_name',
|
8
|
+
regexp: /([\w\s]+?)/,
|
9
|
+
transformer: -> (s) { s },
|
10
|
+
use_for_snippets: false
|
11
|
+
)
|
12
|
+
|
13
|
+
Then("print the response") do
|
5
14
|
puts %/The response:\n#{@response.to_json_s}/
|
6
15
|
end
|
7
16
|
|
8
|
-
#
|
17
|
+
# SIMPLE VALUE RESPONSE
|
18
|
+
|
19
|
+
# response is a string with the specified value
|
20
|
+
Then("the response #{HAVE_ALTERNATION} (the )(following )value {string}") do |value|
|
21
|
+
expected = value
|
22
|
+
data = @response.get get_root_data_key()
|
23
|
+
raise %/Response did not match: #{expected}\n#{data}/ if data.empty? || !data.include?(expected)
|
24
|
+
end
|
25
|
+
|
26
|
+
# OBJECT RESPONSE
|
27
|
+
|
28
|
+
# response is an object with a field that is validated by a pre-defined regex
|
29
|
+
Then("the response #{HAVE_ALTERNATION} {field_name} of type {word}") do |field, type|
|
30
|
+
regex = case type
|
31
|
+
when 'datetime' then /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:[+|-]\d{2}:\d{2})?$/i
|
32
|
+
when 'guid' then /^[{(]?[0-9A-F]{8}[-]?([0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$/i
|
33
|
+
else nil
|
34
|
+
end
|
35
|
+
|
36
|
+
type = 'string' if regex.nil?
|
37
|
+
value = field.get_value(@response, type)
|
38
|
+
field.validate_value(@response, value.to_s, Regexp.new(regex))
|
39
|
+
end
|
40
|
+
|
41
|
+
# response is an object with a field that is validated by a custom regex
|
42
|
+
Then("the response #{HAVE_ALTERNATION} {field_name} of type {word} that matches {string}") do |field, type, regex|
|
43
|
+
value = field.get_value(@response, type)
|
44
|
+
field.validate_value(@response, value.to_s, Regexp.new(regex))
|
45
|
+
end
|
9
46
|
|
10
|
-
|
47
|
+
# response is an object with specific attributes having defined values
|
48
|
+
Then("the response #{HAVE_ALTERNATION} (the )(following )attributes:") do |attributes|
|
49
|
+
expected = get_attributes(attributes.hashes)
|
50
|
+
data = @response.get get_root_data_key()
|
51
|
+
raise %/Response did not match:\n#{expected.inspect}\n#{data}/ if data.empty? || !data.deep_include?(expected)
|
52
|
+
end
|
53
|
+
|
54
|
+
# ARRAY RESPONSE
|
55
|
+
|
56
|
+
# response is an array of objects
|
57
|
+
Then("the response is a list of/containing {list_has_count} {field_name}") do |list_comparison, item|
|
11
58
|
list = @response.get_as_type get_root_data_key(), 'array'
|
12
|
-
raise %/Expected
|
59
|
+
raise %/Expected #{list_comparison.to_string()} items in array for path '#{get_root_data_key()}', found: #{list.count}\n#{@response.to_json_s}/ if !list_comparison.compare(list.count)
|
13
60
|
end
|
14
61
|
|
15
|
-
|
62
|
+
# response is an array of objects where the specified number of entries match the defined data attributes
|
63
|
+
Then("(the response is a list with ){list_has_count} {word} #{HAVE_ALTERNATION} (the )(following )(data )attributes:") do |list_comparison, item_name, attributes|
|
16
64
|
expected = get_attributes(attributes.hashes)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
root: true,
|
21
|
-
type: 'single'
|
22
|
-
})
|
23
|
-
data = @response.get get_key(grouping)
|
24
|
-
raise %/Could not find a match for: #{nesting}\n#{expected.inspect}\n#{@response.to_json_s}/ if data.empty? || !nest_match(data, grouping, expected)
|
65
|
+
data = @response.get_as_type get_root_data_key(), 'array'
|
66
|
+
matched = data.select { |item| !item.empty? && item.deep_include?(expected) }
|
67
|
+
raise %/Expected #{list_comparison.to_string()} items in array that matched:\n#{expected.inspect}\n#{data}/ if !list_comparison.compare(matched.count)
|
25
68
|
end
|
26
69
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
grouping.push({
|
70
|
+
# response is an array of objects where the specified number of entries match the defined data attributes
|
71
|
+
Then("(the response is a list with ){list_has_count} {item_name} {list_nesting}") do |list_comparison, item_name, nesting|
|
72
|
+
nesting.push({
|
31
73
|
root: true,
|
32
|
-
type: '
|
74
|
+
type: 'multiple',
|
75
|
+
comparison: list_comparison
|
33
76
|
})
|
34
|
-
data = @response.get get_key(grouping)
|
35
|
-
raise %/Could not find a match for: #{nesting}\n#{@response.to_json_s}/ if data.empty? || !
|
77
|
+
data = @response.get get_key(nesting.grouping)
|
78
|
+
raise %/Could not find a match for: #{nesting.match}\n#{@response.to_json_s}/ if data.empty? || !nest_match_attributes(data, nesting.grouping, {}, false)
|
36
79
|
end
|
37
80
|
|
38
|
-
|
81
|
+
# response is an array of objects where the specified number of entries match the defined data attributes
|
82
|
+
Then("(the response is a list with ){list_has_count} {item_name} {list_nesting} #{HAVE_ALTERNATION} (the )(following )(data )attributes:") do |list_comparison, item_name, nesting, attributes|
|
39
83
|
expected = get_attributes(attributes.hashes)
|
40
|
-
|
41
|
-
grouping = get_grouping(groups)
|
42
|
-
grouping.push({
|
84
|
+
nesting.push({
|
43
85
|
root: true,
|
44
86
|
type: 'multiple',
|
45
|
-
|
46
|
-
count_mod: count_mod
|
87
|
+
comparison: list_comparison
|
47
88
|
})
|
48
|
-
data = @response.get get_key(grouping)
|
49
|
-
raise %/
|
89
|
+
data = @response.get get_key(nesting.grouping)
|
90
|
+
raise %/Could not find a match for: #{nesting.match}\n#{expected.inspect}\n#{@response.to_json_s}/ if data.empty? || !nest_match_attributes(data, nesting.grouping, expected, false)
|
50
91
|
end
|
51
92
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
93
|
+
# response is an array of objects where the specified number of entries match the defined string value
|
94
|
+
Then("(the response is a list with ){list_has_count} {item_name} having/containing/with (the )(following )value {string}") do |list_comparison, item_name, value|
|
95
|
+
expected = value
|
96
|
+
data = @response.get_as_type get_root_data_key(), 'array'
|
97
|
+
matched = data.select { |item| !item.empty? && item.include?(expected) }
|
98
|
+
raise %/Expected #{list_comparison.to_string()} items in array that matched:\n#{expected}\n#{data}/ if !list_comparison.compare(matched.count)
|
99
|
+
end
|
100
|
+
|
101
|
+
# response is an array of objects where the specified number of entries match the defined string value
|
102
|
+
Then("(the response is a list with ){list_has_count} {item_name} {list_nesting} #{HAVE_ALTERNATION} (the )(following )value {string}") do |list_comparison, item_name, nesting, value|
|
103
|
+
expected = value
|
104
|
+
nesting.push({
|
56
105
|
root: true,
|
57
106
|
type: 'multiple',
|
58
|
-
|
59
|
-
count_mod: count_mod
|
107
|
+
comparison: list_comparison
|
60
108
|
})
|
61
|
-
data = @response.get get_key(grouping)
|
62
|
-
raise %/
|
109
|
+
data = @response.get get_key(nesting.grouping)
|
110
|
+
raise %/Could not find a match for: #{nesting.match}\n#{expected}\n#{@response.to_json_s}/ if data.empty? || !nest_match_attributes(data, nesting.grouping, expected, true)
|
63
111
|
end
|
64
112
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
if (num) then
|
72
|
-
list[:count] = num.to_i
|
73
|
-
list[:count_mod] = num_mod
|
74
|
-
end
|
75
|
-
grouping = [list]
|
76
|
-
grouping.concat(get_grouping(groups))
|
77
|
-
grouping.push({
|
113
|
+
# HIERARCHICAL RESPONSE
|
114
|
+
|
115
|
+
# response has the specified hierarchy of objects / lists where the specified number of leaf items match the defined data attributes
|
116
|
+
Then("the response {list_nesting} #{HAVE_ALTERNATION} (the )(following )attributes:") do |nesting, attributes|
|
117
|
+
expected = get_attributes(attributes.hashes)
|
118
|
+
nesting.push({
|
78
119
|
root: true,
|
79
120
|
type: 'single'
|
80
121
|
})
|
81
|
-
data = @response.get get_key(grouping)
|
82
|
-
raise %/Could not find a match for #{nesting}#{
|
122
|
+
data = @response.get get_key(nesting.grouping)
|
123
|
+
raise %/Could not find a match for: #{nesting.match}\n#{expected.inspect}\n#{@response.to_json_s}/ if data.empty? || !nest_match_attributes(data, nesting.grouping, expected, false)
|
83
124
|
end
|
84
125
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
key: get_resource(item)
|
90
|
-
}
|
91
|
-
if (num) then
|
92
|
-
list[:count] = num.to_i
|
93
|
-
list[:count_mod] = num_mod
|
94
|
-
end
|
95
|
-
grouping = [list]
|
96
|
-
grouping.concat(get_grouping(groups))
|
97
|
-
grouping.push({
|
126
|
+
# response has the specified hierarchy of objects / lists where the specified number of leaf items match the defined string value
|
127
|
+
Then("the response {list_nesting} #{HAVE_ALTERNATION} (the )(following )value {string}") do |nesting, value|
|
128
|
+
expected = value
|
129
|
+
nesting.push({
|
98
130
|
root: true,
|
99
|
-
type: '
|
100
|
-
count: count.to_i,
|
101
|
-
count_mod: count_mod
|
131
|
+
type: 'single'
|
102
132
|
})
|
103
|
-
data = @response.get get_key(grouping)
|
104
|
-
raise %/
|
105
|
-
end
|
106
|
-
|
107
|
-
# gets the relevant key for the response based on the first key element
|
108
|
-
def get_key(grouping)
|
109
|
-
if ENV['error_key'] && !ENV['error_key'].empty? && grouping.count > 1 && grouping[-2][:key].singularize == ENV['error_key'] then
|
110
|
-
get_root_error_key()
|
111
|
-
else
|
112
|
-
get_root_data_key()
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
# gets an array in the nesting format that nest_match understands to interrogate nested object and array data
|
117
|
-
def get_grouping(nesting)
|
118
|
-
grouping = []
|
119
|
-
while matches = /^#{HAVE_SYNONYM} (?:a|an|(?:(#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT}|\d+))) (\w+)\s?+/.match(nesting)
|
120
|
-
nesting = nesting[matches[0].length, nesting.length]
|
121
|
-
if matches[2].nil? then
|
122
|
-
level = {
|
123
|
-
type: 'single',
|
124
|
-
key: get_parameter(matches[3]),
|
125
|
-
root: false
|
126
|
-
}
|
127
|
-
else
|
128
|
-
level = {
|
129
|
-
type: 'multiple',
|
130
|
-
key: get_parameter(matches[3]),
|
131
|
-
count: to_num(matches[2]),
|
132
|
-
root: false,
|
133
|
-
count_mod: to_compare(matches[1])
|
134
|
-
}
|
135
|
-
end
|
136
|
-
grouping.push(level)
|
137
|
-
end
|
138
|
-
grouping.reverse
|
133
|
+
data = @response.get get_key(nesting.grouping)
|
134
|
+
raise %/Could not find a match for: #{nesting.match}\n#{expected}\n#{@response.to_json_s}/ if data.empty? || !nest_match_attributes(data, nesting.grouping, expected, true)
|
139
135
|
end
|
140
136
|
|
141
|
-
#
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
local_nesting = nesting.dup
|
151
|
-
level = local_nesting.pop
|
152
|
-
case level[:type]
|
153
|
-
when 'single' then
|
154
|
-
child_data = level[:root] ? data.dup : data[get_parameter(level[:key])]
|
155
|
-
return nest_match(child_data, local_nesting, expected)
|
156
|
-
when 'multiple' then
|
157
|
-
child_data = level[:root] ? data.dup : data[get_resource(level[:key])]
|
158
|
-
matched = child_data.select { |item| nest_match(item, local_nesting, expected) }
|
159
|
-
return num_compare(level[:count_mod], matched.count, level[:count])
|
160
|
-
when 'list' then
|
161
|
-
child_data = level[:root] ? data.dup : data[get_resource(level[:key])]
|
162
|
-
return false if !child_data.is_a?(Array)
|
163
|
-
if level.has_key?(:count) then
|
164
|
-
return num_compare(level[:count_mod], child_data.count, level[:count])
|
165
|
-
end
|
166
|
-
return true
|
167
|
-
else
|
168
|
-
raise %/Unknown nested data type: #{level[:type]}/
|
169
|
-
end
|
137
|
+
# response has the specified hierarchy of objects / lists where the specified number of leaf items is as expected only (no data checked)
|
138
|
+
Then("the response {list_nesting}") do |nesting|
|
139
|
+
nesting.push({
|
140
|
+
root: true,
|
141
|
+
type: 'single'
|
142
|
+
})
|
143
|
+
data = @response.get get_key(nesting.grouping)
|
144
|
+
raise %/Could not find a match for: #{nesting.match}\n#{@response.to_json_s}/ if data.empty? || !nest_match_attributes(data, nesting.grouping, {}, false)
|
170
145
|
end
|
@@ -1,63 +1,102 @@
|
|
1
1
|
require 'cucumber-api/response'
|
2
2
|
require 'cucumber-api/steps'
|
3
3
|
|
4
|
-
|
4
|
+
ParameterType(
|
5
|
+
name: 'item_type',
|
6
|
+
regexp: /([\w\s]+)/,
|
7
|
+
transformer: -> (s) { s },
|
8
|
+
use_for_snippets: false
|
9
|
+
)
|
10
|
+
|
11
|
+
Then("the request is/was successful") do
|
5
12
|
raise %/Expected Successful response code 2xx but was #{@response.code}/ if @response.code < 200 || @response.code >= 300
|
6
13
|
end
|
7
14
|
|
8
|
-
Then(
|
15
|
+
Then("the request is/was redirected") do
|
9
16
|
raise %/Expected redirected response code 3xx but was #{@response.code}/ if @response.code < 300 || @response.code >= 400
|
10
17
|
end
|
11
18
|
|
12
|
-
Then(
|
19
|
+
Then("the request fail(s/ed)") do
|
13
20
|
raise %/Expected failed response code 4xx\/5xx but was #{@response.code}/ if @response.code < 400 || @response.code >= 600
|
14
21
|
end
|
15
22
|
|
16
|
-
Then(
|
23
|
+
Then("the request is/was successful and a/the resource is/was created") do
|
17
24
|
steps %Q{Then the response status should be "201"}
|
18
25
|
end
|
19
26
|
|
20
|
-
Then(
|
27
|
+
Then("(the request is/was successful and )a/the {item_type} is/was created") do | item_type |
|
21
28
|
steps %Q{Then the response status should be "201"}
|
22
29
|
end
|
23
30
|
|
24
|
-
Then(
|
31
|
+
Then("the request is/was successfully accepted") do
|
25
32
|
steps %Q{Then the response status should be "202"}
|
26
33
|
end
|
27
34
|
|
28
|
-
Then(
|
35
|
+
Then("the request is/was successful and (an )empty/blank/no response body is/was returned") do
|
29
36
|
steps %Q{Then the response status should be "204"}
|
30
37
|
raise %/Expected the request body to be empty/ if !@response.body.empty?
|
31
38
|
end
|
32
39
|
|
33
|
-
Then(
|
40
|
+
Then("the request fail(s/ed) because (the )it/resource is/was invalid") do
|
41
|
+
steps %Q{Then the response status should be "400"}
|
42
|
+
end
|
43
|
+
|
44
|
+
Then("the request fail(s/ed) because (the ){item_type} is/was invalid") do | item_type |
|
34
45
|
steps %Q{Then the response status should be "400"}
|
35
46
|
end
|
36
47
|
|
37
|
-
Then(
|
48
|
+
Then("the request fail(s/ed) because (the )it/resource is/was/am/are unauthorised/unauthorized") do
|
49
|
+
steps %Q{Then the response status should be "401"}
|
50
|
+
end
|
51
|
+
|
52
|
+
Then("the request fail(s/ed) because (the ){item_type} is/was/am/are unauthorised/unauthorized") do | item_type |
|
38
53
|
steps %Q{Then the response status should be "401"}
|
39
54
|
end
|
40
55
|
|
41
|
-
Then(
|
56
|
+
Then("the request fail(s/ed) because (the )it/resource is/was forbidden") do
|
42
57
|
steps %Q{Then the response status should be "403"}
|
43
58
|
end
|
44
59
|
|
45
|
-
Then(
|
60
|
+
Then("the request fail(s/ed) because (the ){item_type} is/was forbidden") do | item_type |
|
61
|
+
steps %Q{Then the response status should be "403"}
|
62
|
+
end
|
63
|
+
|
64
|
+
Then("the request fail(s/ed) because (the )it/resource is/was not found") do
|
65
|
+
steps %Q{Then the response status should be "404"}
|
66
|
+
end
|
67
|
+
|
68
|
+
Then("the request fail(s/ed) because (the ){item_type} is/was not found") do | item_type |
|
46
69
|
steps %Q{Then the response status should be "404"}
|
47
70
|
end
|
48
71
|
|
49
|
-
Then(
|
72
|
+
Then("the request fail(s/ed) because (the )it/resource is/was not allowed") do
|
50
73
|
steps %Q{Then the response status should be "405"}
|
51
74
|
end
|
52
75
|
|
53
|
-
Then(
|
76
|
+
Then("the request fail(s/ed) because (the ){item_type} is/was not allowed") do | item_type |
|
77
|
+
steps %Q{Then the response status should be "405"}
|
78
|
+
end
|
79
|
+
|
80
|
+
Then("the request fail(s/ed) because there is/was/has a conflict") do
|
81
|
+
steps %Q{Then the response status should be "409"}
|
82
|
+
end
|
83
|
+
|
84
|
+
Then("the request fail(s/ed) because there is/was/has a conflict with {item_type}") do | item_type |
|
54
85
|
steps %Q{Then the response status should be "409"}
|
55
86
|
end
|
56
87
|
|
57
|
-
Then(
|
88
|
+
Then("the request fail(s/ed) because (the )it/resource is/was/has gone") do
|
58
89
|
steps %Q{Then the response status should be "410"}
|
59
90
|
end
|
60
91
|
|
61
|
-
Then(
|
92
|
+
Then("the request fail(s/ed) because (the ){item_type} is/was/has gone") do | item_type |
|
93
|
+
steps %Q{Then the response status should be "410"}
|
94
|
+
end
|
95
|
+
|
96
|
+
Then("the request fail(s/ed) because (the )it/resource is/was not implemented") do
|
97
|
+
steps %Q{Then the response status should be "501"}
|
98
|
+
end
|
99
|
+
|
100
|
+
Then("the request fail(s/ed) because (the ){item_type} is/was not implemented") do | item_type |
|
62
101
|
steps %Q{Then the response status should be "501"}
|
63
102
|
end
|
@@ -1,85 +1,69 @@
|
|
1
1
|
require 'active_support/inflector'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
HAVE_ALTERNATION = "has/have/having/contain/contains/containing/with"
|
4
|
+
RESOURCE_NAME_SYNONYM = '\w+\b(?:\s+\w+\b)*?|`[^`]*`'
|
5
|
+
FIELD_NAME_SYNONYM = '\w+\b(?:(?:\s+:)?\s+\w+\b)*?|`[^`]*`'
|
6
|
+
MAXIMAL_FIELD_NAME_SYNONYM = '\w+\b(?:(?:\s+:)?\s+\w+\b)*|`[^`]*`'
|
7
|
+
|
8
|
+
ParameterType(
|
9
|
+
name: 'resource_name',
|
10
|
+
regexp: /#{RESOURCE_NAME_SYNONYM}/,
|
11
|
+
transformer: -> (s) { get_resource(s) },
|
12
|
+
use_for_snippets: false
|
13
|
+
)
|
14
|
+
|
15
|
+
ParameterType(
|
16
|
+
name: 'field_name',
|
17
|
+
regexp: /#{FIELD_NAME_SYNONYM}/,
|
18
|
+
transformer: -> (s) { ResponseField.new(s) },
|
19
|
+
use_for_snippets: false
|
20
|
+
)
|
6
21
|
|
7
|
-
|
8
|
-
|
9
|
-
end
|
22
|
+
module Boolean; end
|
23
|
+
class TrueClass; include Boolean; end
|
24
|
+
class FalseClass; include Boolean; end
|
10
25
|
|
11
|
-
|
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
|
26
|
+
module Enum; end
|
27
|
+
class String; include Enum; end
|
30
28
|
|
31
|
-
|
32
|
-
def
|
33
|
-
|
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 ''
|
29
|
+
class ResponseField
|
30
|
+
def initialize(names)
|
31
|
+
@fields = get_fields(names)
|
40
32
|
end
|
41
|
-
end
|
42
33
|
|
43
|
-
|
44
|
-
|
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
|
34
|
+
def to_json_path()
|
35
|
+
return "#{get_root_data_key()}#{@fields.join('.')}"
|
52
36
|
end
|
53
|
-
end
|
54
37
|
|
55
|
-
def
|
56
|
-
|
57
|
-
return %w(zero one two three four five six seven eight nine ten).index(num)
|
38
|
+
def get_value(response, type)
|
39
|
+
return response.get_as_type to_json_path(), parse_type(type)
|
58
40
|
end
|
59
|
-
return num.to_i
|
60
|
-
end
|
61
41
|
|
62
|
-
|
63
|
-
|
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
|
42
|
+
def validate_value(response, value, regex)
|
43
|
+
raise %/Expected #{json_path} value '#{value}' to match regex: #{regex}\n#{response.to_json_s}/ if (regex =~ value).nil?
|
78
44
|
end
|
79
|
-
end
|
80
45
|
end
|
81
46
|
|
82
47
|
def parse_type(type)
|
48
|
+
replacements = {
|
49
|
+
/^numeric$/i => 'numeric',
|
50
|
+
/^int$/i => 'numeric',
|
51
|
+
/^long$/i => 'numeric',
|
52
|
+
/^number$/i => 'numeric',
|
53
|
+
/^decimal$/i => 'numeric',
|
54
|
+
/^double$/i => 'numeric',
|
55
|
+
/^bool$/i => 'boolean',
|
56
|
+
/^null$/i => 'nil_class',
|
57
|
+
/^nil$/i => 'nil_class',
|
58
|
+
/^string$/i => 'string',
|
59
|
+
/^text$/i => 'string'
|
60
|
+
}
|
61
|
+
type.tr(' ', '_')
|
62
|
+
replacements.each { |k,v| type.gsub!(k, v) }
|
63
|
+
type
|
64
|
+
end
|
65
|
+
|
66
|
+
def string_to_type(value, type)
|
83
67
|
replacements = {
|
84
68
|
/^numeric$/i => 'integer',
|
85
69
|
/^int$/i => 'integer',
|
@@ -90,53 +74,115 @@ def parse_type(type)
|
|
90
74
|
/^bool$/i => 'boolean',
|
91
75
|
/^null$/i => 'nil_class',
|
92
76
|
/^nil$/i => 'nil_class',
|
77
|
+
/^string$/i => 'string',
|
93
78
|
/^text$/i => 'string'
|
94
79
|
}
|
95
80
|
type.tr(' ', '_')
|
96
81
|
replacements.each { |k,v| type.gsub!(k, v) }
|
97
|
-
type
|
82
|
+
type = type.camelize.constantize
|
83
|
+
# cannot use 'case type' which checks for instances of a type rather than type equality
|
84
|
+
if type == Boolean then !(value =~ /true|yes/i).nil?
|
85
|
+
elsif type == Enum then value.upcase.tr(" ", "_")
|
86
|
+
elsif type == Float then value.to_f
|
87
|
+
elsif type == Integer then value.to_i
|
88
|
+
elsif type == NilClass then nil
|
89
|
+
else value
|
90
|
+
end
|
98
91
|
end
|
99
92
|
|
100
93
|
def get_resource(name)
|
101
|
-
|
102
|
-
|
94
|
+
if name[0] == '`' && name[-1] == '`'
|
95
|
+
name = name[1..-2]
|
96
|
+
else
|
97
|
+
name = name.parameterize
|
98
|
+
name = (ENV.has_key?('resource_single') && ENV['resource_single'] == 'true') ? name.singularize : name.pluralize
|
99
|
+
end
|
100
|
+
return name
|
103
101
|
end
|
104
102
|
|
105
103
|
def get_root_data_key()
|
106
104
|
return ENV.has_key?('data_key') && !ENV['data_key'].empty? ? "$.#{ENV['data_key']}." : "$."
|
107
105
|
end
|
108
106
|
|
109
|
-
def
|
110
|
-
return "
|
107
|
+
def get_json_path(names)
|
108
|
+
return "#{get_root_data_key()}#{get_fields(names).join('.')}"
|
111
109
|
end
|
112
110
|
|
113
|
-
def
|
114
|
-
return
|
111
|
+
def get_fields(names)
|
112
|
+
return names.split(':').map { |n| get_field(n.strip) }
|
115
113
|
end
|
116
114
|
|
117
|
-
def
|
118
|
-
|
115
|
+
def get_field(name)
|
116
|
+
if name[0] == '`' && name[-1] == '`'
|
117
|
+
name = name[1..-2]
|
118
|
+
elsif name[0] != '[' || name[-1] != ']'
|
119
|
+
separator = ENV.has_key?('field_separator') ? ENV['field_separator'] : '_'
|
120
|
+
name = name.parameterize(separator: separator)
|
121
|
+
name = name.camelize(:lower) if (ENV.has_key?('field_camel') && ENV['field_camel'] == 'true')
|
122
|
+
end
|
123
|
+
return name
|
119
124
|
end
|
120
125
|
|
121
|
-
def
|
126
|
+
def get_list_field(name)
|
122
127
|
if name[0] == '`' && name[-1] == '`'
|
123
128
|
name = name[1..-2]
|
124
|
-
|
129
|
+
elsif name[0] != '[' || name[-1] != ']'
|
125
130
|
separator = ENV.has_key?('field_separator') ? ENV['field_separator'] : '_'
|
126
131
|
name = name.parameterize(separator: separator)
|
132
|
+
name = name.pluralize
|
127
133
|
name = name.camelize(:lower) if (ENV.has_key?('field_camel') && ENV['field_camel'] == 'true')
|
128
134
|
end
|
129
|
-
name
|
135
|
+
return name
|
130
136
|
end
|
131
137
|
|
132
138
|
def get_attributes(hashes)
|
133
139
|
attributes = hashes.each_with_object({}) do |row, hash|
|
134
140
|
name, value, type = row["attribute"], row["value"], row["type"]
|
141
|
+
value = resolve_functions(value)
|
135
142
|
value = resolve(value)
|
136
143
|
value.gsub!(/\\n/, "\n")
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
144
|
+
names = get_fields(name)
|
145
|
+
new_hash = names.reverse.inject(string_to_type(value, type)) { |a, n| add_to_hash(a, n) }
|
146
|
+
hash.deep_merge!(new_hash) { |key, old, new| new.kind_of?(Array) ? merge_arrays(old, new) : new }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def resolve_functions(value)
|
151
|
+
value.gsub!(/\[([a-zA-Z0-9_]+)\]/) do |s|
|
152
|
+
s.gsub!(/[\[\]]/, '')
|
153
|
+
case s.downcase
|
154
|
+
when "datetime"
|
155
|
+
Time.now.strftime("%Y%m%d%H%M%S")
|
156
|
+
else
|
157
|
+
raise 'Unrecognised function ' + s + '?'
|
158
|
+
end
|
159
|
+
end
|
160
|
+
value
|
161
|
+
end
|
162
|
+
|
163
|
+
def add_to_hash(a, n)
|
164
|
+
result = nil
|
165
|
+
if (n[0] == '[' && n[-1] == ']') then
|
166
|
+
array = Array.new(n[1..-2].to_i() + 1)
|
167
|
+
array[n[1..-2].to_i()] = a
|
168
|
+
result = array
|
169
|
+
end
|
170
|
+
result != nil ? result : { n => a };
|
171
|
+
end
|
172
|
+
|
173
|
+
def merge_arrays(a, b)
|
174
|
+
new_length = [a.length, b.length].max
|
175
|
+
new_array = Array.new(new_length)
|
176
|
+
new_length.times do |n|
|
177
|
+
if b[n].nil? then
|
178
|
+
new_array[n] = a[n]
|
179
|
+
else
|
180
|
+
if a[n].nil? then
|
181
|
+
new_array[n] = b[n]
|
182
|
+
else
|
183
|
+
new_array[n] = a[n].merge(b[n])
|
184
|
+
end
|
185
|
+
end
|
141
186
|
end
|
187
|
+
return new_array
|
142
188
|
end
|