swaggable 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +20 -0
- data/README.md +123 -0
- data/Rakefile +13 -0
- data/assets/json-schema-draft-04.json +150 -0
- data/assets/swagger-2.0-schema.json +1495 -0
- data/lib/swaggable.rb +14 -0
- data/lib/swaggable/api_definition.rb +52 -0
- data/lib/swaggable/endpoint_definition.rb +54 -0
- data/lib/swaggable/grape_adapter.rb +84 -0
- data/lib/swaggable/parameter_definition.rb +50 -0
- data/lib/swaggable/rack_app.rb +30 -0
- data/lib/swaggable/response_definition.rb +16 -0
- data/lib/swaggable/swagger_2_serializer.rb +92 -0
- data/lib/swaggable/swagger_2_validator.rb +28 -0
- data/lib/swaggable/tag_definition.rb +27 -0
- data/lib/swaggable/version.rb +3 -0
- data/spec/assets/valid-swagger-2.0.json +1 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/swaggable/api_definition_spec.rb +169 -0
- data/spec/swaggable/endpoint_definition_spec.rb +77 -0
- data/spec/swaggable/grape_adapter_spec.rb +198 -0
- data/spec/swaggable/integration_spec.rb +100 -0
- data/spec/swaggable/parameter_definition_spec.rb +64 -0
- data/spec/swaggable/rack_app_spec.rb +49 -0
- data/spec/swaggable/response_definition_spec.rb +17 -0
- data/spec/swaggable/swagger_2_serializer_spec.rb +208 -0
- data/spec/swaggable/swagger_2_validator_spec.rb +26 -0
- data/spec/swaggable/tag_definition_spec.rb +48 -0
- data/spec/swaggable_spec.rb +7 -0
- data/swaggable.gemspec +29 -0
- metadata +161 -0
@@ -0,0 +1 @@
|
|
1
|
+
{"swagger":"2.0","info":{"description":"This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.","version":"1.0.0","title":"Swagger Petstore","termsOfService":"http://swagger.io/terms/","contact":{"email":"apiteam@swagger.io"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"}},"host":"petstore.swagger.io","basePath":"/v2","tags":[{"name":"pet","description":"Everything about your Pets","externalDocs":{"description":"Find out more","url":"http://swagger.io"}},{"name":"store","description":"Access to Petstore orders"},{"name":"user","description":"Operations about user","externalDocs":{"description":"Find out more about our store","url":"http://swagger.io"}}],"schemes":["http"],"paths":{"/pet":{"post":{"tags":["pet"],"summary":"Add a new pet to the store","description":"","operationId":"addPet","consumes":["application/json","application/xml"],"produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"405":{"description":"Invalid input"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]},"put":{"tags":["pet"],"summary":"Update an existing pet","description":"","operationId":"updatePet","consumes":["application/json","application/xml"],"produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"},"405":{"description":"Validation exception"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByStatus":{"get":{"tags":["pet"],"summary":"Finds Pets by status","description":"Multiple status values can be provided with comma seperated strings","operationId":"findPetsByStatus","produces":["application/xml","application/json"],"parameters":[{"name":"status","in":"query","description":"Status values that need to be considered for filter","required":true,"type":"array","items":{"type":"string","enum":["available","pending","sold"],"default":"available"},"collectionFormat":"csv"}],"responses":{"200":{"description":"successful operation","schema":{"type":"array","items":{"$ref":"#/definitions/Pet"}}},"400":{"description":"Invalid status value"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByTags":{"get":{"tags":["pet"],"summary":"Finds Pets by tags","description":"Muliple tags can be provided with comma seperated strings. Use tag1, tag2, tag3 for testing.","operationId":"findPetsByTags","produces":["application/xml","application/json"],"parameters":[{"name":"tags","in":"query","description":"Tags to filter by","required":true,"type":"array","items":{"type":"string"},"collectionFormat":"csv"}],"responses":{"200":{"description":"successful operation","schema":{"type":"array","items":{"$ref":"#/definitions/Pet"}}},"400":{"description":"Invalid tag value"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/{petId}":{"get":{"tags":["pet"],"summary":"Find pet by ID","description":"Returns a single pet","operationId":"getPetById","produces":["application/xml","application/json"],"parameters":[{"name":"petId","in":"path","description":"ID of pet to return","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Pet"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"security":[{"api_key":[]}]},"post":{"tags":["pet"],"summary":"Updates a pet in the store with form data","description":"","operationId":"updatePetWithForm","consumes":["application/x-www-form-urlencoded"],"produces":["application/xml","application/json"],"parameters":[{"name":"petId","in":"path","description":"ID of pet that needs to be updated","required":true,"type":"integer","format":"int64"},{"name":"name","in":"formData","description":"Updated name of the pet","required":false,"type":"string"},{"name":"status","in":"formData","description":"Updated status of the pet","required":false,"type":"string"}],"responses":{"405":{"description":"Invalid input"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]},"delete":{"tags":["pet"],"summary":"Deletes a pet","description":"","operationId":"deletePet","produces":["application/xml","application/json"],"parameters":[{"name":"api_key","in":"header","required":false,"type":"string"},{"name":"petId","in":"path","description":"Pet id to delete","required":true,"type":"integer","format":"int64"}],"responses":{"400":{"description":"Invalid pet value"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/{petId}/uploadImage":{"post":{"tags":["pet"],"summary":"uploads an image","description":"","operationId":"uploadFile","consumes":["multipart/form-data"],"produces":["application/json"],"parameters":[{"name":"petId","in":"path","description":"ID of pet to update","required":true,"type":"integer","format":"int64"},{"name":"additionalMetadata","in":"formData","description":"Additional data to pass to server","required":false,"type":"string"},{"name":"file","in":"formData","description":"file to upload","required":false,"type":"file"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/ApiResponse"}}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/store/inventory":{"get":{"tags":["store"],"summary":"Returns pet inventories by status","description":"Returns a map of status codes to quantities","operationId":"getInventory","produces":["application/json"],"parameters":[],"responses":{"200":{"description":"successful operation","schema":{"type":"object","additionalProperties":{"type":"integer","format":"int32"}}}},"security":[{"api_key":[]}]}},"/store/order":{"post":{"tags":["store"],"summary":"Place an order for a pet","description":"","operationId":"placeOrder","produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"order placed for purchasing the pet","required":true,"schema":{"$ref":"#/definitions/Order"}}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid Order"}}}},"/store/order/{orderId}":{"get":{"tags":["store"],"summary":"Find purchase order by ID","description":"For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions","operationId":"getOrderById","produces":["application/xml","application/json"],"parameters":[{"name":"orderId","in":"path","description":"ID of pet that needs to be fetched","required":true,"type":"integer","maximum":5.0,"minimum":1.0,"format":"int64"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}}},"delete":{"tags":["store"],"summary":"Delete purchase order by ID","description":"For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors","operationId":"deleteOrder","produces":["application/xml","application/json"],"parameters":[{"name":"orderId","in":"path","description":"ID of the order that needs to be deleted","required":true,"type":"string","minimum":1.0}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}}}},"/user":{"post":{"tags":["user"],"summary":"Create user","description":"This can only be done by the logged in user.","operationId":"createUser","produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"Created user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"default":{"description":"successful operation"}}}},"/user/createWithArray":{"post":{"tags":["user"],"summary":"Creates list of users with given input array","description":"","operationId":"createUsersWithArrayInput","produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"type":"array","items":{"$ref":"#/definitions/User"}}}],"responses":{"default":{"description":"successful operation"}}}},"/user/createWithList":{"post":{"tags":["user"],"summary":"Creates list of users with given input array","description":"","operationId":"createUsersWithListInput","produces":["application/xml","application/json"],"parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"type":"array","items":{"$ref":"#/definitions/User"}}}],"responses":{"default":{"description":"successful operation"}}}},"/user/login":{"get":{"tags":["user"],"summary":"Logs user into the system","description":"","operationId":"loginUser","produces":["application/xml","application/json"],"parameters":[{"name":"username","in":"query","description":"The user name for login","required":true,"type":"string"},{"name":"password","in":"query","description":"The password for login in clear text","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"type":"string"},"headers":{"X-Rate-Limit":{"type":"integer","format":"int32","description":"calls per hour allowed by the user"},"X-Expires-After":{"type":"string","format":"date-time","description":"date in UTC when toekn expires"}}},"400":{"description":"Invalid username/password supplied"}}}},"/user/logout":{"get":{"tags":["user"],"summary":"Logs out current logged in user session","description":"","operationId":"logoutUser","produces":["application/xml","application/json"],"parameters":[],"responses":{"default":{"description":"successful operation"}}}},"/user/{username}":{"get":{"tags":["user"],"summary":"Get user by user name","description":"","operationId":"getUserByName","produces":["application/xml","application/json"],"parameters":[{"name":"username","in":"path","description":"The name that needs to be fetched. Use user1 for testing. ","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/User"}},"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}}},"put":{"tags":["user"],"summary":"Updated user","description":"This can only be done by the logged in user.","operationId":"updateUser","produces":["application/xml","application/json"],"parameters":[{"name":"username","in":"path","description":"name that need to be deleted","required":true,"type":"string"},{"in":"body","name":"body","description":"Updated user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"400":{"description":"Invalid user supplied"},"404":{"description":"User not found"}}},"delete":{"tags":["user"],"summary":"Delete user","description":"This can only be done by the logged in user.","operationId":"deleteUser","produces":["application/xml","application/json"],"parameters":[{"name":"username","in":"path","description":"The name that needs to be deleted","required":true,"type":"string"}],"responses":{"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}}}}},"securityDefinitions":{"petstore_auth":{"type":"oauth2","authorizationUrl":"http://petstore.swagger.io/api/oauth/dialog","flow":"implicit","scopes":{"write:pets":"modify pets in your account","read:pets":"read your pets"}},"api_key":{"type":"apiKey","name":"api_key","in":"header"}},"definitions":{"Order":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"petId":{"type":"integer","format":"int64"},"quantity":{"type":"integer","format":"int32"},"shipDate":{"type":"string","format":"date-time"},"status":{"type":"string","description":"Order Status","enum":["placed","approved","delivered"]},"complete":{"type":"boolean","default":false}},"xml":{"name":"Order"}},"Category":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}},"xml":{"name":"Category"}},"User":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"username":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"},"phone":{"type":"string"},"userStatus":{"type":"integer","format":"int32","description":"User Status"}},"xml":{"name":"User"}},"Tag":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}},"xml":{"name":"Tag"}},"Pet":{"type":"object","required":["name","photoUrls"],"properties":{"id":{"type":"integer","format":"int64"},"category":{"$ref":"#/definitions/Category"},"name":{"type":"string","example":"doggie"},"photoUrls":{"type":"array","xml":{"name":"photoUrl","wrapped":true},"items":{"type":"string"}},"tags":{"type":"array","xml":{"name":"tag","wrapped":true},"items":{"$ref":"#/definitions/Tag"}},"status":{"type":"string","description":"pet status in the store","enum":["available","pending","sold"]}},"xml":{"name":"Pet"}},"ApiResponse":{"type":"object","properties":{"code":{"type":"integer","format":"int32"},"type":{"type":"string"},"message":{"type":"string"}}}},"externalDocs":{"description":"Find out more about Swagger","url":"http://swagger.io"}}
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rspec'
|
3
|
+
require 'pry'
|
4
|
+
require 'webmock/rspec'
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
config.color = true
|
8
|
+
config.tty = true
|
9
|
+
config.disable_monkey_patching!
|
10
|
+
# config.full_backtrace = true
|
11
|
+
# config.formatter = :documentation # :documentation, :progress, :html, :textmate
|
12
|
+
end
|
13
|
+
|
14
|
+
$LOAD_PATH.unshift File.expand_path('lib')
|
15
|
+
require 'swaggable'
|
16
|
+
|
17
|
+
$LOAD_PATH.unshift File.expand_path('spec/support')
|
18
|
+
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'Swaggable::ApiDefinition' do
|
4
|
+
let(:subject_class) { Swaggable::ApiDefinition }
|
5
|
+
let(:subject_instance) { Swaggable::ApiDefinition.new }
|
6
|
+
subject { subject_instance }
|
7
|
+
|
8
|
+
it 'has a version' do
|
9
|
+
subject.version = 'v2.0'
|
10
|
+
expect(subject.version).to eq 'v2.0'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'has a title' do
|
14
|
+
subject.title = 'a new title'
|
15
|
+
expect(subject.title).to eq 'a new title'
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'has a description' do
|
19
|
+
subject.description = 'a new description'
|
20
|
+
expect(subject.description).to eq 'a new description'
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'has a base_path' do
|
24
|
+
subject.base_path = 'a new base_path'
|
25
|
+
expect(subject.base_path).to eq 'a new base_path'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'has endpoints' do
|
29
|
+
endpoint = Swaggable::EndpointDefinition.new verb: :get, path: '/'
|
30
|
+
subject.endpoints << endpoint
|
31
|
+
expect(subject.endpoints.first).to eq endpoint
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#endpoints' do
|
35
|
+
subject { subject_instance.endpoints }
|
36
|
+
|
37
|
+
it 'accumulates endpoints with <<' do
|
38
|
+
endpoint = Swaggable::EndpointDefinition.new verb: :get, path: '/'
|
39
|
+
subject << endpoint
|
40
|
+
expect(subject['GET /']).to be endpoint
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'accumulates endpoints with add' do
|
44
|
+
endpoint = Swaggable::EndpointDefinition.new verb: :get, path: '/'
|
45
|
+
subject.add endpoint
|
46
|
+
expect(subject['GET /']).to be endpoint
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'adds new endpoints' do
|
50
|
+
endpoint = subject.add_new do |e|
|
51
|
+
e.verb = :post
|
52
|
+
e.path = '/users'
|
53
|
+
end
|
54
|
+
|
55
|
+
expect(subject['POST /users']).to be endpoint
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'iterates through endpoints' do
|
59
|
+
has_run = false
|
60
|
+
|
61
|
+
endpoint = subject.add_new do |e|
|
62
|
+
e.verb = :post
|
63
|
+
e.path = '/users'
|
64
|
+
end
|
65
|
+
|
66
|
+
subject.each do |e|
|
67
|
+
has_run = true
|
68
|
+
expect(e).to be endpoint
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'converts to array' do
|
73
|
+
endpoint = subject.add_new do |e|
|
74
|
+
e.verb = :post
|
75
|
+
e.path = '/users'
|
76
|
+
end
|
77
|
+
|
78
|
+
expect(subject.to_a).to eq [endpoint]
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'clears' do
|
82
|
+
endpoint = subject.add_new do |e|
|
83
|
+
e.verb = :post
|
84
|
+
e.path = '/users'
|
85
|
+
end
|
86
|
+
|
87
|
+
subject.clear
|
88
|
+
|
89
|
+
expect(subject.to_a).to eq []
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe '#tags' do
|
94
|
+
it 'is collected from endpoints' do
|
95
|
+
tag = instance_double(Swaggable::TagDefinition, name: 'A tag')
|
96
|
+
endpoint = Swaggable::EndpointDefinition.new verb: :get, path: '/'
|
97
|
+
endpoint.tags << tag
|
98
|
+
subject.endpoints << endpoint
|
99
|
+
expect(subject.tags.first).to eq tag
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'avoids duplicates' do
|
103
|
+
tag_1 = Swaggable::TagDefinition.new name: 'tag_1'
|
104
|
+
tag_1_again = Swaggable::TagDefinition.new name: 'tag_1'
|
105
|
+
tag_2 = Swaggable::TagDefinition.new name: 'tag_2'
|
106
|
+
|
107
|
+
endpoint_a = Swaggable::EndpointDefinition.new
|
108
|
+
endpoint_b = Swaggable::EndpointDefinition.new
|
109
|
+
|
110
|
+
endpoint_a.tags << tag_1
|
111
|
+
endpoint_b.tags << tag_1_again
|
112
|
+
endpoint_b.tags << tag_2
|
113
|
+
|
114
|
+
subject.endpoints << endpoint_a
|
115
|
+
subject.endpoints << endpoint_b
|
116
|
+
|
117
|
+
expect(subject.tags.to_a).to eq [tag_1_again, tag_2]
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'is empty array when no tags are present' do
|
121
|
+
subject.endpoints.clear
|
122
|
+
expect(subject.tags).to eq []
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'is frozen to avoid giving the false impression that it can be modified' do
|
126
|
+
expect{ subject.tags << instance_double(Swaggable::TagDefinition) }.to raise_error
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'yields itself on initialize' do
|
131
|
+
yielded = false
|
132
|
+
|
133
|
+
subject_class.new do |s|
|
134
|
+
expect(s).to be_a subject_class
|
135
|
+
yielded = true
|
136
|
+
end
|
137
|
+
|
138
|
+
expect(yielded).to be true
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'builds from a Grape API' do
|
142
|
+
allow(subject_class.grape_adapter).to receive(:import) { |grape, api| api.title = 'A test'; api }
|
143
|
+
result = subject_class.from_grape_api double('grape')
|
144
|
+
expect(result.title).to eq 'A test'
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'has a dsl' do
|
148
|
+
api = Swaggable::ApiDefinition.new
|
149
|
+
|
150
|
+
api.configure do
|
151
|
+
version 'v1.0'
|
152
|
+
title 'My API'
|
153
|
+
description 'My cool API'
|
154
|
+
base_path '/a/path'
|
155
|
+
|
156
|
+
endpoints do
|
157
|
+
add_new do
|
158
|
+
path '/users'
|
159
|
+
verb :post
|
160
|
+
description 'Creates users'
|
161
|
+
summary 'Allows to create an user'
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
expect(api.version).to eq 'v1.0'
|
167
|
+
expect(api.endpoints.first.path).to eq '/users'
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'Swaggable::EndpointDefinition' do
|
4
|
+
let(:subject_class) { Swaggable::EndpointDefinition }
|
5
|
+
let(:subject_instance) { Swaggable::EndpointDefinition.new }
|
6
|
+
subject { subject_instance }
|
7
|
+
|
8
|
+
it 'has a path' do
|
9
|
+
subject.path = '/'
|
10
|
+
expect(subject.path).to eq '/'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'has a verb' do
|
14
|
+
subject.verb = 'POST'
|
15
|
+
expect(subject.verb).to eq 'POST'
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'has a description' do
|
19
|
+
subject.description = 'a new desc'
|
20
|
+
expect(subject.description).to eq 'a new desc'
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'has a summary' do
|
24
|
+
subject.summary = 'a new summary'
|
25
|
+
expect(subject.summary).to eq 'a new summary'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'has tags' do
|
29
|
+
tag = instance_double(Swaggable::TagDefinition, name: 'A Tag')
|
30
|
+
subject.tags << tag
|
31
|
+
expect(subject.tags.to_a).to eq [tag]
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'has consumes' do
|
35
|
+
format = double('format')
|
36
|
+
subject.consumes << format
|
37
|
+
expect(subject.consumes).to eq [format]
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'has produces' do
|
41
|
+
format = double('format')
|
42
|
+
subject.produces << format
|
43
|
+
expect(subject.produces).to eq [format]
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'has parameters' do
|
47
|
+
parameter = Swaggable::ParameterDefinition.new name: 'some_parameter'
|
48
|
+
subject.parameters << parameter
|
49
|
+
expect(subject.parameters.to_a).to eq [parameter]
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'yields itself on initialize' do
|
53
|
+
yielded = false
|
54
|
+
|
55
|
+
subject_class.new do |s|
|
56
|
+
expect(s).to be_a subject_class
|
57
|
+
yielded = true
|
58
|
+
end
|
59
|
+
|
60
|
+
expect(yielded).to be true
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'accepts attributes on initialize' do
|
64
|
+
endpoint = subject_class.new path: '/a/path', verb: 'GET'
|
65
|
+
expect(endpoint.path).to eq '/a/path'
|
66
|
+
expect(endpoint.verb).to eq 'GET'
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'has responses' do
|
70
|
+
subject.responses.add_new do
|
71
|
+
status 418
|
72
|
+
description 'Teapot'
|
73
|
+
end
|
74
|
+
|
75
|
+
expect(subject.responses[418].description).to eq 'Teapot'
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'grape'
|
3
|
+
|
4
|
+
RSpec.describe 'Swaggable::GrapeAdapter' do
|
5
|
+
let(:subject_class) { Swaggable::GrapeAdapter }
|
6
|
+
let(:subject_instance) { Swaggable::GrapeAdapter.new }
|
7
|
+
subject { subject_instance }
|
8
|
+
|
9
|
+
describe '#import(grape_api, api_definition)' do
|
10
|
+
let(:api) { Swaggable::ApiDefinition.new }
|
11
|
+
let(:grape) { Class.new(Grape::API) }
|
12
|
+
|
13
|
+
def do_import
|
14
|
+
subject.import grape, api
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns the api' do
|
18
|
+
api = do_import
|
19
|
+
expect(api).to be_a Swaggable::ApiDefinition
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'sets version' do
|
23
|
+
grape.version 'v2.0'
|
24
|
+
do_import
|
25
|
+
expect(api.version).to eq 'v2.0'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'sets title' do
|
29
|
+
allow(grape).to receive(:name).and_return('MyAPI')
|
30
|
+
do_import
|
31
|
+
expect(api.title).to eq 'MyAPI'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'sets base path' do
|
35
|
+
do_import
|
36
|
+
expect(api.base_path).to eq '/'
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'endpoints' do
|
40
|
+
it 'sets verb' do
|
41
|
+
grape.post { }
|
42
|
+
do_import
|
43
|
+
expect(api.endpoints.first.verb).to eq 'post'
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'sets description as summary' do
|
47
|
+
grape.desc 'My endpoint'
|
48
|
+
grape.post { }
|
49
|
+
do_import
|
50
|
+
expect(api.endpoints.first.summary).to eq 'My endpoint'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'sets format' do
|
54
|
+
grape.format :json
|
55
|
+
grape.post { }
|
56
|
+
do_import
|
57
|
+
expect(api.endpoints.first.produces).to eq ['application/json']
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'path' do
|
61
|
+
it 'has no (.:format)' do
|
62
|
+
grape.post('/a/path') { }
|
63
|
+
do_import
|
64
|
+
expect(api.endpoints.first.path).to eq '/a/path'
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'has no (/.:format)' do
|
68
|
+
grape.version 'v1.0'
|
69
|
+
grape.get('/') { }
|
70
|
+
do_import
|
71
|
+
expect(api.endpoints.first.path).to eq '/v1.0'
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'has version number' do
|
75
|
+
grape.version 'v3.0'
|
76
|
+
grape.post('/a/path') { }
|
77
|
+
do_import
|
78
|
+
expect(api.endpoints.first.path).to eq '/v3.0/a/path'
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'has version number if present with prefix too' do
|
82
|
+
grape.version 'v3.0'
|
83
|
+
grape.prefix '/api'
|
84
|
+
grape.post('/a/path') { }
|
85
|
+
do_import
|
86
|
+
expect(api.endpoints.first.path).to eq '/api/v3.0/a/path'
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'has version number if present with prefix not beginning with / too' do
|
90
|
+
grape.version 'v3.0'
|
91
|
+
grape.prefix 'api'
|
92
|
+
grape.post('/a/path') { }
|
93
|
+
do_import
|
94
|
+
expect(api.endpoints.first.path).to eq '/api/v3.0/a/path'
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'has parameters' do
|
98
|
+
grape.version 'v3.0'
|
99
|
+
grape.prefix '/api'
|
100
|
+
grape.post('/a/path/:with/:parameters') { }
|
101
|
+
do_import
|
102
|
+
expect(api.endpoints.first.path).to eq '/api/v3.0/a/path/{with}/{parameters}'
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'has tags' do
|
106
|
+
grape.post('/a/path') { }
|
107
|
+
do_import
|
108
|
+
tag = api.endpoints.first.tags.first
|
109
|
+
expect(tag.name).to eq grape.name
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe 'parameters' do
|
114
|
+
it 'have name' do
|
115
|
+
grape.params do
|
116
|
+
requires :user_uuid
|
117
|
+
end
|
118
|
+
|
119
|
+
grape.post('/a/path') { }
|
120
|
+
|
121
|
+
do_import
|
122
|
+
expect(api.endpoints.first.parameters.first.name).to eq 'user_uuid'
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'have type' do
|
126
|
+
grape.params do
|
127
|
+
requires :user_uuid, type: String
|
128
|
+
end
|
129
|
+
|
130
|
+
grape.post('/a/path') { }
|
131
|
+
|
132
|
+
do_import
|
133
|
+
expect(api.endpoints.first.parameters.first.type).to eq :string
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'have required' do
|
137
|
+
grape.params do
|
138
|
+
requires :required_param
|
139
|
+
end
|
140
|
+
|
141
|
+
grape.post('/a/path') { }
|
142
|
+
|
143
|
+
do_import
|
144
|
+
expect(api.endpoints.first.parameters.first.required).to eq true
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'have description' do
|
148
|
+
grape.params do
|
149
|
+
requires :param, desc: 'A param'
|
150
|
+
end
|
151
|
+
|
152
|
+
grape.post('/a/path') { }
|
153
|
+
|
154
|
+
do_import
|
155
|
+
expect(api.endpoints.first.parameters.first.description).to eq 'A param'
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'have location path if path param' do
|
159
|
+
grape.params do
|
160
|
+
requires :required_param
|
161
|
+
end
|
162
|
+
|
163
|
+
grape.post('/a/:required_param') { }
|
164
|
+
|
165
|
+
do_import
|
166
|
+
expect(api.endpoints.first.parameters.first.location).to eq :path
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'have location query if non-path param' do
|
170
|
+
grape.params do
|
171
|
+
requires :required_param
|
172
|
+
end
|
173
|
+
|
174
|
+
grape.post('/a') { }
|
175
|
+
|
176
|
+
do_import
|
177
|
+
expect(api.endpoints.first.parameters.first.location).to eq :query
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe 'responses' do
|
182
|
+
it 'have status' do
|
183
|
+
grape.post('/', http_codes: [201, 'Created']) { }
|
184
|
+
|
185
|
+
do_import
|
186
|
+
expect(api.endpoints.first.responses.first.status).to eq 201
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'have description' do
|
190
|
+
grape.post('/', http_codes: [[201, 'Created']]) { }
|
191
|
+
|
192
|
+
do_import
|
193
|
+
expect(api.endpoints.first.responses.first.description).to eq 'Created'
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|