hops-airborne 0.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +38 -0
- data/.rspec +1 -0
- data/.travis.yml +12 -0
- data/Gemfile +10 -0
- data/LICENSE +9 -0
- data/README.md +429 -0
- data/airborne.gemspec +19 -0
- data/lib/airborne/base.rb +82 -0
- data/lib/airborne/optional_hash_type_expectations.rb +23 -0
- data/lib/airborne/path_matcher.rb +101 -0
- data/lib/airborne/rack_test_requester.rb +18 -0
- data/lib/airborne/request_expectations.rb +285 -0
- data/lib/airborne/rest_client_requester.rb +33 -0
- data/lib/airborne.rb +29 -0
- data/spec/airborne/base_spec.rb +55 -0
- data/spec/airborne/client_requester_spec.rb +42 -0
- data/spec/airborne/delete_spec.rb +9 -0
- data/spec/airborne/expectations/expect_header_contains_spec.rb +21 -0
- data/spec/airborne/expectations/expect_header_spec.rb +21 -0
- data/spec/airborne/expectations/expect_json_keys_path_spec.rb +15 -0
- data/spec/airborne/expectations/expect_json_keys_spec.rb +21 -0
- data/spec/airborne/expectations/expect_json_lambda_spec.rb +9 -0
- data/spec/airborne/expectations/expect_json_options_spec.rb +65 -0
- data/spec/airborne/expectations/expect_json_path_spec.rb +108 -0
- data/spec/airborne/expectations/expect_json_regex_spec.rb +33 -0
- data/spec/airborne/expectations/expect_json_sizes_spec.rb +33 -0
- data/spec/airborne/expectations/expect_json_spec.rb +27 -0
- data/spec/airborne/expectations/expect_json_types_date_spec.rb +52 -0
- data/spec/airborne/expectations/expect_json_types_lambda_spec.rb +9 -0
- data/spec/airborne/expectations/expect_json_types_optional_spec.rb +15 -0
- data/spec/airborne/expectations/expect_json_types_options_spec.rb +59 -0
- data/spec/airborne/expectations/expect_json_types_path_spec.rb +65 -0
- data/spec/airborne/expectations/expect_json_types_spec.rb +85 -0
- data/spec/airborne/expectations/expect_status_spec.rb +23 -0
- data/spec/airborne/head_spec.rb +10 -0
- data/spec/airborne/options_spec.rb +10 -0
- data/spec/airborne/patch_spec.rb +9 -0
- data/spec/airborne/path_spec.rb +34 -0
- data/spec/airborne/post_spec.rb +17 -0
- data/spec/airborne/put_spec.rb +9 -0
- data/spec/airborne/rack/rack_sinatra_spec.rb +43 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/stub_helper.rb +45 -0
- data/spec/test_responses/array_of_types.json +9 -0
- data/spec/test_responses/array_of_values.json +5 -0
- data/spec/test_responses/array_response.json +5 -0
- data/spec/test_responses/array_with_index.json +6 -0
- data/spec/test_responses/array_with_nested.json +16 -0
- data/spec/test_responses/array_with_nested_bad_data.json +15 -0
- data/spec/test_responses/array_with_partial_nested.json +12 -0
- data/spec/test_responses/date_is_null_response.json +3 -0
- data/spec/test_responses/date_response.json +3 -0
- data/spec/test_responses/hash_property.json +3 -0
- data/spec/test_responses/invalid_get.json +1 -0
- data/spec/test_responses/invalid_json.json +1 -0
- data/spec/test_responses/numeric_property.json +6 -0
- data/spec/test_responses/simple_delete.json +4 -0
- data/spec/test_responses/simple_get.json +5 -0
- data/spec/test_responses/simple_json.json +5 -0
- data/spec/test_responses/simple_nested_path.json +12 -0
- data/spec/test_responses/simple_patch.json +4 -0
- data/spec/test_responses/simple_path_get.json +8 -0
- data/spec/test_responses/simple_post.json +4 -0
- data/spec/test_responses/simple_put.json +4 -0
- metadata +206 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
module Airborne
|
|
2
|
+
class PathError < StandardError; end
|
|
3
|
+
|
|
4
|
+
module PathMatcher
|
|
5
|
+
def get_by_path(path, json, &block)
|
|
6
|
+
fail PathError, "Invalid Path, contains '..'" if /\.\./ =~ path
|
|
7
|
+
type = false
|
|
8
|
+
parts = path.split('.')
|
|
9
|
+
parts.each_with_index do |part, index|
|
|
10
|
+
if part == '*' || part == '?'
|
|
11
|
+
ensure_array(path, json)
|
|
12
|
+
type = part
|
|
13
|
+
if index < parts.length.pred
|
|
14
|
+
walk_with_path(type, index, path, parts, json, &block) && return
|
|
15
|
+
end
|
|
16
|
+
next
|
|
17
|
+
end
|
|
18
|
+
begin
|
|
19
|
+
json = process_json(part, json)
|
|
20
|
+
rescue
|
|
21
|
+
raise PathError, "Expected #{json.class}\nto be an object with property #{part}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
if type == '*'
|
|
25
|
+
expect_all(json, &block)
|
|
26
|
+
elsif type == '?'
|
|
27
|
+
expect_one(path, json, &block)
|
|
28
|
+
else
|
|
29
|
+
yield json
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def walk_with_path(type, index, path, parts, json, &block)
|
|
36
|
+
last_error = nil
|
|
37
|
+
item_count = json.length
|
|
38
|
+
error_count = 0
|
|
39
|
+
json.each do |element|
|
|
40
|
+
begin
|
|
41
|
+
sub_path = parts[(index.next)...(parts.length)].join('.')
|
|
42
|
+
get_by_path(sub_path, element, &block)
|
|
43
|
+
rescue Exception => e
|
|
44
|
+
last_error = e
|
|
45
|
+
error_count += 1
|
|
46
|
+
end
|
|
47
|
+
ensure_match_all(last_error) if type == '*'
|
|
48
|
+
ensure_match_one(path, item_count, error_count) if type == '?'
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def process_json(part, json)
|
|
53
|
+
if index?(part) && json.is_a?(Array)
|
|
54
|
+
part = part.to_i
|
|
55
|
+
json = json[part]
|
|
56
|
+
else
|
|
57
|
+
json = json[part.to_sym]
|
|
58
|
+
end
|
|
59
|
+
json
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def index?(part)
|
|
63
|
+
part =~ /^\d+$/
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def expect_one(path, json, &block)
|
|
67
|
+
item_count = json.length
|
|
68
|
+
error_count = 0
|
|
69
|
+
json.each do |part|
|
|
70
|
+
begin
|
|
71
|
+
yield part
|
|
72
|
+
rescue Exception
|
|
73
|
+
error_count += 1
|
|
74
|
+
ensure_match_one(path, item_count, error_count)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def expect_all(json, &block)
|
|
80
|
+
last_error = nil
|
|
81
|
+
begin
|
|
82
|
+
json.each { |part| yield part }
|
|
83
|
+
rescue Exception => e
|
|
84
|
+
last_error = e
|
|
85
|
+
end
|
|
86
|
+
ensure_match_all(last_error)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def ensure_match_one(path, item_count, error_count)
|
|
90
|
+
fail RSpec::Expectations::ExpectationNotMetError, "Expected one object in path #{path} to match provided JSON values" if item_count == error_count
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def ensure_match_all(error)
|
|
94
|
+
fail error unless error.nil?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def ensure_array(path, json)
|
|
98
|
+
fail RSpec::Expectations::ExpectationNotMetError, "Expected #{path} to be array got #{json.class} from JSON response" unless json.class == Array
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'rack/test'
|
|
2
|
+
|
|
3
|
+
module Airborne
|
|
4
|
+
module RackTestRequester
|
|
5
|
+
def make_request(method, url, options = {})
|
|
6
|
+
headers = options[:headers] || {}
|
|
7
|
+
base_headers = Airborne.configuration.headers || {}
|
|
8
|
+
headers = base_headers.merge(headers)
|
|
9
|
+
browser = Rack::Test::Session.new(Rack::MockSession.new(Airborne.configuration.rack_app))
|
|
10
|
+
headers.each { |name, value| browser.header(name, value) }
|
|
11
|
+
browser.send(method, url, options[:body] || {}, headers)
|
|
12
|
+
Rack::MockResponse.class_eval do
|
|
13
|
+
alias_method :code, :status
|
|
14
|
+
end
|
|
15
|
+
browser.last_response
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
require 'rspec'
|
|
2
|
+
require 'date'
|
|
3
|
+
require 'rack/utils'
|
|
4
|
+
|
|
5
|
+
module Airborne
|
|
6
|
+
class ExpectationError < StandardError; end
|
|
7
|
+
module RequestExpectations
|
|
8
|
+
include RSpec
|
|
9
|
+
include PathMatcher
|
|
10
|
+
|
|
11
|
+
def expect_json_types(*args)
|
|
12
|
+
call_with_path(args) do |param, body|
|
|
13
|
+
expect_json_types_impl(param, body)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def expect_json(*args)
|
|
18
|
+
call_with_path(args) do |param, body|
|
|
19
|
+
expect_json_impl(param, body)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def expect_json_keys(*args)
|
|
24
|
+
call_with_path(args) do |param, body|
|
|
25
|
+
expect(body.keys).to include(*param)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def expect_json_sizes(*args)
|
|
30
|
+
args.push(convert_expectations_for_json_sizes(args.pop))
|
|
31
|
+
|
|
32
|
+
expect_json_types(*args)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def expect_status(code)
|
|
36
|
+
expect(response.code).to eq(resolve_status(code, response.code))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def expect_header(key, content)
|
|
40
|
+
expect_header_impl(key, content)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def expect_header_contains(key, content)
|
|
44
|
+
expect_header_impl(key, content, true)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def optional(hash)
|
|
48
|
+
OptionalHashTypeExpectations.new(hash)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def regex(reg)
|
|
52
|
+
Regexp.new(reg)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def date
|
|
56
|
+
->(value) { yield DateTime.parse(value) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def expect_header_impl(key, content, contains = nil)
|
|
62
|
+
header = headers[key]
|
|
63
|
+
if header
|
|
64
|
+
if contains
|
|
65
|
+
expect(header.downcase).to include(content.downcase)
|
|
66
|
+
else
|
|
67
|
+
expect(header.downcase).to eq(content.downcase)
|
|
68
|
+
end
|
|
69
|
+
else
|
|
70
|
+
fail RSpec::Expectations::ExpectationNotMetError, "Header #{key} not present in the HTTP response"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def expect_json_impl(expected, actual)
|
|
75
|
+
return if nil_optional_hash?(expected, actual)
|
|
76
|
+
|
|
77
|
+
actual = actual.to_s if expected.is_a?(Regexp)
|
|
78
|
+
|
|
79
|
+
return expect(actual).to match(expected) if property?(expected)
|
|
80
|
+
|
|
81
|
+
keys = []
|
|
82
|
+
|
|
83
|
+
keys << expected.keys if match_expected?
|
|
84
|
+
keys << actual.keys if match_actual?
|
|
85
|
+
keys = expected.keys & actual.keys if match_none?
|
|
86
|
+
|
|
87
|
+
keys.flatten.uniq.each do |prop|
|
|
88
|
+
expected_value = extract_expected_value(expected, prop)
|
|
89
|
+
actual_value = extract_actual(actual, prop)
|
|
90
|
+
|
|
91
|
+
next expect_json_impl(expected_value, actual_value) if hash?(expected_value) && hash?(actual_value)
|
|
92
|
+
next expected_value.call(actual_value) if expected_value.is_a?(Proc)
|
|
93
|
+
next expect(actual_value.to_s).to match(expected_value) if expected_value.is_a?(Regexp)
|
|
94
|
+
|
|
95
|
+
expect(actual_value).to eq(expected_value)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def expect_json_types_impl(expected, actual)
|
|
100
|
+
return if nil_optional_hash?(expected, actual)
|
|
101
|
+
|
|
102
|
+
@mapper ||= get_mapper
|
|
103
|
+
|
|
104
|
+
actual = convert_to_date(actual) if ((expected == :date) || (expected == :date_or_null))
|
|
105
|
+
|
|
106
|
+
return expect_type(expected, actual) if expected.is_a?(Symbol)
|
|
107
|
+
return expected.call(actual) if expected.is_a?(Proc)
|
|
108
|
+
|
|
109
|
+
keys = []
|
|
110
|
+
|
|
111
|
+
keys << expected.keys if match_expected?
|
|
112
|
+
keys << actual.keys if match_actual?
|
|
113
|
+
keys = expected.keys & actual.keys if match_none?
|
|
114
|
+
|
|
115
|
+
keys.flatten.uniq.each do |prop|
|
|
116
|
+
type = extract_expected_type(expected, prop)
|
|
117
|
+
value = extract_actual(actual, prop)
|
|
118
|
+
value = convert_to_date(value) if ((type == :date) || (type == :date_or_null))
|
|
119
|
+
|
|
120
|
+
next expect_json_types_impl(type, value) if hash?(type)
|
|
121
|
+
next type.call(value) if type.is_a?(Proc)
|
|
122
|
+
|
|
123
|
+
type_string = type.to_s
|
|
124
|
+
|
|
125
|
+
if type_string.include?('array_of') && !(type_string.include?('or_null') && value.nil?)
|
|
126
|
+
check_array_types(value, prop, type)
|
|
127
|
+
else
|
|
128
|
+
expect_type(type, value, prop)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def call_with_path(args)
|
|
134
|
+
if args.length == 2
|
|
135
|
+
get_by_path(args[0], json_body) do |json_chunk|
|
|
136
|
+
yield(args[1], json_chunk)
|
|
137
|
+
end
|
|
138
|
+
else
|
|
139
|
+
yield(args[0], json_body)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def extract_expected_value(expected, prop)
|
|
144
|
+
begin
|
|
145
|
+
raise unless expected.keys.include?(prop)
|
|
146
|
+
expected[prop]
|
|
147
|
+
rescue
|
|
148
|
+
raise ExpectationError, "Expectation is expected to contain property: #{prop}"
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def extract_expected_type(expected, prop)
|
|
153
|
+
begin
|
|
154
|
+
type = expected[prop]
|
|
155
|
+
type.nil? ? raise : type
|
|
156
|
+
rescue
|
|
157
|
+
raise ExpectationError, "Expectation is expected to contain property: #{prop}"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def extract_actual(actual, prop)
|
|
162
|
+
begin
|
|
163
|
+
value = actual[prop]
|
|
164
|
+
rescue
|
|
165
|
+
raise ExpectationError, "Expected #{actual.class} #{actual}\nto be an object with property #{prop}"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def expect_type(expected_type, value, prop_name = nil)
|
|
170
|
+
fail ExpectationError, "Expected type #{expected_type}\nis an invalid type" if @mapper[expected_type].nil?
|
|
171
|
+
|
|
172
|
+
insert = prop_name.nil? ? '' : "#{prop_name} to be of type"
|
|
173
|
+
message = "Expected #{insert} #{expected_type}\n got #{value.class} instead"
|
|
174
|
+
|
|
175
|
+
expect(@mapper[expected_type].any?{|type| value.is_a?(type)}).to eq(true), message
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def convert_to_date(value)
|
|
179
|
+
begin
|
|
180
|
+
DateTime.parse(value)
|
|
181
|
+
rescue
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def check_array_types(value, prop_name, expected_type)
|
|
186
|
+
expect_array(value, prop_name, expected_type)
|
|
187
|
+
value.each do |val|
|
|
188
|
+
expect_type(expected_type, val, prop_name)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def nil_optional_hash?(expected, hash)
|
|
193
|
+
expected.is_a?(Airborne::OptionalHashTypeExpectations) && hash.nil?
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def hash?(hash)
|
|
197
|
+
hash.is_a?(Hash) || hash.is_a?(Airborne::OptionalHashTypeExpectations)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def expect_array(value, prop_name, expected_type)
|
|
201
|
+
expect(value.class).to eq(Array), "Expected #{prop_name}\n to be of type #{expected_type}\n got #{value.class} instead"
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def convert_expectations_for_json_sizes(old_expectations)
|
|
205
|
+
unless old_expectations.is_a?(Hash)
|
|
206
|
+
return convert_expectation_for_json_sizes(old_expectations)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
old_expectations.each_with_object({}) do |(prop_name, expected_size), memo|
|
|
210
|
+
new_value = if expected_size.is_a?(Hash)
|
|
211
|
+
convert_expectations_for_json_sizes(expected_size)
|
|
212
|
+
else
|
|
213
|
+
convert_expectation_for_json_sizes(expected_size)
|
|
214
|
+
end
|
|
215
|
+
memo[prop_name] = new_value
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def convert_expectation_for_json_sizes(expected_size)
|
|
220
|
+
->(data) { expect(data.size).to eq(expected_size) }
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def ensure_hash_contains_prop(prop_name, hash)
|
|
224
|
+
begin
|
|
225
|
+
yield
|
|
226
|
+
rescue
|
|
227
|
+
raise ExpectationError, "Expected #{hash.class} #{hash}\nto be an object with property #{prop_name}"
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def property?(expectation)
|
|
232
|
+
[String, Regexp, Float, Integer, TrueClass, FalseClass, NilClass, Array].any?{|type| expectation.is_a?(type)}
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def get_mapper
|
|
236
|
+
base_mapper = {
|
|
237
|
+
integer: [Integer],
|
|
238
|
+
array_of_integers: [Integer],
|
|
239
|
+
int: [Integer],
|
|
240
|
+
array_of_ints: [Integer],
|
|
241
|
+
float: [Float, Integer],
|
|
242
|
+
array_of_floats: [Float, Integer],
|
|
243
|
+
string: [String],
|
|
244
|
+
array_of_strings: [String],
|
|
245
|
+
boolean: [TrueClass, FalseClass],
|
|
246
|
+
array_of_booleans: [TrueClass, FalseClass],
|
|
247
|
+
bool: [TrueClass, FalseClass],
|
|
248
|
+
array_of_bools: [TrueClass, FalseClass],
|
|
249
|
+
object: [Hash],
|
|
250
|
+
array_of_objects: [Hash],
|
|
251
|
+
array: [Array],
|
|
252
|
+
array_of_arrays: [Array],
|
|
253
|
+
date: [DateTime],
|
|
254
|
+
null: [NilClass]
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
mapper = base_mapper.clone
|
|
258
|
+
base_mapper.each do |key, value|
|
|
259
|
+
mapper[(key.to_s + '_or_null').to_sym] = value + [NilClass]
|
|
260
|
+
end
|
|
261
|
+
mapper
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def resolve_status(candidate, authority)
|
|
265
|
+
candidate = Rack::Utils::SYMBOL_TO_STATUS_CODE[candidate] if candidate.is_a?(Symbol)
|
|
266
|
+
case authority
|
|
267
|
+
when String then candidate.to_s
|
|
268
|
+
when Integer then candidate.to_i
|
|
269
|
+
else candidate
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def match_none?
|
|
274
|
+
!match_actual? && !match_expected?
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def match_actual?
|
|
278
|
+
Airborne.configuration.match_actual?
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def match_expected?
|
|
282
|
+
Airborne.configuration.match_expected?
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'rest_client'
|
|
2
|
+
|
|
3
|
+
module Airborne
|
|
4
|
+
module RestClientRequester
|
|
5
|
+
def make_request(method, url, options = {})
|
|
6
|
+
headers = base_headers.merge(options[:headers] || {})
|
|
7
|
+
res = if method == :post || method == :patch || method == :put
|
|
8
|
+
begin
|
|
9
|
+
request_body = options[:body].nil? ? '' : options[:body]
|
|
10
|
+
request_body = request_body.to_json if options[:body].is_a?(Hash)
|
|
11
|
+
RestClient::Request.execute(:method => method, :url => get_url(url), :payload => request_body,
|
|
12
|
+
:headers => headers , :verify_ssl => Airborne.configuration.verify_ssl)
|
|
13
|
+
rescue RestClient::Exception => e
|
|
14
|
+
e.response
|
|
15
|
+
end
|
|
16
|
+
else
|
|
17
|
+
begin
|
|
18
|
+
RestClient::Request.execute(:method => method, :url => get_url(url), :headers => headers,
|
|
19
|
+
:verify_ssl => Airborne.configuration.verify_ssl)
|
|
20
|
+
rescue RestClient::Exception => e
|
|
21
|
+
e.response
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
res
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def base_headers
|
|
30
|
+
{ content_type: :json }.merge(Airborne.configuration.headers || {})
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/airborne.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'airborne/optional_hash_type_expectations'
|
|
2
|
+
require 'airborne/path_matcher'
|
|
3
|
+
require 'airborne/request_expectations'
|
|
4
|
+
require 'airborne/rest_client_requester'
|
|
5
|
+
require 'airborne/rack_test_requester'
|
|
6
|
+
require 'airborne/base'
|
|
7
|
+
require 'openssl'
|
|
8
|
+
|
|
9
|
+
RSpec.configure do |config|
|
|
10
|
+
config.add_setting :base_url
|
|
11
|
+
config.add_setting :verify_ssl, default: OpenSSL::SSL::VERIFY_NONE
|
|
12
|
+
config.add_setting :match_expected
|
|
13
|
+
config.add_setting :match_actual
|
|
14
|
+
config.add_setting :match_expected_default, default: true
|
|
15
|
+
config.add_setting :match_actual_default, default: false
|
|
16
|
+
config.add_setting :headers
|
|
17
|
+
config.add_setting :rack_app
|
|
18
|
+
config.add_setting :requester_type
|
|
19
|
+
config.add_setting :requester_module
|
|
20
|
+
config.before do |example|
|
|
21
|
+
config.match_expected = example.metadata[:match_expected].nil? ?
|
|
22
|
+
Airborne.configuration.match_expected_default? : example.metadata[:match_expected]
|
|
23
|
+
config.match_actual = example.metadata[:match_actual].nil? ?
|
|
24
|
+
Airborne.configuration.match_actual_default? : example.metadata[:match_actual]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Include last since it depends on the configuration already being added
|
|
28
|
+
config.include Airborne
|
|
29
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'base spec' do
|
|
4
|
+
it 'when request is made response should be set' do
|
|
5
|
+
mock_get('simple_get')
|
|
6
|
+
get '/simple_get'
|
|
7
|
+
expect(response).to_not be(nil)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'when request is made headers should be set' do
|
|
11
|
+
mock_get('simple_get')
|
|
12
|
+
get '/simple_get'
|
|
13
|
+
expect(headers).to_not be(nil)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'should throw an InvalidJsonError when accessing json_body on invalid json' do
|
|
17
|
+
mock_get('invalid_json')
|
|
18
|
+
get '/invalid_json'
|
|
19
|
+
expect(body).to eq('invalid1234')
|
|
20
|
+
expect { json_body }.to raise_error(InvalidJsonError)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'when request is made headers should be hash with indifferent access' do
|
|
24
|
+
mock_get('simple_get', 'Content-Type' => 'application/json')
|
|
25
|
+
get '/simple_get'
|
|
26
|
+
expect(headers).to be_kind_of(Hash)
|
|
27
|
+
expect(headers[:content_type]).to eq('application/json')
|
|
28
|
+
expect(headers['content_type']).to eq('application/json')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'when request is made body should be set' do
|
|
32
|
+
mock_get('simple_get')
|
|
33
|
+
get '/simple_get'
|
|
34
|
+
expect(body).to_not be(nil)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'when request is made json body should be symbolized hash' do
|
|
38
|
+
mock_get('simple_get')
|
|
39
|
+
get '/simple_get'
|
|
40
|
+
expect(json_body).to be_kind_of(Hash)
|
|
41
|
+
expect(json_body.first[0]).to be_kind_of(Symbol)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'should handle a 500 error on get' do
|
|
45
|
+
mock_get('simple_get', {}, [500, 'Internal Server Error'])
|
|
46
|
+
get '/simple_get'
|
|
47
|
+
expect(json_body).to_not be(nil)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'should handle a 500 error on post' do
|
|
51
|
+
mock_post('simple_post', {}, [500, 'Internal Server Error'])
|
|
52
|
+
post '/simple_post', {}
|
|
53
|
+
expect(json_body).to_not be(nil)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'openssl'
|
|
3
|
+
|
|
4
|
+
describe 'client requester' do
|
|
5
|
+
before do
|
|
6
|
+
allow(RestClient::Request).to receive(:execute)
|
|
7
|
+
RSpec::Mocks.space.proxy_for(self).remove_stub_if_present(:get)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
after do
|
|
11
|
+
allow(RestClient).to receive(:send).and_call_original
|
|
12
|
+
Airborne.configure { |config| config.headers = {} }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'should set :content_type to :json by default' do
|
|
16
|
+
get '/foo'
|
|
17
|
+
|
|
18
|
+
expect(RestClient::Request).to have_received(:execute)
|
|
19
|
+
.with(:method => :get, :url => 'http://www.example.com/foo',
|
|
20
|
+
:headers => { content_type: :json }, :verify_ssl => OpenSSL::SSL::VERIFY_NONE)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'should override headers with option[:headers]' do
|
|
24
|
+
get '/foo', { content_type: 'application/x-www-form-urlencoded' }
|
|
25
|
+
|
|
26
|
+
expect(RestClient::Request).to have_received(:execute)
|
|
27
|
+
.with(:method => :get, :url => 'http://www.example.com/foo',
|
|
28
|
+
:headers => { content_type: 'application/x-www-form-urlencoded' },
|
|
29
|
+
:verify_ssl => OpenSSL::SSL::VERIFY_NONE)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'should override headers with airborne config headers' do
|
|
33
|
+
Airborne.configure { |config| config.headers = { content_type: 'text/plain' } }
|
|
34
|
+
|
|
35
|
+
get '/foo'
|
|
36
|
+
|
|
37
|
+
expect(RestClient::Request).to have_received(:execute)
|
|
38
|
+
.with(:method => :get, :url => 'http://www.example.com/foo',
|
|
39
|
+
:headers => { content_type: 'text/plain' },
|
|
40
|
+
:verify_ssl => OpenSSL::SSL::VERIFY_NONE)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'expect header contains' do
|
|
4
|
+
it 'should ensure partial header match exists' do
|
|
5
|
+
mock_get('simple_get', 'Content-Type' => 'application/json')
|
|
6
|
+
get '/simple_get'
|
|
7
|
+
expect_header_contains(:content_type, 'json')
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'should ensure header is present' do
|
|
11
|
+
mock_get('simple_get', 'Content-Type' => 'application/json')
|
|
12
|
+
get '/simple_get'
|
|
13
|
+
expect { expect_header_contains(:foo, 'bar') }.to raise_error(ExpectationNotMetError)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'should ensure partial header is present' do
|
|
17
|
+
mock_get('simple_get', 'Content-Type' => 'application/json')
|
|
18
|
+
get '/simple_get'
|
|
19
|
+
expect { expect_header_contains(:content_type, 'bar') }.to raise_error(ExpectationNotMetError)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'expect header' do
|
|
4
|
+
it 'should find exact match for header content' do
|
|
5
|
+
mock_get('simple_get', 'Content-Type' => 'application/json')
|
|
6
|
+
get '/simple_get'
|
|
7
|
+
expect_header(:content_type, 'application/json')
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'should find exact match for header content' do
|
|
11
|
+
mock_get('simple_get', 'Content-Type' => 'json')
|
|
12
|
+
get '/simple_get'
|
|
13
|
+
expect { expect_header(:content_type, 'application/json') }.to raise_error(ExpectationNotMetError)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'should ensure correct headers are present' do
|
|
17
|
+
mock_get('simple_get', 'Content-Type' => 'application/json')
|
|
18
|
+
get '/simple_get'
|
|
19
|
+
expect { expect_header(:foo, 'bar') }.to raise_error(ExpectationNotMetError)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'expect_json_keys with path' do
|
|
4
|
+
it 'should ensure json keys with path' do
|
|
5
|
+
mock_get('simple_nested_path')
|
|
6
|
+
get '/simple_nested_path', {}
|
|
7
|
+
expect_json_keys('address', [:street, :city])
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'should fail when keys are missing with path' do
|
|
11
|
+
mock_get('simple_nested_path')
|
|
12
|
+
get '/simple_nested_path', {}
|
|
13
|
+
expect { expect_json_keys('address', [:bad]) }.to raise_error(ExpectationNotMetError)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'expect_json_keys' do
|
|
4
|
+
it 'should fail when json keys are missing' do
|
|
5
|
+
mock_get('simple_json')
|
|
6
|
+
get '/simple_json', {}
|
|
7
|
+
expect { expect_json_keys([:foo, :bar, :baz, :bax]) }.to raise_error(ExpectationNotMetError)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'should ensure correct json keys' do
|
|
11
|
+
mock_get('simple_json')
|
|
12
|
+
get '/simple_json', {}
|
|
13
|
+
expect_json_keys([:foo, :bar, :baz])
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'should ensure correct partial json keys' do
|
|
17
|
+
mock_get('simple_json')
|
|
18
|
+
get '/simple_json', {}
|
|
19
|
+
expect_json_keys([:foo, :bar])
|
|
20
|
+
end
|
|
21
|
+
end
|