cucumber-rest-bdd 0.5.3 → 0.6.0.pre.183
Sign up to get free protection for your applications and to get access to all the features.
- 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
|