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.
@@ -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