cucumber-rest-bdd 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/cucumber-rest-bdd/data.rb +66 -40
- data/lib/cucumber-rest-bdd/hash.rb +6 -5
- data/lib/cucumber-rest-bdd/level.rb +37 -36
- data/lib/cucumber-rest-bdd/list.rb +124 -112
- data/lib/cucumber-rest-bdd/steps/functional.rb +39 -14
- data/lib/cucumber-rest-bdd/steps/resource.rb +102 -80
- data/lib/cucumber-rest-bdd/steps/response.rb +131 -88
- data/lib/cucumber-rest-bdd/steps/status.rb +57 -51
- data/lib/cucumber-rest-bdd/types.rb +150 -141
- data/lib/cucumber-rest-bdd/url.rb +6 -5
- metadata +17 -15
@@ -2,101 +2,107 @@ require 'cucumber-api/response'
|
|
2
2
|
require 'cucumber-api/steps'
|
3
3
|
|
4
4
|
ParameterType(
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
name: 'item_type',
|
6
|
+
regexp: /([\w\s]+)/,
|
7
|
+
transformer: ->(s) { s },
|
8
|
+
use_for_snippets: false
|
9
9
|
)
|
10
10
|
|
11
|
-
Then(
|
12
|
-
|
11
|
+
Then('the request is/was successful') do
|
12
|
+
if @response.code < 200 || @response.code >= 300
|
13
|
+
raise %(Expected Successful response code 2xx but was #{@response.code})
|
14
|
+
end
|
13
15
|
end
|
14
16
|
|
15
|
-
Then(
|
16
|
-
|
17
|
+
Then('the request is/was redirected') do
|
18
|
+
if @response.code < 300 || @response.code >= 400
|
19
|
+
raise %(Expected redirected response code 3xx but was #{@response.code})
|
20
|
+
end
|
17
21
|
end
|
18
22
|
|
19
|
-
Then(
|
20
|
-
|
23
|
+
Then('the request fail(s/ed)') do
|
24
|
+
if @response.code < 400 || @response.code >= 600
|
25
|
+
raise %(Expected failed response code 4xx\/5xx but was #{@response.code})
|
26
|
+
end
|
21
27
|
end
|
22
28
|
|
23
|
-
Then(
|
24
|
-
|
29
|
+
Then('the request is/was successful and a/the resource is/was created') do
|
30
|
+
steps %(Then the response status should be "201")
|
25
31
|
end
|
26
32
|
|
27
|
-
Then(
|
28
|
-
|
33
|
+
Then('(the request is/was successful and )a/the {item_type} is/was created') do |_item_type|
|
34
|
+
steps %(Then the response status should be "201")
|
29
35
|
end
|
30
36
|
|
31
|
-
Then(
|
32
|
-
steps %
|
37
|
+
Then('the request is/was successfully accepted') do
|
38
|
+
steps %(Then the response status should be "202")
|
33
39
|
end
|
34
40
|
|
35
|
-
Then(
|
36
|
-
steps %
|
37
|
-
raise
|
41
|
+
Then('the request is/was successful and (an )empty/blank/no response body is/was returned') do
|
42
|
+
steps %(Then the response status should be "204")
|
43
|
+
raise %(Expected the request body to be empty) unless @response.body.empty?
|
38
44
|
end
|
39
45
|
|
40
|
-
Then(
|
41
|
-
|
46
|
+
Then('the request fail(s/ed) because (the )it/resource is/was invalid') do
|
47
|
+
steps %(Then the response status should be "400")
|
42
48
|
end
|
43
49
|
|
44
|
-
Then(
|
45
|
-
|
50
|
+
Then('the request fail(s/ed) because (the ){item_type} is/was invalid') do |_item_type|
|
51
|
+
steps %(Then the response status should be "400")
|
46
52
|
end
|
47
53
|
|
48
|
-
Then(
|
49
|
-
|
54
|
+
Then('the request fail(s/ed) because (the )it/resource is/was/am/are unauthorised/unauthorized') do
|
55
|
+
steps %(Then the response status should be "401")
|
50
56
|
end
|
51
57
|
|
52
|
-
Then(
|
53
|
-
|
58
|
+
Then('the request fail(s/ed) because (the ){item_type} is/was/am/are unauthorised/unauthorized') do |_item_type|
|
59
|
+
steps %(Then the response status should be "401")
|
54
60
|
end
|
55
61
|
|
56
|
-
Then(
|
57
|
-
|
62
|
+
Then('the request fail(s/ed) because (the )it/resource is/was forbidden') do
|
63
|
+
steps %(Then the response status should be "403")
|
58
64
|
end
|
59
65
|
|
60
|
-
Then(
|
61
|
-
|
66
|
+
Then('the request fail(s/ed) because (the ){item_type} is/was forbidden') do |_item_type|
|
67
|
+
steps %(Then the response status should be "403")
|
62
68
|
end
|
63
69
|
|
64
|
-
Then(
|
65
|
-
|
70
|
+
Then('the request fail(s/ed) because (the )it/resource is/was not found') do
|
71
|
+
steps %(Then the response status should be "404")
|
66
72
|
end
|
67
73
|
|
68
|
-
Then(
|
69
|
-
|
74
|
+
Then('the request fail(s/ed) because (the ){item_type} is/was not found') do |_item_type|
|
75
|
+
steps %(Then the response status should be "404")
|
70
76
|
end
|
71
77
|
|
72
|
-
Then(
|
73
|
-
|
78
|
+
Then('the request fail(s/ed) because (the )it/resource is/was not allowed') do
|
79
|
+
steps %(Then the response status should be "405")
|
74
80
|
end
|
75
81
|
|
76
|
-
Then(
|
77
|
-
|
82
|
+
Then('the request fail(s/ed) because (the ){item_type} is/was not allowed') do |_item_type|
|
83
|
+
steps %(Then the response status should be "405")
|
78
84
|
end
|
79
85
|
|
80
|
-
Then(
|
81
|
-
|
86
|
+
Then('the request fail(s/ed) because there is/was/has a conflict') do
|
87
|
+
steps %(Then the response status should be "409")
|
82
88
|
end
|
83
89
|
|
84
|
-
Then(
|
85
|
-
|
90
|
+
Then('the request fail(s/ed) because there is/was/has a conflict with {item_type}') do |_item_type|
|
91
|
+
steps %(Then the response status should be "409")
|
86
92
|
end
|
87
93
|
|
88
|
-
Then(
|
89
|
-
|
94
|
+
Then('the request fail(s/ed) because (the )it/resource is/was/has gone') do
|
95
|
+
steps %(Then the response status should be "410")
|
90
96
|
end
|
91
97
|
|
92
|
-
Then(
|
93
|
-
|
98
|
+
Then('the request fail(s/ed) because (the ){item_type} is/was/has gone') do |_item_type|
|
99
|
+
steps %(Then the response status should be "410")
|
94
100
|
end
|
95
101
|
|
96
|
-
Then(
|
97
|
-
|
102
|
+
Then('the request fail(s/ed) because (the )it/resource is/was not implemented') do
|
103
|
+
steps %(Then the response status should be "501")
|
98
104
|
end
|
99
105
|
|
100
|
-
Then(
|
101
|
-
|
106
|
+
Then('the request fail(s/ed) because (the ){item_type} is/was not implemented') do |_item_type|
|
107
|
+
steps %(Then the response status should be "501")
|
102
108
|
end
|
@@ -1,188 +1,197 @@
|
|
1
1
|
require 'active_support/inflector'
|
2
2
|
|
3
|
-
HAVE_ALTERNATION =
|
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)*|`[^`]*`'
|
3
|
+
HAVE_ALTERNATION = 'has/have/having/contain/contains/containing/with'.freeze
|
4
|
+
RESOURCE_NAME_SYNONYM = '\w+\b(?:\s+\w+\b)*?|`[^`]*`'.freeze
|
5
|
+
FIELD_NAME_SYNONYM = '\w+\b(?:(?:\s+:)?\s+\w+\b)*?|`[^`]*`'.freeze
|
6
|
+
MAXIMAL_FIELD_NAME_SYNONYM = '\w+\b(?:(?:\s+:)?\s+\w+\b)*|`[^`]*`'.freeze
|
7
7
|
|
8
8
|
ParameterType(
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
name: 'resource_name',
|
10
|
+
regexp: /#{RESOURCE_NAME_SYNONYM}/,
|
11
|
+
transformer: ->(s) { get_resource(s) },
|
12
|
+
use_for_snippets: false
|
13
13
|
)
|
14
14
|
|
15
15
|
ParameterType(
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
name: 'field_name',
|
17
|
+
regexp: /#{FIELD_NAME_SYNONYM}/,
|
18
|
+
transformer: ->(s) { ResponseField.new(s) },
|
19
|
+
use_for_snippets: false
|
20
20
|
)
|
21
21
|
|
22
|
+
# Add Boolean module to handle types
|
22
23
|
module Boolean; end
|
24
|
+
# True
|
23
25
|
class TrueClass; include Boolean; end
|
26
|
+
# False
|
24
27
|
class FalseClass; include Boolean; end
|
25
28
|
|
29
|
+
# Add Enum module to handle types
|
26
30
|
module Enum; end
|
31
|
+
# Enum is a type of string
|
27
32
|
class String; include Enum; end
|
28
33
|
|
34
|
+
# Handle parsing a field from a response
|
29
35
|
class ResponseField
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
36
|
+
def initialize(names)
|
37
|
+
@fields = split_fields(names)
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_json_path
|
41
|
+
"#{root_data_key}#{@fields.join('.')}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_value(response, type)
|
45
|
+
response.get_as_type to_json_path, parse_type(type)
|
46
|
+
end
|
47
|
+
|
48
|
+
def validate_value(response, value, regex)
|
49
|
+
raise "Expected #{json_path} value '#{value}' to match regex: #{regex}\n#{response.to_json_s}" \
|
50
|
+
if (regex =~ value).nil?
|
51
|
+
end
|
45
52
|
end
|
46
53
|
|
47
54
|
def parse_type(type)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
55
|
+
replacements = {
|
56
|
+
/^numeric$/i => 'numeric',
|
57
|
+
/^int$/i => 'numeric',
|
58
|
+
/^long$/i => 'numeric',
|
59
|
+
/^number$/i => 'numeric',
|
60
|
+
/^decimal$/i => 'numeric',
|
61
|
+
/^double$/i => 'numeric',
|
62
|
+
/^bool$/i => 'boolean',
|
63
|
+
/^null$/i => 'nil_class',
|
64
|
+
/^nil$/i => 'nil_class',
|
65
|
+
/^string$/i => 'string',
|
66
|
+
/^text$/i => 'string'
|
67
|
+
}
|
68
|
+
type.tr(' ', '_')
|
69
|
+
replacements.each { |k, v| type.gsub!(k, v) }
|
70
|
+
type
|
64
71
|
end
|
65
72
|
|
66
73
|
def string_to_type(value, type)
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
74
|
+
replacements = {
|
75
|
+
/^numeric$/i => 'integer',
|
76
|
+
/^int$/i => 'integer',
|
77
|
+
/^long$/i => 'integer',
|
78
|
+
/^number$/i => 'integer',
|
79
|
+
/^decimal$/i => 'float',
|
80
|
+
/^double$/i => 'float',
|
81
|
+
/^bool$/i => 'boolean',
|
82
|
+
/^null$/i => 'nil_class',
|
83
|
+
/^nil$/i => 'nil_class',
|
84
|
+
/^string$/i => 'string',
|
85
|
+
/^text$/i => 'string'
|
86
|
+
}
|
87
|
+
type.tr(' ', '_')
|
88
|
+
replacements.each { |k, v| type.gsub!(k, v) }
|
89
|
+
type = type.camelize.constantize
|
90
|
+
# cannot use 'case type' which checks for instances of a type rather than type equality
|
91
|
+
if type == Boolean then !(value =~ /true|yes/i).nil?
|
92
|
+
elsif type == Enum then value.upcase.tr(' ', '_')
|
93
|
+
elsif type == Float then value.to_f
|
94
|
+
elsif type == Integer then value.to_i
|
95
|
+
elsif type == NilClass then nil
|
96
|
+
else value
|
97
|
+
end
|
91
98
|
end
|
92
99
|
|
93
100
|
def get_resource(name)
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
+
if name[0] == '`' && name[-1] == '`'
|
102
|
+
name = name[1..-2]
|
103
|
+
else
|
104
|
+
name = name.parameterize
|
105
|
+
name = ENV.key?('resource_single') && ENV['resource_single'] == 'true' ? name.singularize : name.pluralize
|
106
|
+
end
|
107
|
+
name
|
101
108
|
end
|
102
109
|
|
103
|
-
def
|
104
|
-
|
110
|
+
def root_data_key
|
111
|
+
ENV.key?('data_key') && !ENV['data_key'].empty? ? "$.#{ENV['data_key']}." : '$.'
|
105
112
|
end
|
106
113
|
|
107
|
-
def
|
108
|
-
|
114
|
+
def json_path(names)
|
115
|
+
"#{root_data_key}#{split_fields(names).join('.')}"
|
109
116
|
end
|
110
117
|
|
111
|
-
def
|
112
|
-
|
118
|
+
def split_fields(names)
|
119
|
+
names.split(':').map { |n| parse_field(n.strip) }
|
113
120
|
end
|
114
121
|
|
115
|
-
def
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
122
|
+
def parse_field(name)
|
123
|
+
if name[0] == '`' && name[-1] == '`'
|
124
|
+
name = name[1..-2]
|
125
|
+
elsif name[0] != '[' || name[-1] != ']'
|
126
|
+
separator = ENV.key?('field_separator') ? ENV['field_separator'] : '_'
|
127
|
+
name = name.parameterize(separator: separator)
|
128
|
+
name = name.camelize(:lower) if ENV.key?('field_camel') && ENV['field_camel'] == 'true'
|
129
|
+
end
|
130
|
+
name
|
124
131
|
end
|
125
132
|
|
126
|
-
def
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
133
|
+
def parse_list_field(name)
|
134
|
+
if name[0] == '`' && name[-1] == '`'
|
135
|
+
name = name[1..-2]
|
136
|
+
elsif name[0] != '[' || name[-1] != ']'
|
137
|
+
separator = ENV.key?('field_separator') ? ENV['field_separator'] : '_'
|
138
|
+
name = name.parameterize(separator: separator)
|
139
|
+
name = name.pluralize
|
140
|
+
name = name.camelize(:lower) if ENV.key?('field_camel') && ENV['field_camel'] == 'true'
|
141
|
+
end
|
142
|
+
name
|
136
143
|
end
|
137
144
|
|
138
|
-
def
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
145
|
+
def parse_attributes(hashes)
|
146
|
+
hashes.each_with_object({}) do |row, hash|
|
147
|
+
name = row['attribute']
|
148
|
+
value = row['value']
|
149
|
+
type = row['type']
|
150
|
+
value = resolve_functions(value)
|
151
|
+
value = resolve(value)
|
152
|
+
value.gsub!(/\\n/, "\n")
|
153
|
+
names = split_fields(name)
|
154
|
+
new_hash = names.reverse.inject(string_to_type(value, type)) { |a, n| add_to_hash(a, n) }
|
155
|
+
hash.deep_merge!(new_hash) { |_, old, new| new.is_a?(Array) ? merge_arrays(old, new) : new }
|
156
|
+
end
|
148
157
|
end
|
149
158
|
|
150
159
|
def resolve_functions(value)
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
end
|
160
|
+
value.gsub!(/\[([a-zA-Z0-9_]+)\]/) do |s|
|
161
|
+
s.gsub!(/[\[\]]/, '')
|
162
|
+
case s.downcase
|
163
|
+
when 'datetime'
|
164
|
+
Time.now.strftime('%Y%m%d%H%M%S')
|
165
|
+
else
|
166
|
+
raise 'Unrecognised function ' + s + '?'
|
159
167
|
end
|
160
|
-
|
168
|
+
end
|
169
|
+
value
|
161
170
|
end
|
162
171
|
|
163
|
-
def add_to_hash(
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
172
|
+
def add_to_hash(hash, node)
|
173
|
+
result = nil
|
174
|
+
if node[0] == '[' && node[-1] == ']'
|
175
|
+
array = Array.new(node[1..-2].to_i + 1)
|
176
|
+
array[node[1..-2].to_i] = hash
|
177
|
+
result = array
|
178
|
+
end
|
179
|
+
!result.nil? ? result : { node => hash }
|
171
180
|
end
|
172
181
|
|
173
|
-
def merge_arrays(
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
182
|
+
def merge_arrays(first, second)
|
183
|
+
new_length = [first.length, second.length].max
|
184
|
+
new_array = Array.new(new_length)
|
185
|
+
new_length.times do |n|
|
186
|
+
new_array[n] = if second[n].nil?
|
187
|
+
first[n]
|
188
|
+
else
|
189
|
+
new_array[n] = if first[n].nil?
|
190
|
+
second[n]
|
191
|
+
else
|
192
|
+
first[n].merge(second[n])
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
new_array
|
188
197
|
end
|