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.
@@ -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
- Then(/^print the response$/) do
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
- # response interrogation
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
- Then(/^the response is a list (?:of|containing) (#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT}|\d+) .*?$/) do |count_mod, count|
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 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)
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
- Then(/^the response ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:\w+) )*)#{HAVE_SYNONYM} (?:the )?(?:following )?(?:data|error )?attributes:$/) do |nesting, attributes|
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
- groups = nesting
18
- grouping = get_grouping(groups)
19
- grouping.push({
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
- Then(/^the response ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:\w+)\s?)+)$/) do |nesting|
28
- groups = nesting
29
- grouping = get_grouping(groups)
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: 'single'
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? || !nest_match(data, grouping, {})
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
- Then(/^(#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT}|\d+) (?:.*?) ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:\w+) )*)#{HAVE_SYNONYM} (?:the )?(?:following )?(?:data )?attributes:$/) do |count_mod, count, nesting, attributes|
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
- groups = nesting
41
- grouping = get_grouping(groups)
42
- grouping.push({
84
+ nesting.push({
43
85
  root: true,
44
86
  type: 'multiple',
45
- count: count.to_i,
46
- count_mod: count_mod
87
+ comparison: list_comparison
47
88
  })
48
- data = @response.get get_key(grouping)
49
- 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(data, grouping, expected)
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
- Then(/^(#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT}|\d+) (?:.*?) ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:\w+)\s?)+)$/) do |count_mod, count, nesting|
53
- groups = nesting
54
- grouping = get_grouping(groups)
55
- grouping.push({
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
- count: count.to_i,
59
- count_mod: count_mod
107
+ comparison: list_comparison
60
108
  })
61
- data = @response.get get_key(grouping)
62
- raise %/Expected #{compare_to_string(count_mod)}#{count} items in array with: #{nesting}\n#{@response.to_json_s}/ if !nest_match(data, grouping, {})
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
- Then(/^the response ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:\w+) )*)#{HAVE_SYNONYM} a list of (#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT} |\d+ )?(\w+)$/) do |nesting, num_mod, num, item|
66
- groups = nesting
67
- list = {
68
- type: 'list',
69
- key: get_resource(item)
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}#{compare_to_string(num_mod)}#{num} #{item}\n#{@response.to_json_s}/ if !nest_match(data, grouping, {})
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
- Then(/^(#{FEWER_MORE_THAN})?\s*(#{CAPTURE_INT}|\d+) (?:.*?) ((?:#{HAVE_SYNONYM} (?:a|an|(?:(?:#{FEWER_MORE_THAN})?\s*#{CAPTURE_INT}|\d+)) (?:\w+) )*)#{HAVE_SYNONYM} a list of (#{FEWER_MORE_THAN})?\s*(?:(#{CAPTURE_INT}|\d+) )?(\w+)$/) do |count_mod, count, nesting, num_mod, num, item|
86
- groups = nesting
87
- list = {
88
- type: 'list',
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: 'multiple',
100
- count: count.to_i,
101
- count_mod: count_mod
131
+ type: 'single'
102
132
  })
103
- data = @response.get get_key(grouping)
104
- 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(data, grouping, {})
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
- # top level has 2 children with an item containing at most three fish with attributes:
142
- #
143
- # 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}]
144
- #
145
- # returns true if the expected data is contained within the data based on the nesting information
146
- def nest_match(data, nesting, expected)
147
- return false if !data
148
- return data.deep_include?(expected) if nesting.size == 0
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
- Then(/^the request (?:is|was) successful$/) do
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(/^the request (?:is|was) redirected$/) do
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(/^(?:it|the request) fail(?:s|ed)$/) do
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(/^the (?!request)(?:.+?) (?:is|was) created$/) do
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(/^the request (?:is|was) successful and (?:a resource|.+) (?:is|was) created$/) do
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(/^the request (?:is|was) successfully accepted$/) do
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(/^the request (?:is|was) successful and (?:no|an empty) response body is returned$/) do
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(/^(?:it|the request) fail(?:s|ed) because it (?:is|was) invalid$/) do
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(/^(?:it|the request) fail(?:s|ed) because (?:.+) (?:is|was|am|are) unauthori[sz]ed$/) do
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(/^(?:it|the request) fail(?:s|ed) because (?:.+) (?:is|was) forbidden$/) do
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(/^(?:it|the request) fail(?:s|ed) because the (?:.+) (?:is|was) not found$/) do
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(/^(?:it|the request) fail(?:s|ed) because it (?:is|was) not allowed$/) do
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(/^(?:it|the request) fail(?:s|ed) because there (?:is|was|has) a conflict(?: with .+)?$/) do
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(/^(?:it|the request) fail(?:s|ed) because the (?:.+) (?:is|was|has) gone$/) do
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(/^(?:it|the request) fail(?:s|ed) because the (?:.+) (?:is|was) not implemented$/) do
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
- 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
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
- FEWER_MORE_THAN = Transform(/^(?:(?:fewer|less|more) than|at (?:least|most))$/) do |v|
8
- to_compare(v)
9
- end
22
+ module Boolean; end
23
+ class TrueClass; include Boolean; end
24
+ class FalseClass; include Boolean; end
10
25
 
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
26
+ module Enum; end
27
+ class String; include Enum; end
30
28
 
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 ''
29
+ class ResponseField
30
+ def initialize(names)
31
+ @fields = get_fields(names)
40
32
  end
41
- end
42
33
 
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
34
+ def to_json_path()
35
+ return "#{get_root_data_key()}#{@fields.join('.')}"
52
36
  end
53
- end
54
37
 
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)
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
- 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
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
- resource = name.parameterize
102
- resource = (ENV.has_key?('resource_single') && ENV['resource_single'] == 'true') ? resource.singularize : resource.pluralize
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 get_root_error_key()
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 get_json_path(names)
114
- return "#{get_root_data_key()}#{get_parameters(names).join('.')}"
111
+ def get_fields(names)
112
+ return names.split(':').map { |n| get_field(n.strip) }
115
113
  end
116
114
 
117
- def get_parameters(names)
118
- names.split(':').map { |n| get_parameter(n) }
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 get_parameter(name)
126
+ def get_list_field(name)
122
127
  if name[0] == '`' && name[-1] == '`'
123
128
  name = name[1..-2]
124
- else
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
- type = parse_type(type)
138
- names = get_parameters(name)
139
- new_hash = names.reverse.inject(value.to_type(type.camelize.constantize)) { |a, n| { n => a } }
140
- hash.deep_merge!(new_hash)
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