spaceborne 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -2
- data/Gemfile +7 -1
- data/README.md +191 -15
- data/lib/spaceborne.rb +7 -3
- data/lib/spaceborne/version.rb +1 -1
- data/spaceborne.gemspec +7 -2
- metadata +83 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1be4d255659704a43dfd8522c9e028e9cd34da75
|
4
|
+
data.tar.gz: 809d3a5002bb4de524e927ee403dfc8b4a6d79a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 620ae1514021b81d429d15f0fc9136c26be3f276bc131ee185701fc04a9bef2adc9215fe281733c3ded12add2f28dfbf8dddb51078653e9aa2d2de4bb9778ee5
|
7
|
+
data.tar.gz: ec66899ff1c619a07a462ca388c47e44eddcf655abb0161a9389d5d22c17ffd6133fe0afdffb056f14f5f382d51ea42d573429915ebb1da792a8299b7e13a43c
|
data/.rspec
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -12,48 +12,224 @@ gem 'spaceborne'
|
|
12
12
|
|
13
13
|
And then execute:
|
14
14
|
|
15
|
-
|
15
|
+
```ruby
|
16
|
+
$ bundle
|
17
|
+
```
|
16
18
|
|
17
19
|
Or install it yourself as:
|
18
20
|
|
19
|
-
|
21
|
+
```ruby
|
22
|
+
$ gem install spaceborne
|
23
|
+
```
|
20
24
|
|
21
25
|
## Creating API tests
|
22
26
|
|
27
|
+
### Making a request
|
28
|
+
|
29
|
+
Spaceborne/airborne use curlyrest/rest-client to make the API requests. These are done in the manner that you actually think about the request, the http action verb, the url, headers, and body(optional depending on the verb). When creating a test, you can call any of the following methods: get, post, put, patch, delete, head, options.
|
30
|
+
|
31
|
+
#### Parts of a request
|
32
|
+
|
33
|
+
| part | description | restrictions |
|
34
|
+
|:--- |:----|:----|
|
35
|
+
|url|uri describing destination of the request|not including query/fragment|
|
36
|
+
|headers|hash of request headers|*optional request modifiers*|
|
37
|
+
|body|data being passed in the request|not on head/get requests|
|
38
|
+
|query|data passed on url after '?'|added as a 'params' hash in headers|
|
39
|
+
|
40
|
+
#### Optional request modifiers
|
41
|
+
These are passed as headers, but will be removed from the actual request, taking the desired effect.
|
42
|
+
|
43
|
+
| modifier | values | effect|
|
44
|
+
|:---|:---|:---|
|
45
|
+
|:use_curl| true, 'debug'|executes the command via curl, and if 'debug', output the command and response|
|
46
|
+
|:use_proxy| url of the proxy | sends the request to the proxy |
|
47
|
+
|:nonjson_data|true|forces content-type to application/x-www-form-urlencoded|
|
48
|
+
|
49
|
+
#### Request examples
|
50
|
+
passing request headers
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
get 'http://example.com/api/v1/my_api', { 'x-auth-token' => 'my_token' }
|
54
|
+
```
|
55
|
+
|
56
|
+
passing a body (`post`, `put`, `patch`, `delete`) as a hash
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
post 'http://example.com/api/v1/my_api', { :name => 'John Doe' }, { 'x-auth-token' => 'my_token' }
|
60
|
+
```
|
61
|
+
|
62
|
+
passing Query params via headers
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
post 'http://example.com/api/v1/my_api', { }, { 'params' => {'param_key' => 'param_value' } }
|
66
|
+
```
|
67
|
+
|
68
|
+
make the request using curl and show me the entire request/response
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
get 'http://example.com/api/v1/my_api', { 'x-auth-token' => 'my_token', use_curl: 'debug' }
|
72
|
+
```
|
73
|
+
|
74
|
+
|
75
|
+
### Validating a response
|
76
|
+
|
77
|
+
Most of the power of spaceborne comes from being able to write expectations in a compact form that has great power at being able to perform the actual validation you have in mind. This allows you to build up positive and negative test cases and choose what parts of the response are important, or ignored. Here's an example that we'll look at.
|
78
|
+
|
23
79
|
```ruby
|
24
80
|
require 'spaceborne'
|
25
81
|
|
26
|
-
describe 'sample
|
27
|
-
it 'should
|
82
|
+
describe 'simple get sample' do
|
83
|
+
it 'should pass validation' do
|
28
84
|
wrap_request do
|
29
85
|
get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" }
|
30
86
|
expect_json_types(name: :string)
|
87
|
+
expect_json(name: 'John Doe')
|
31
88
|
end
|
32
89
|
end
|
90
|
+
end
|
91
|
+
```
|
92
|
+
The parts of the example outside of the wrap_request block are typical rspec. The wrap_request is a spaceborne concept, saying that if anything fails inside of the block, to ouput to stdout information about the request, and response. This is to work around the issue of an expectation failing, and having good info about why, but having no idea what the request or response were that caused the failure. The actual request is done on the get line. Validation of the response is the following expect_ lines.
|
93
|
+
|
94
|
+
#### Parts of response to validate
|
95
|
+
|
96
|
+
* HTTP Status
|
97
|
+
* `expect_status`(200) - expect to get a status of 200
|
98
|
+
* Headers
|
99
|
+
* `expect_header`(validators)
|
100
|
+
* `expect_header_types`(validators)
|
101
|
+
* JSON
|
102
|
+
* `expect_json`(validators)
|
103
|
+
* `expect_json`(path, validators)
|
104
|
+
* `expect_json_types`(validators)
|
105
|
+
* `expect_json_types`(path, validators)
|
106
|
+
* `expect_json_keys`(validators)
|
107
|
+
* `expect_json_keys`(path, validators)
|
108
|
+
* `expect_json_sizes`(validators)
|
109
|
+
* `expect_json_sizes`(path, validators)
|
110
|
+
|
111
|
+
##### Validators
|
112
|
+
The validators mimic the structure of the response you're validating. For example, if your API returns the following json on a get call with Alex specified in the URL:
|
33
113
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
114
|
+
```ruby
|
115
|
+
{
|
116
|
+
"name": "Alex",
|
117
|
+
"address": {
|
118
|
+
"street": "Area 51",
|
119
|
+
"city": "Roswell",
|
120
|
+
"state": "NM",
|
121
|
+
"coordinates": {
|
122
|
+
"latitude": 33.3872,
|
123
|
+
"longitude": 104.5281 } },
|
124
|
+
"phones": [
|
125
|
+
{ "type": "cell",
|
126
|
+
"number": "123-456-7890"},
|
127
|
+
{ "type": "home",
|
128
|
+
"number": "987-654-3210"} ]
|
129
|
+
}
|
130
|
+
```
|
131
|
+
|
132
|
+
some possible validations are:
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
expect_json(name: 'Alex') # exact match because you asked for Alex
|
136
|
+
expect_json_types(name: :string, address: {street: :string, city: :string, state: :string,
|
137
|
+
coordinates: {latitude: :float, longitude: :float}},
|
138
|
+
phones: :array_of_objects) # all the types and structure (cannot go into arrays this way)
|
139
|
+
expect_json('address', state: /^[A-Z]{2}$/) # ensures address/state has 2 capital letters
|
140
|
+
expect_json_types('phones.*', type: :string, number: :string) # looks at all elements in array
|
141
|
+
expect_json_types('phones.1', type: :string, number: :string) # looks at second element in array
|
142
|
+
expect_json_keys('address', [:street, :city, :state, :coordinates]) # ensure specified keys present
|
143
|
+
expect_json_sizes(phones: 2) # expect the phones array size to be 2
|
144
|
+
```
|
145
|
+
|
146
|
+
Additionally, if an entire object could be null, but you'd still want to test the types if it does exist, you can wrap the expectations in a call to `optional`:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
it 'should allow optional nested hash' do
|
150
|
+
get '/simple_path_get' #may or may not return coordinates
|
151
|
+
expect_json_types('address.coordinates', optional(latitude: :float, longitude: :float))
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
Additionally, when calling `expect_json`, you can provide a regex pattern in a call to `regex`:
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
describe 'sample spec' do
|
159
|
+
it 'should validate types' do
|
160
|
+
get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" }
|
161
|
+
expect_json(name: regex("^John"))
|
39
162
|
end
|
40
163
|
end
|
41
164
|
```
|
42
165
|
|
43
|
-
|
166
|
+
When calling `expect_json` or `expect_json_types`, you can optionally provide a block and run your own `rspec` expectations:
|
44
167
|
|
45
|
-
|
168
|
+
```ruby
|
169
|
+
describe 'sample spec' do
|
170
|
+
it 'should validate types' do
|
171
|
+
get 'http://example.com/api/v1/simple_get' #json api that returns { "name" : "John Doe" }
|
172
|
+
expect_json(name: -> (name){ expect(name.length).to eq(8) })
|
173
|
+
end
|
174
|
+
end
|
175
|
+
```
|
46
176
|
|
177
|
+
When calling `expect_*_types`, these are the valid types that can be tested against:
|
178
|
+
|
179
|
+
* `:int` or `:integer`
|
180
|
+
* `:float`
|
181
|
+
* `:bool` or `:boolean`
|
182
|
+
* `:string`
|
183
|
+
* `:date`
|
184
|
+
* `:object`
|
185
|
+
* `:null`
|
186
|
+
* `:array`
|
187
|
+
* `:array_of_integers` or `:array_of_ints`
|
188
|
+
* `:array_of_floats`
|
189
|
+
* `:array_of_strings`
|
190
|
+
* `:array_of_booleans` or `:array_of_bools`
|
191
|
+
* `:array_of_objects`
|
192
|
+
* `:array_of_arrays`
|
193
|
+
|
194
|
+
If the properties are optional and may not appear in the response, you can append `_or_null` to the types above.
|
195
|
+
|
196
|
+
Validation for headers follows the same pattern as above, although nesting of multiple levels isn't going to be needed.
|
197
|
+
|
47
198
|
## Extensions to Airborne
|
48
199
|
|
49
200
|
1. Uses curlyrest to allow extension of rest-client with curl requests
|
50
|
-
2.
|
51
|
-
3. json_body is only
|
201
|
+
2. Uses wrap_request to bundle groups of expectations so that if any fail, you will actually see the request and response printed out, rather than just seeing the expectation that failed
|
202
|
+
3. json_body is only parsed once after a request rather than on each expect call
|
52
203
|
4. Expectations for headers use the same form as the expectations for json bodies
|
53
204
|
* `expect_header same arguments/handling as expect_json`
|
54
205
|
* `expect_header_types same arguments/handling as expect_json_types`
|
55
|
-
5.
|
56
|
-
6.
|
206
|
+
5. It is possible to use non-json data in a request
|
207
|
+
6. Expectations on a response with an array of hashes with keys that are unknown, but that have a defined structure are supported (using the '*' in a path)
|
208
|
+
|
209
|
+
The following example shows how this works
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
{ "array_of_hashes": [
|
213
|
+
{ "husband": {"first": "fred", "last": "flinstone"}},
|
214
|
+
{ "buddy": {"first": "barney", "last": "rubble"}},
|
215
|
+
{ "wife": {"first": "wilma", "last": "flinstone"}}
|
216
|
+
],
|
217
|
+
"hash_of_hashes":
|
218
|
+
{ "husband": {"first": "fred", "last": "flinstone"},
|
219
|
+
"buddy": {"first": "barney", "last": "rubble"},
|
220
|
+
"wife": {"first": "wilma", "last": "flinstone"}
|
221
|
+
}
|
222
|
+
}
|
223
|
+
```
|
224
|
+
You can now validate the fact that each element in the collection has a key which is variant, but a value that has a defined format (first and last are strings).
|
225
|
+
|
226
|
+
expect_json_types('array_of_hashes.*.*', first: :string, last: :string)
|
227
|
+
expect_json_types('hash_of_hashes.*', first: :string, last: :string)
|
228
|
+
|
229
|
+
|
230
|
+
## Development
|
231
|
+
|
232
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
57
233
|
|
58
234
|
## Contributing
|
59
235
|
|
data/lib/spaceborne.rb
CHANGED
@@ -37,7 +37,7 @@ module Airborne
|
|
37
37
|
def make_request(method, url, options = {})
|
38
38
|
@json_body = nil
|
39
39
|
@request_body = nil
|
40
|
-
if options[:headers].has_key?(:use_proxy)
|
40
|
+
if options[:headers] && options[:headers].has_key?(:use_proxy)
|
41
41
|
proxy_option = options[:headers][:use_proxy]
|
42
42
|
RestClient.proxy = proxy_option
|
43
43
|
end
|
@@ -69,7 +69,7 @@ module Airborne
|
|
69
69
|
|
70
70
|
private
|
71
71
|
def base_headers
|
72
|
-
{
|
72
|
+
{ content_type: :json }.merge(Airborne.configuration.headers || {})
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
@@ -113,7 +113,11 @@ module Airborne
|
|
113
113
|
def get_by_path(path, json, &block)
|
114
114
|
fail PathError, "Invalid Path, contains '..'" if /\.\./ =~ path
|
115
115
|
type = false
|
116
|
-
|
116
|
+
if path.class == Symbol
|
117
|
+
parts = path.to_s.split('.')
|
118
|
+
else
|
119
|
+
parts = path.split('.')
|
120
|
+
end
|
117
121
|
parts.each_with_index do |part, index|
|
118
122
|
if part == '*' || part == '?'
|
119
123
|
ensure_array_or_hash(path, json)
|
data/lib/spaceborne/version.rb
CHANGED
data/spaceborne.gemspec
CHANGED
@@ -19,10 +19,15 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
+
spec.add_runtime_dependency 'rspec', '~> 3.1'
|
23
|
+
spec.add_runtime_dependency 'rest-client', '< 3.0', '>= 1.7.3'
|
24
|
+
spec.add_runtime_dependency 'rack-test', '~> 0.6', '>= 0.6.2'
|
25
|
+
spec.add_runtime_dependency 'rack'
|
26
|
+
spec.add_runtime_dependency 'activesupport'
|
27
|
+
spec.add_development_dependency 'webmock', '~> 0'
|
28
|
+
|
22
29
|
spec.add_development_dependency "bundler", "~> 1.11"
|
23
30
|
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
-
spec.add_development_dependency "rspec", "~> 3.0"
|
25
|
-
spec.add_development_dependency "rest-client", "~> 2.0"
|
26
31
|
spec.add_development_dependency "byebug", "~> 2.0"
|
27
32
|
spec.add_development_dependency "airborne", "~> 0.2.13"
|
28
33
|
spec.add_development_dependency "curlyrest", "~> 0.1.0"
|
metadata
CHANGED
@@ -1,71 +1,139 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spaceborne
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Keith Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-07-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: rspec
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1
|
20
|
-
type: :
|
19
|
+
version: '3.1'
|
20
|
+
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1
|
26
|
+
version: '3.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rest-client
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "<"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 1.7.3
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "<"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '3.0'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.7.3
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rack-test
|
29
49
|
requirement: !ruby/object:Gem::Requirement
|
30
50
|
requirements:
|
31
51
|
- - "~>"
|
32
52
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
53
|
+
version: '0.6'
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 0.6.2
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0.6'
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 0.6.2
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: rack
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
type: :runtime
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: activesupport
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :runtime
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: webmock
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - "~>"
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
34
102
|
type: :development
|
35
103
|
prerelease: false
|
36
104
|
version_requirements: !ruby/object:Gem::Requirement
|
37
105
|
requirements:
|
38
106
|
- - "~>"
|
39
107
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
108
|
+
version: '0'
|
41
109
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
110
|
+
name: bundler
|
43
111
|
requirement: !ruby/object:Gem::Requirement
|
44
112
|
requirements:
|
45
113
|
- - "~>"
|
46
114
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
115
|
+
version: '1.11'
|
48
116
|
type: :development
|
49
117
|
prerelease: false
|
50
118
|
version_requirements: !ruby/object:Gem::Requirement
|
51
119
|
requirements:
|
52
120
|
- - "~>"
|
53
121
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
122
|
+
version: '1.11'
|
55
123
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
124
|
+
name: rake
|
57
125
|
requirement: !ruby/object:Gem::Requirement
|
58
126
|
requirements:
|
59
127
|
- - "~>"
|
60
128
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
129
|
+
version: '10.0'
|
62
130
|
type: :development
|
63
131
|
prerelease: false
|
64
132
|
version_requirements: !ruby/object:Gem::Requirement
|
65
133
|
requirements:
|
66
134
|
- - "~>"
|
67
135
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
136
|
+
version: '10.0'
|
69
137
|
- !ruby/object:Gem::Dependency
|
70
138
|
name: byebug
|
71
139
|
requirement: !ruby/object:Gem::Requirement
|