airborne 0.0.21 → 0.0.22
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 +4 -4
- data/Gemfile +2 -2
- data/README.md +5 -15
- data/airborne.gemspec +1 -1
- data/lib/airborne.rb +6 -6
- data/lib/airborne/base.rb +74 -84
- data/lib/airborne/optional_hash_type_expectations.rb +12 -12
- data/lib/airborne/path_matcher.rb +43 -43
- data/lib/airborne/rack_test_requester.rb +7 -7
- data/lib/airborne/request_expectations.rb +118 -112
- data/lib/airborne/rest_client_requester.rb +21 -21
- data/spec/airborne/base_spec.rb +50 -50
- data/spec/airborne/delete_spec.rb +5 -5
- data/spec/airborne/expect_json_keys_spec.rb +27 -15
- data/spec/airborne/expect_json_spec.rb +54 -54
- data/spec/airborne/expect_json_types_spec.rb +107 -107
- data/spec/airborne/expect_status_spec.rb +10 -10
- data/spec/airborne/headers_spec.rb +25 -25
- data/spec/airborne/patch_spec.rb +5 -5
- data/spec/airborne/post_spec.rb +5 -5
- data/spec/airborne/put_spec.rb +5 -5
- data/spec/airborne/rack_sinatra_spec.rb +15 -15
- data/spec/spec_helper.rb +2 -2
- data/spec/stub_helper.rb +32 -32
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e009399096d82e3b30b22f4c8f6fc7255e11699f
|
4
|
+
data.tar.gz: 32d38638f1dac42754cc53750ad2a6599be88674
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 35ac6a72835f11646301b47fbd8ee40134a3f6063e173999866bb4726cadebbd7777194d1aed4a3663b9baa46fdb5c3fff1bc75483b9fa336ae9cc167d2e532f
|
7
|
+
data.tar.gz: 5a574890b43ad0d9e2953055912bbb86ca74c68b7f0b7ceb6e2346ef8e302a59f5b8890ec4c2cd8bc9423ca4af951b97666b0f854262eee6b3e5dfdcf9833e16
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -138,32 +138,22 @@ Under the covers, Airborne uses [rack-test](https://github.com/brynary/rack-test
|
|
138
138
|
|
139
139
|
##Rails Applications
|
140
140
|
|
141
|
-
If you're testing
|
141
|
+
If you're testing an API you've written in Rails, Airborne plays along with `rspec-rails`:
|
142
142
|
|
143
|
-
In your `spec/rails_helper.rb` file, change RSpec configure to Airborne configure, and set the `rack_app` configuration setting to your Rails Application:
|
144
|
-
|
145
|
-
```ruby
|
146
|
-
Airborne.configure do |config|
|
147
|
-
config.rack_app = Rails.application
|
148
|
-
end
|
149
|
-
```
|
150
|
-
|
151
|
-
Then, in your actual tests, since `rspec-rails` already has `get`, `post`, `put`... functions, you need to use `airborne_get`, `airborne_post` instead:
|
152
143
|
|
153
144
|
```ruby
|
154
145
|
require 'rails_helper'
|
155
146
|
|
156
|
-
RSpec.describe
|
157
|
-
describe "GET
|
147
|
+
RSpec.describe HomeController, :type => :controller do
|
148
|
+
describe "GET index" do
|
158
149
|
it "returns correct types" do
|
159
|
-
|
150
|
+
get :index, :format => 'json' #if your route responds to both html and json
|
160
151
|
expect_json_types({foo: :string})
|
161
152
|
end
|
162
153
|
end
|
163
154
|
end
|
164
155
|
```
|
165
|
-
|
166
|
-
|
156
|
+
|
167
157
|
##API
|
168
158
|
|
169
159
|
* `expect_json_types` - Tests the types of the JSON property values returned
|
data/airborne.gemspec
CHANGED
data/lib/airborne.rb
CHANGED
@@ -7,10 +7,10 @@ require 'airborne/base'
|
|
7
7
|
|
8
8
|
|
9
9
|
RSpec.configure do |config|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
10
|
+
config.include Airborne
|
11
|
+
config.add_setting :base_url
|
12
|
+
config.add_setting :headers
|
13
|
+
config.add_setting :rack_app
|
14
|
+
config.add_setting :requester_type
|
15
|
+
config.add_setting :requester_module
|
16
16
|
end
|
data/lib/airborne/base.rb
CHANGED
@@ -2,88 +2,78 @@ require 'json'
|
|
2
2
|
require 'active_support/hash_with_indifferent_access'
|
3
3
|
|
4
4
|
module Airborne
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
def set_response(res)
|
81
|
-
@response = res
|
82
|
-
@body = res.body
|
83
|
-
@headers = HashWithIndifferentAccess.new(res.headers.deep_symbolize_keys) unless res.headers.nil?
|
84
|
-
begin
|
85
|
-
@json_body = JSON.parse(res.body, symbolize_names: true) unless res.body.empty?
|
86
|
-
rescue
|
87
|
-
end
|
88
|
-
end
|
5
|
+
include RequestExpectations
|
6
|
+
|
7
|
+
def self.configure
|
8
|
+
RSpec.configure do |config|
|
9
|
+
yield config
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
if(!Airborne.configuration.requester_module.nil?)
|
15
|
+
base.send(:include, Airborne.configuration.requester_module)
|
16
|
+
elsif(!Airborne.configuration.rack_app.nil?)
|
17
|
+
base.send(:include, RackTestRequester)
|
18
|
+
else
|
19
|
+
base.send(:include, RestClientRequester)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.configuration
|
24
|
+
RSpec.configuration
|
25
|
+
end
|
26
|
+
|
27
|
+
def get(url, headers = nil)
|
28
|
+
set_response(make_request(:get, url, {headers: headers}))
|
29
|
+
end
|
30
|
+
|
31
|
+
def post(url, post_body = nil, headers = nil)
|
32
|
+
set_response(make_request(:post, url, {body: post_body, headers: headers}))
|
33
|
+
end
|
34
|
+
|
35
|
+
def patch(url, patch_body = nil, headers = nil )
|
36
|
+
set_response(make_request(:patch, url, {body: patch_body, headers: headers}))
|
37
|
+
end
|
38
|
+
|
39
|
+
def put(url, put_body = nil, headers = nil )
|
40
|
+
set_response(make_request(:put, url, {body: put_body, headers: headers}))
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete(url, headers = nil)
|
44
|
+
set_response(make_request(:delete, url, {headers: headers}))
|
45
|
+
end
|
46
|
+
|
47
|
+
def response
|
48
|
+
@response
|
49
|
+
end
|
50
|
+
|
51
|
+
def headers
|
52
|
+
@headers
|
53
|
+
end
|
54
|
+
|
55
|
+
def body
|
56
|
+
@body
|
57
|
+
end
|
58
|
+
|
59
|
+
def json_body
|
60
|
+
@json_body
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def get_url(url)
|
66
|
+
base = Airborne.configuration.base_url || ""
|
67
|
+
base + url
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_response(res)
|
71
|
+
@response = res
|
72
|
+
@body = res.body
|
73
|
+
@headers = HashWithIndifferentAccess.new(res.headers.deep_symbolize_keys) unless res.headers.nil?
|
74
|
+
begin
|
75
|
+
@json_body = JSON.parse(res.body, symbolize_names: true) unless res.body.empty?
|
76
|
+
rescue
|
77
|
+
end
|
78
|
+
end
|
89
79
|
end
|
@@ -1,15 +1,15 @@
|
|
1
1
|
module Airborne
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
class OptionalHashTypeExpectations
|
3
|
+
include Enumerable
|
4
|
+
attr_accessor :hash
|
5
|
+
def initialize(hash)
|
6
|
+
@hash = hash
|
7
|
+
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
def each
|
10
|
+
@hash.each do|k,v|
|
11
|
+
yield(k,v)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
15
|
end
|
@@ -1,45 +1,45 @@
|
|
1
1
|
module Airborne
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
2
|
+
module PathMatcher
|
3
|
+
def get_by_path(path, json, &block)
|
4
|
+
type = false
|
5
|
+
parts = path.split('.')
|
6
|
+
parts.each_with_index do |part, index|
|
7
|
+
if part == '*' || part == '?'
|
8
|
+
type = part
|
9
|
+
raise "Expected #{path} to be array got #{json.class} from JSON response" unless json.class == Array
|
10
|
+
if index < parts.length.pred
|
11
|
+
json.each do |element|
|
12
|
+
sub_path = parts[(index.next)...(parts.length)].join('.')
|
13
|
+
get_by_path(sub_path, element, &block)
|
14
|
+
end
|
15
|
+
return
|
16
|
+
end
|
17
|
+
next
|
18
|
+
end
|
19
|
+
if /^[\d]+(\.[\d]+){0,1}$/ === part
|
20
|
+
part = part.to_i
|
21
|
+
json = json[part]
|
22
|
+
else
|
23
|
+
json = json[part.to_sym]
|
24
|
+
raise "Expected #{path} to be object or array got #{json.class} from JSON response" unless [Array, Hash, NilClass].include?(json.class)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
if type == '*'
|
28
|
+
json.each{|part| yield part}
|
29
|
+
elsif type == '?'
|
30
|
+
item_count = json.length
|
31
|
+
error_count = 0
|
32
|
+
json.each do |part|
|
33
|
+
begin
|
34
|
+
yield part
|
35
|
+
rescue Exception => e
|
36
|
+
error_count += 1
|
37
|
+
raise "Expected one object in path #{path} to match provided JSON values" if item_count == error_count
|
38
|
+
end
|
39
|
+
end
|
40
|
+
else
|
41
|
+
yield json
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
45
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
require 'rack/test'
|
2
2
|
|
3
3
|
module Airborne
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
module RackTestRequester
|
5
|
+
def make_request(method, url, options = {})
|
6
|
+
browser = Rack::Test::Session.new(Rack::MockSession.new(Airborne.configuration.rack_app))
|
7
|
+
browser.send(method, url, options[:body] || {}, options[:headers] || {})
|
8
|
+
browser.last_response
|
9
|
+
end
|
10
|
+
end
|
11
11
|
end
|
@@ -1,130 +1,136 @@
|
|
1
1
|
require 'rspec'
|
2
2
|
|
3
3
|
module Airborne
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
module RequestExpectations
|
5
|
+
include RSpec
|
6
|
+
include PathMatcher
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
def expect_json_types(*args)
|
9
|
+
set_response(@response) if @json_body.nil?
|
10
|
+
call_with_path(args) do |param, body|
|
11
|
+
expect_json_types_impl(param, body)
|
12
|
+
end
|
13
|
+
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
def expect_json(*args)
|
16
|
+
set_response(@response) if @json_body.nil?
|
17
|
+
call_with_path(args) do |param, body|
|
18
|
+
expect_json_impl(param, body)
|
19
|
+
end
|
20
|
+
end
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
def expect_json_keys(*args)
|
23
|
+
set_response(@response) if @json_body.nil?
|
24
|
+
call_with_path(args) do |param, body|
|
25
|
+
expect(body.keys).to include(*param)
|
26
|
+
end
|
27
|
+
end
|
25
28
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
+
def expect_status(code)
|
30
|
+
set_response(@response) if @json_body.nil?
|
31
|
+
expect(response.code).to eq(code)
|
32
|
+
end
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
def expect_header(key, content)
|
35
|
+
set_response(@response) if @json_body.nil?
|
36
|
+
header = headers[key]
|
37
|
+
if header
|
38
|
+
expect(header.downcase).to eq(content.downcase)
|
39
|
+
else
|
40
|
+
raise "Header #{key} not present in HTTP response"
|
41
|
+
end
|
42
|
+
end
|
38
43
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
def expect_header_contains(key, content)
|
45
|
+
set_response(@response) if @json_body.nil?
|
46
|
+
header = headers[key]
|
47
|
+
if header
|
48
|
+
expect(header.downcase).to include(content.downcase)
|
49
|
+
else
|
50
|
+
raise "Header #{key} not present in HTTP response"
|
51
|
+
end
|
52
|
+
end
|
47
53
|
|
48
|
-
|
49
|
-
|
50
|
-
|
54
|
+
def optional(hash)
|
55
|
+
OptionalHashTypeExpectations.new(hash)
|
56
|
+
end
|
51
57
|
|
52
|
-
|
53
|
-
|
54
|
-
|
58
|
+
def regex(reg)
|
59
|
+
Regexp.new(reg)
|
60
|
+
end
|
55
61
|
|
56
|
-
|
62
|
+
private
|
57
63
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
64
|
+
def call_with_path(args)
|
65
|
+
if args.length == 2
|
66
|
+
get_by_path(args[0], json_body) do|json_chunk|
|
67
|
+
yield(args[1], json_chunk)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
yield(args[0], json_body)
|
71
|
+
end
|
72
|
+
end
|
67
73
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
74
|
+
def get_mapper
|
75
|
+
base_mapper = {
|
76
|
+
integer: [Fixnum,Bignum],
|
77
|
+
array_of_integers: [Fixnum,Bignum],
|
78
|
+
int: [Fixnum,Bignum],
|
79
|
+
array_of_ints: [Fixnum,Bignum],
|
80
|
+
float: [Float,Fixnum,Bignum],
|
81
|
+
array_of_floats: [Float,Fixnum,Bignum],
|
82
|
+
string: [String],
|
83
|
+
array_of_strings: [String],
|
84
|
+
boolean: [TrueClass, FalseClass],
|
85
|
+
array_of_booleans: [TrueClass, FalseClass],
|
86
|
+
bool: [TrueClass, FalseClass],
|
87
|
+
array_of_bools: [TrueClass, FalseClass],
|
88
|
+
object: [Hash],
|
89
|
+
array_of_objects: [Hash],
|
90
|
+
array: [Array],
|
91
|
+
array_of_arrays: [Array]
|
92
|
+
}
|
87
93
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
+
mapper = base_mapper.clone
|
95
|
+
base_mapper.each do |key, value|
|
96
|
+
mapper[(key.to_s + "_or_null").to_sym] = value + [NilClass]
|
97
|
+
end
|
98
|
+
mapper
|
99
|
+
end
|
94
100
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
101
|
+
def expect_json_types_impl(expectations, hash)
|
102
|
+
return if expectations.class == Airborne::OptionalHashTypeExpectations && hash.nil?
|
103
|
+
@mapper ||= get_mapper
|
104
|
+
expectations.each do |prop_name, expected_type|
|
105
|
+
value = hash[prop_name]
|
106
|
+
if expected_type.class == Hash || expected_type.class == Airborne::OptionalHashTypeExpectations
|
107
|
+
expect_json_types_impl(expected_type, value)
|
108
|
+
elsif expected_type.class == Proc
|
109
|
+
expected_type.call(value)
|
110
|
+
elsif expected_type.to_s.include?("array_of")
|
111
|
+
expect(value.class).to eq(Array), "Expected #{prop_name} to be of type #{expected_type}, got #{value.class} instead"
|
112
|
+
value.each do |val|
|
113
|
+
expect(@mapper[expected_type].include?(val.class)).to eq(true), "Expected #{prop_name} to be of type #{expected_type}, got #{val.class} instead"
|
114
|
+
end
|
115
|
+
else
|
116
|
+
expect(@mapper[expected_type].include?(value.class)).to eq(true), "Expected #{prop_name} to be of type #{expected_type}, got #{value.class} instead"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
114
120
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
121
|
+
def expect_json_impl(expectations, hash)
|
122
|
+
expectations.each do |prop_name, expected_value|
|
123
|
+
actual_value = hash[prop_name]
|
124
|
+
if expected_value.class == Hash
|
125
|
+
expect_json_impl(expected_value, actual_value)
|
126
|
+
elsif expected_value.class == Proc
|
127
|
+
expected_value.call(actual_value)
|
128
|
+
elsif expected_value.class == Regexp
|
129
|
+
expect(actual_value).to match(expected_value)
|
130
|
+
else
|
131
|
+
expect(actual_value).to eq(expected_value)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
130
136
|
end
|