raml_ruby 0.1.1
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/.gitignore +19 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +71 -0
- data/Rakefile +1 -0
- data/fixtures/include_1.raml +7 -0
- data/fixtures/schemas/canonicalSchemas.raml +3 -0
- data/fixtures/schemas/filesystem/file.json +1 -0
- data/fixtures/schemas/filesystem/files.json +1 -0
- data/fixtures/schemas/filesystem/fileupdate.json +1 -0
- data/fixtures/schemas/filesystem/relative/test.json +1 -0
- data/lib/raml.rb +104 -0
- data/lib/raml/exceptions.rb +27 -0
- data/lib/raml/mixin/bodies.rb +32 -0
- data/lib/raml/mixin/documentable.rb +32 -0
- data/lib/raml/mixin/global.rb +20 -0
- data/lib/raml/mixin/headers.rb +22 -0
- data/lib/raml/mixin/merge.rb +24 -0
- data/lib/raml/mixin/parent.rb +54 -0
- data/lib/raml/mixin/validation.rb +49 -0
- data/lib/raml/node.rb +219 -0
- data/lib/raml/node/abstract_method.rb +61 -0
- data/lib/raml/node/abstract_resource.rb +165 -0
- data/lib/raml/node/abstract_resource_circular.rb +5 -0
- data/lib/raml/node/body.rb +94 -0
- data/lib/raml/node/documentation.rb +28 -0
- data/lib/raml/node/header.rb +4 -0
- data/lib/raml/node/method.rb +106 -0
- data/lib/raml/node/parameter/abstract_parameter.rb +251 -0
- data/lib/raml/node/parameter/base_uri_parameter.rb +6 -0
- data/lib/raml/node/parameter/form_parameter.rb +6 -0
- data/lib/raml/node/parameter/query_parameter.rb +6 -0
- data/lib/raml/node/parameter/uri_parameter.rb +7 -0
- data/lib/raml/node/parametized_reference.rb +15 -0
- data/lib/raml/node/reference.rb +4 -0
- data/lib/raml/node/resource.rb +26 -0
- data/lib/raml/node/resource_type.rb +20 -0
- data/lib/raml/node/resource_type_reference.rb +5 -0
- data/lib/raml/node/response.rb +32 -0
- data/lib/raml/node/root.rb +246 -0
- data/lib/raml/node/schema.rb +41 -0
- data/lib/raml/node/schema_reference.rb +5 -0
- data/lib/raml/node/template.rb +55 -0
- data/lib/raml/node/trait.rb +18 -0
- data/lib/raml/node/trait_reference.rb +5 -0
- data/lib/raml/parser.rb +57 -0
- data/lib/raml/parser/include.rb +25 -0
- data/lib/raml/patch/hash.rb +6 -0
- data/lib/raml/patch/module.rb +12 -0
- data/lib/raml/version.rb +3 -0
- data/raml_ruby.gemspec +35 -0
- data/raml_spec_reqs.md +276 -0
- data/templates/abstract_parameter.slim +68 -0
- data/templates/body.slim +15 -0
- data/templates/collapse.slim +10 -0
- data/templates/documentation.slim +2 -0
- data/templates/method.slim +38 -0
- data/templates/resource.slim +33 -0
- data/templates/response.slim +13 -0
- data/templates/root.slim +39 -0
- data/templates/style.sass +119 -0
- data/test/apis/box-api.raml +4224 -0
- data/test/apis/instagram-api.raml +3378 -0
- data/test/apis/stripe-api.raml +12227 -0
- data/test/apis/twilio-rest-api.raml +6618 -0
- data/test/apis/twitter-rest-api.raml +34284 -0
- data/test/raml/body_spec.rb +268 -0
- data/test/raml/documentation_spec.rb +49 -0
- data/test/raml/header_spec.rb +17 -0
- data/test/raml/include_spec.rb +40 -0
- data/test/raml/method_spec.rb +701 -0
- data/test/raml/parameter/abstract_parameter_spec.rb +564 -0
- data/test/raml/parameter/form_parameter_spec.rb +17 -0
- data/test/raml/parameter/query_parameter_spec.rb +33 -0
- data/test/raml/parameter/uri_parameter_spec.rb +44 -0
- data/test/raml/parser_spec.rb +53 -0
- data/test/raml/raml_spec.rb +32 -0
- data/test/raml/resource_spec.rb +440 -0
- data/test/raml/resource_type_spec.rb +51 -0
- data/test/raml/response_spec.rb +251 -0
- data/test/raml/root_spec.rb +655 -0
- data/test/raml/schema_spec.rb +110 -0
- data/test/raml/spec_helper.rb +11 -0
- data/test/raml/template_spec.rb +98 -0
- data/test/raml/trait_spec.rb +31 -0
- metadata +337 -0
@@ -0,0 +1,268 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe Raml::Body do
|
4
|
+
let (:media_type) { 'text/xml' }
|
5
|
+
let (:body_data ) {
|
6
|
+
YAML.load(%q(
|
7
|
+
schema: |
|
8
|
+
{
|
9
|
+
"$schema": "http://json-schema.org/draft-03/schema#",
|
10
|
+
"properties": {
|
11
|
+
"input": {
|
12
|
+
"required": false,
|
13
|
+
"type": "string"
|
14
|
+
}
|
15
|
+
},
|
16
|
+
"required": false,
|
17
|
+
"type": "object"
|
18
|
+
}
|
19
|
+
))
|
20
|
+
}
|
21
|
+
let(:form_body_data) {
|
22
|
+
YAML.load(%q(
|
23
|
+
formParameters:
|
24
|
+
param:
|
25
|
+
type: string
|
26
|
+
))
|
27
|
+
}
|
28
|
+
let(:root) { Raml::Root.new 'title' => 'x', 'baseUri' => 'http://foo.com', 'schemas' => [{'Job' => 'xxx'}] }
|
29
|
+
|
30
|
+
subject { Raml::Body.new media_type, body_data, root }
|
31
|
+
|
32
|
+
describe '#initialize' do
|
33
|
+
context 'when the media type is valid' do
|
34
|
+
it "inits body with media_type" do
|
35
|
+
expect( subject.media_type ).to eq media_type
|
36
|
+
end
|
37
|
+
end
|
38
|
+
context 'when the media type is invalid' do
|
39
|
+
let(:media_type) { 'foo' }
|
40
|
+
it { expect { subject }.to raise_error Raml::InvalidMediaType }
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'when the body is not a web form' do
|
44
|
+
context 'when the schema property is valid schema' do
|
45
|
+
it "inits body with schema" do
|
46
|
+
expect( subject.schema ).to be_an Raml::Schema
|
47
|
+
end
|
48
|
+
end
|
49
|
+
context 'when the schema property is valid schema reference' do
|
50
|
+
let (:body_data ) { { 'schema' => 'Job' } }
|
51
|
+
it "inits body with schema" do
|
52
|
+
expect( subject.schema ).to be_an Raml::SchemaReference
|
53
|
+
end
|
54
|
+
end
|
55
|
+
context 'when the schema property is not a string' do
|
56
|
+
let (:body_data ) { { 'schema' => 1 } }
|
57
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /schema/ }
|
58
|
+
end
|
59
|
+
context 'when the schema property is an empty string' do
|
60
|
+
let (:body_data ) { { 'schema' => '' } }
|
61
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /schema/ }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
context 'when body is a web form' do
|
65
|
+
let(:body_data) { form_body_data }
|
66
|
+
[ 'application/x-www-form-urlencoded', 'multipart/form-data' ].each do |mtype|
|
67
|
+
context "when media type is #{mtype}" do
|
68
|
+
let(:media_type) { mtype }
|
69
|
+
context 'when a formParameters property is not provided' do
|
70
|
+
before { body_data.delete 'formParameters' }
|
71
|
+
it { expect { subject }.to raise_error Raml::RequiredPropertyMissing, /formParameters/ }
|
72
|
+
end
|
73
|
+
context 'when a formParameters property is provided' do
|
74
|
+
context 'when a formParameters property is valid' do
|
75
|
+
it { expect { subject }.to_not raise_error }
|
76
|
+
it 'stores all as Raml::Parameter::FormParameter instances' do
|
77
|
+
expect( subject.form_parameters.values ).to all( be_a Raml::Parameter::FormParameter )
|
78
|
+
subject.form_parameters.keys.should contain_exactly('param')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
context 'when the formParameters property is not a map' do
|
82
|
+
before { body_data['formParameters'] = 1 }
|
83
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /formParameters/ }
|
84
|
+
end
|
85
|
+
context 'when the formParameters property is not a map with non-string keys' do
|
86
|
+
before { body_data['formParameters'] = { 1 => {}} }
|
87
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /formParameters/ }
|
88
|
+
end
|
89
|
+
context 'when the formParameters property is not a map with non-string keys' do
|
90
|
+
before { body_data['formParameters'] = { '1' => 'x'} }
|
91
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /formParameters/ }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'when a schema property is not provided' do
|
96
|
+
it { expect { subject }.to_not raise_error }
|
97
|
+
end
|
98
|
+
context 'when a schema property is provided' do
|
99
|
+
let(:body_data) {
|
100
|
+
YAML.load %q(
|
101
|
+
schema: |
|
102
|
+
{
|
103
|
+
"$schema": "http://json-schema.org/draft-03/schema#",
|
104
|
+
"properties": {
|
105
|
+
"input": {
|
106
|
+
"required": false,
|
107
|
+
"type": "string"
|
108
|
+
}
|
109
|
+
},
|
110
|
+
"required": false,
|
111
|
+
"type": "object"
|
112
|
+
}
|
113
|
+
formParameters:
|
114
|
+
param:
|
115
|
+
type: string
|
116
|
+
)
|
117
|
+
}
|
118
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /schema/ }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe '#form_parameters' do
|
126
|
+
context 'when body is not a web form' do
|
127
|
+
it 'returns no form parameters' do
|
128
|
+
subject.form_parameters { should be_empty }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
context 'when body is a web form' do
|
132
|
+
let(:body_data) { form_body_data }
|
133
|
+
it 'returns form parameters' do
|
134
|
+
subject.form_parameters { should_not be_empty }
|
135
|
+
expect( subject.form_parameters.values ).to all( be_a Raml::Parameter::FormParameter )
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe '#web_form?' do
|
141
|
+
context 'when body isnt a web form' do
|
142
|
+
it { should_not be_web_form }
|
143
|
+
end
|
144
|
+
context 'when body is a web form' do
|
145
|
+
let(:body_data) { form_body_data }
|
146
|
+
[ 'application/x-www-form-urlencoded', 'multipart/form-data' ].each do |mtype|
|
147
|
+
context "when media type is #{mtype}" do
|
148
|
+
let(:media_type) { mtype }
|
149
|
+
it { should be_web_form }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe '#merge' do
|
156
|
+
context 'when body and mixin have different media types' do
|
157
|
+
let(:body ) { Raml::Body.new 'foo/bar', {}, root }
|
158
|
+
let(:mixin) { Raml::Body.new 'foo/boo', {}, root }
|
159
|
+
it { expect { body.merge mixin }.to raise_error Raml::MergeError }
|
160
|
+
end
|
161
|
+
context 'when body and mixin have the same media type' do
|
162
|
+
let(:body ) { Raml::Body.new 'foo/bar', body_data , root }
|
163
|
+
let(:mixin) { Raml::Body.new 'foo/bar', mixin_data, root }
|
164
|
+
|
165
|
+
context 'when the body to merge in has a property set' do
|
166
|
+
context 'when the body to merge into does not have the property set' do
|
167
|
+
let(:body_data) { {} }
|
168
|
+
context 'example property' do
|
169
|
+
let(:mixin_data) { {'example' => 'body example'} }
|
170
|
+
it 'merges in the property' do
|
171
|
+
body.merge(mixin).example.should eq mixin.example
|
172
|
+
end
|
173
|
+
end
|
174
|
+
context 'schema property' do
|
175
|
+
let(:mixin_data) { {'schema' => 'mixin schema'} }
|
176
|
+
it 'merges in the property' do
|
177
|
+
body.merge(mixin).schema.should be_a Raml::Schema
|
178
|
+
body.schema.should eq mixin.schema
|
179
|
+
end
|
180
|
+
end
|
181
|
+
context 'formParameters properties' do
|
182
|
+
let(:mixin_data) { {
|
183
|
+
'formParameters' => {
|
184
|
+
'param1' => {'description' => 'foo'},
|
185
|
+
'param2' => {'description' => 'bar'}
|
186
|
+
}
|
187
|
+
} }
|
188
|
+
it 'adds the form parameters to the body' do
|
189
|
+
body.merge(mixin).form_parameters.keys.should contain_exactly('param1', 'param2')
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
context 'when the body to merge into has the property set' do
|
194
|
+
context 'example property' do
|
195
|
+
let(:body_data ) { {'example' => 'body example'} }
|
196
|
+
let(:mixin_data) { {'example' => 'mixin example'} }
|
197
|
+
it 'overrides the property' do
|
198
|
+
body.merge(mixin).example.should eq 'mixin example'
|
199
|
+
end
|
200
|
+
end
|
201
|
+
context 'schema property' do
|
202
|
+
let(:body_data ) { {'schema' => 'body schema' } }
|
203
|
+
let(:mixin_data) { {'schema' => 'mixin schema'} }
|
204
|
+
it 'overrides the property' do
|
205
|
+
body.merge(mixin).schema.value.should eq 'mixin schema'
|
206
|
+
end
|
207
|
+
end
|
208
|
+
context 'formParameters properties' do
|
209
|
+
let(:body_data) { {
|
210
|
+
'formParameters' => {
|
211
|
+
'param1' => {'description' => 'foo'},
|
212
|
+
'param2' => {'description' => 'bar'}
|
213
|
+
}
|
214
|
+
} }
|
215
|
+
context 'when the merged in body form parameters are different from the form parametes of the body merged into' do
|
216
|
+
let(:mixin_data) { {
|
217
|
+
'formParameters' => {
|
218
|
+
'param3' => {'description' => 'foo2'},
|
219
|
+
'param4' => {'description' => 'bar2'}
|
220
|
+
}
|
221
|
+
} }
|
222
|
+
it 'adds the form parameters to the body' do
|
223
|
+
body.merge(mixin).form_parameters.keys.should contain_exactly('param1', 'param2', 'param3', 'param4')
|
224
|
+
end
|
225
|
+
end
|
226
|
+
context 'when the merged in body form parameters overlap with the form parametes of the body merged into' do
|
227
|
+
let(:mixin_data) { {
|
228
|
+
'formParameters' => {
|
229
|
+
'param2' => {'description' => 'bar3', 'displayName' => 'Param 3'},
|
230
|
+
'param3' => {'description' => 'foo2'},
|
231
|
+
'param4' => {'description' => 'bar2'}
|
232
|
+
}
|
233
|
+
} }
|
234
|
+
it 'merges the matching orm parameters and adds the non-matching orm parameters to the body' do
|
235
|
+
body.merge(mixin).form_parameters.keys.should contain_exactly('param1', 'param2', 'param3', 'param4')
|
236
|
+
body.form_parameters['param2'].display_name.should eq mixin.form_parameters['param2'].display_name
|
237
|
+
body.form_parameters['param2'].description.should eq mixin.form_parameters['param2'].description
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
describe '#document' do
|
247
|
+
context 'when the body is not a form body' do
|
248
|
+
it 'returns a String' do
|
249
|
+
subject.document.should be_a String
|
250
|
+
end
|
251
|
+
it 'should render the template' do
|
252
|
+
mock(Slim::Template).new(/templates\/body.slim\z/, is_a(Hash)).mock!.render(is_a(Raml::Node)) { '' }
|
253
|
+
subject.document
|
254
|
+
end
|
255
|
+
end
|
256
|
+
context 'when the body is a form body' do
|
257
|
+
let(:media_type) { 'application/x-www-form-urlencoded' }
|
258
|
+
let(:body_data) { form_body_data }
|
259
|
+
it 'returns a String' do
|
260
|
+
subject.document.should be_a String
|
261
|
+
end
|
262
|
+
it 'should render the template' do
|
263
|
+
mock(Slim::Template).new(/templates\/body.slim\z/, is_a(Hash)).mock!.render(is_a(Raml::Node)) { '' }
|
264
|
+
subject.document
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative 'spec_helper'
|
3
|
+
|
4
|
+
describe Raml::Documentation do
|
5
|
+
let(:root_data) { {'title' => 'x', 'baseUri' => 'http://foo.com'} }
|
6
|
+
let(:root) { Raml::Root.new root_data }
|
7
|
+
|
8
|
+
let(:title) { 'Request Formats' }
|
9
|
+
let(:content) { <<-EOS
|
10
|
+
Credentials
|
11
|
+
-----------
|
12
|
+
|
13
|
+
All requests to Twilio's REST API require you to authenticate using [HTTP basic auth](http://en.wikipedia.org/wiki/Basic_access_authentication) to convey your identity. The username is your AccountSid (a 34 character string, starting with the letters AC). The password is your AuthToken. Your AccountSid and AuthToken are on the [Account Dashboard](http://www.twilio.com/user/account/) page.
|
14
|
+
|
15
|
+
Most HTTP clients (including web-browsers) present a dialog or prompt for you to provide a username and password for HTTP basic auth. Most clients will also allow you to provide credentials in the URL itself. For example:
|
16
|
+
|
17
|
+
```
|
18
|
+
https://{AccountSid}:{AuthToken}@api.twilio.com/2010-04-01/Accounts
|
19
|
+
```
|
20
|
+
|
21
|
+
Retrieving Resources with the HTTP GET Method
|
22
|
+
---------------------------------------------
|
23
|
+
|
24
|
+
You can retrieve a representation of a resource by GETting its url. The easiest way to do this is to copy and paste a URL into your web browser's address bar.
|
25
|
+
|
26
|
+
### Possible GET Response Status Codes
|
27
|
+
|
28
|
+
* 200 OK: The request was successful and the response body contains the representation requested.
|
29
|
+
* 302 FOUND: A common redirect response; you can GET the representation at the URI in the Location response header.
|
30
|
+
* 304 NOT MODIFIED: Your client's cached version of the representation is still up to date.
|
31
|
+
* 401 UNAUTHORIZED: The supplied credentials, if any, are not sufficient to access the resource.
|
32
|
+
* 404 NOT FOUND: You know this one.
|
33
|
+
* 429 TOO MANY REQUESTS: Your application is sending too many simultaneous requests.
|
34
|
+
* 500 SERVER ERROR: We couldn't return the representation due to an internal server error.
|
35
|
+
* 503 SERVICE UNAVAILABLE: We are temporarily unable to return the representation. Please wait for a bit and try again.
|
36
|
+
EOS
|
37
|
+
}
|
38
|
+
subject { Raml::Documentation.new(title, {'content' => content }, root) }
|
39
|
+
|
40
|
+
describe '#document' do
|
41
|
+
it 'returns a String' do
|
42
|
+
subject.document.should be_a String
|
43
|
+
end
|
44
|
+
it 'should render the template' do
|
45
|
+
mock(Slim::Template).new(/templates\/documentation.slim\z/, is_a(Hash)).mock!.render(is_a(Raml::Node)) { '' }
|
46
|
+
subject.document
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe Raml::Header do
|
4
|
+
let(:root_data) { {'title' => 'x', 'baseUri' => 'http://foo.com'} }
|
5
|
+
let(:root) { Raml::Root.new root_data }
|
6
|
+
let(:name) { 'meta' }
|
7
|
+
let (:data) {
|
8
|
+
YAML.load(%q(
|
9
|
+
displayName: Job Metadata
|
10
|
+
description: Field names prefixed with x-Zencoder-job-metadata- contain user-specified metadata.
|
11
|
+
))
|
12
|
+
}
|
13
|
+
|
14
|
+
it "should instanciate Header" do
|
15
|
+
Raml::Header.new(name, data, root)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
describe Raml::Parser::Include do
|
5
|
+
let(:include) { Raml::Parser::Include.new }
|
6
|
+
describe '#init_with' do
|
7
|
+
it 'fetches the path from the given Psych::Coder instance' do
|
8
|
+
include.init_with OpenStruct.new scalar: 'path_to_file'
|
9
|
+
include.path.should eq 'path_to_file'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#content' do
|
14
|
+
before { include.init_with OpenStruct.new scalar: path }
|
15
|
+
context 'when the path in the include directive is absolute' do
|
16
|
+
let(:path) { '/absolute_file_path'}
|
17
|
+
it 'opens and reads the file' do
|
18
|
+
mock(File).open(path).stub!.read { 'content' }
|
19
|
+
include.content('cwd').should eq 'content'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
context 'when the path in the include directive is relative' do
|
23
|
+
let(:path) { 'relative_file_path'}
|
24
|
+
it 'opens and reads the file' do
|
25
|
+
mock(File).open("cwd/#{path}").stub!.read { 'content' }
|
26
|
+
include.content('cwd').should eq 'content'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
[ 'yaml', 'yml', 'raml' ].each do |ext|
|
30
|
+
context "when include directive points to a file ending in .#{ext}" do
|
31
|
+
let(:path) { "/absolute_file_path.#{ext}" }
|
32
|
+
let(:content) { { 'a' => [1,2,3], 'b' => 4 } }
|
33
|
+
it 'parses the file as YAML' do
|
34
|
+
stub(File).open(path).stub!.read { content.to_yaml }
|
35
|
+
include.content('cwd').should eq content
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,701 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe Raml::Method do
|
4
|
+
let(:name) { 'get' }
|
5
|
+
let(:data) {
|
6
|
+
YAML.load(%q(
|
7
|
+
description: Get a list of users
|
8
|
+
queryParameters:
|
9
|
+
page:
|
10
|
+
description: Specify the page that you want to retrieve
|
11
|
+
type: integer
|
12
|
+
required: true
|
13
|
+
example: 1
|
14
|
+
per_page:
|
15
|
+
description: Specify the amount of items that will be retrieved per page
|
16
|
+
type: integer
|
17
|
+
minimum: 10
|
18
|
+
maximum: 200
|
19
|
+
default: 30
|
20
|
+
example: 50
|
21
|
+
protocols: [ HTTP, HTTPS ]
|
22
|
+
responses:
|
23
|
+
200:
|
24
|
+
description: |
|
25
|
+
The list of popular media.
|
26
|
+
))
|
27
|
+
}
|
28
|
+
let(:root_data) { {'title' => 'x', 'baseUri' => 'http://foo.com'} }
|
29
|
+
let(:root) { Raml::Root.new root_data }
|
30
|
+
|
31
|
+
subject { Raml::Method.new(name, data, root) }
|
32
|
+
|
33
|
+
describe '#new' do
|
34
|
+
it "should instanciate Method" do
|
35
|
+
subject
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when the method is a method defined in RFC2616 or RFC5789' do
|
39
|
+
%w(options get head post put delete trace connect patch).each do |method|
|
40
|
+
context "when the method is #{method}" do
|
41
|
+
let(:name) { method }
|
42
|
+
it { expect { subject }.to_not raise_error }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
context 'when the method is an unsupported method' do
|
47
|
+
%w(propfind proppatch mkcol copy move lock unlock).each do |method|
|
48
|
+
context "when the method is #{method}" do
|
49
|
+
let(:name) { method }
|
50
|
+
it { expect { subject }.to raise_error Raml::InvalidMethod }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'when description property is not given' do
|
56
|
+
let(:data) { {} }
|
57
|
+
it { expect { subject }.to_not raise_error }
|
58
|
+
end
|
59
|
+
context 'when description property is given' do
|
60
|
+
context 'when the description property is not a string' do
|
61
|
+
let(:data) { { 'description' => 1 } }
|
62
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /description/ }
|
63
|
+
end
|
64
|
+
context 'when the description property is a string' do
|
65
|
+
let(:data) { { 'description' => 'My Description' } }
|
66
|
+
it { expect { subject }.to_not raise_error }
|
67
|
+
it 'should store the value' do
|
68
|
+
subject.description.should eq data['description']
|
69
|
+
end
|
70
|
+
it 'uses the description in the documentation' do
|
71
|
+
subject.document.should include data['description']
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'when the headers parameter is given' do
|
77
|
+
context 'when the headers property is well formed' do
|
78
|
+
let (:data) {
|
79
|
+
YAML.load(%q(
|
80
|
+
headers:
|
81
|
+
Zencoder-Api-Key:
|
82
|
+
displayName: ZEncoder API Key
|
83
|
+
x-Zencoder-job-metadata-{*}:
|
84
|
+
displayName: Job Metadata
|
85
|
+
))
|
86
|
+
}
|
87
|
+
it { expect { subject }.to_not raise_error }
|
88
|
+
it 'stores all as Raml::Header instances' do
|
89
|
+
expect( subject.headers.values ).to all( be_a Raml::Header )
|
90
|
+
expect( subject.headers.keys ).to contain_exactly('Zencoder-Api-Key','x-Zencoder-job-metadata-{*}')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
context 'when the headers property is not a map' do
|
94
|
+
let(:data) { { 'headers' => 1 } }
|
95
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /headers/ }
|
96
|
+
end
|
97
|
+
context 'when the headers property is not a map with non-string keys' do
|
98
|
+
let(:data) { { 'headers' => { 1 => {}} } }
|
99
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /headers/ }
|
100
|
+
end
|
101
|
+
context 'when the headers property is not a map with non-string keys' do
|
102
|
+
let(:data) { { 'headers' => { '1' => 'x'} } }
|
103
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /headers/ }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'when the protocols property is missing' do
|
108
|
+
let(:data) { { } }
|
109
|
+
it { expect{ subject }.to_not raise_error }
|
110
|
+
end
|
111
|
+
context 'when the protocols property is given' do
|
112
|
+
context 'when the protocol property is not an array' do
|
113
|
+
let(:data) { { 'protocols' => 'HTTP' } }
|
114
|
+
it { expect{ subject }.to raise_error Raml::InvalidProperty, /protocols/ }
|
115
|
+
end
|
116
|
+
context 'when the protocol property is an array but not all elements are strings' do
|
117
|
+
let(:data) { { 'protocols' => ['HTTP', 1] } }
|
118
|
+
it { expect{ subject }.to raise_error Raml::InvalidProperty, /protocols/ }
|
119
|
+
end
|
120
|
+
context 'when the protocol property is an array of strings with invalid values' do
|
121
|
+
let(:data) { { 'protocols' => ['HTTP', 'foo'] } }
|
122
|
+
it { expect{ subject }.to raise_error Raml::InvalidProperty, /protocols/ }
|
123
|
+
end
|
124
|
+
[
|
125
|
+
[ 'HTTP' ],
|
126
|
+
[ 'HTTPS' ],
|
127
|
+
[ 'HTTP', 'HTTPS' ]
|
128
|
+
].each do |protocols|
|
129
|
+
context "when the protocol property is #{protocols}" do
|
130
|
+
let(:data) { { 'protocols' => protocols } }
|
131
|
+
it { expect{ subject }.to_not raise_error }
|
132
|
+
it 'stores the values' do
|
133
|
+
subject.protocols.should eq protocols
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
context 'when the protocol property is an array of valid values in lowercase' do
|
138
|
+
let(:data) { { 'protocols' => ['http', 'https'] } }
|
139
|
+
it 'uppercases them' do
|
140
|
+
subject.protocols.should eq [ 'HTTP', 'HTTPS' ]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'when a queryParameters property is given' do
|
146
|
+
context 'when the queryParameters property is well formed' do
|
147
|
+
let(:data) {
|
148
|
+
YAML.load(
|
149
|
+
%q(
|
150
|
+
description: Get a list of users
|
151
|
+
queryParameters:
|
152
|
+
page:
|
153
|
+
description: Specify the page that you want to retrieve
|
154
|
+
type: integer
|
155
|
+
required: true
|
156
|
+
example: 1
|
157
|
+
per_page:
|
158
|
+
description: Specify the amount of items that will be retrieved per page
|
159
|
+
type: integer
|
160
|
+
minimum: 10
|
161
|
+
maximum: 200
|
162
|
+
default: 30
|
163
|
+
example: 50
|
164
|
+
)
|
165
|
+
)
|
166
|
+
}
|
167
|
+
|
168
|
+
it { expect { subject }.to_not raise_error }
|
169
|
+
it 'stores all as Raml::Parameter::UriParameter instances' do
|
170
|
+
expect( subject.query_parameters.values ).to all( be_a Raml::Parameter::QueryParameter )
|
171
|
+
subject.query_parameters.keys.should contain_exactly('page', 'per_page')
|
172
|
+
end
|
173
|
+
end
|
174
|
+
context 'when the queryParameters property is not a map' do
|
175
|
+
before { data['queryParameters'] = 1 }
|
176
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /queryParameters/ }
|
177
|
+
end
|
178
|
+
context 'when the queryParameters property is not a map with non-string keys' do
|
179
|
+
before { data['queryParameters'] = { 1 => {}} }
|
180
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /queryParameters/ }
|
181
|
+
end
|
182
|
+
context 'when the queryParameters property is not a map with non-string keys' do
|
183
|
+
before { data['queryParameters'] = { '1' => 'x'} }
|
184
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /queryParameters/ }
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
context 'when a body property is given' do
|
189
|
+
context 'when the body property is well formed' do
|
190
|
+
let(:data) {
|
191
|
+
YAML.load(
|
192
|
+
%q(
|
193
|
+
description: Create a Job
|
194
|
+
body:
|
195
|
+
text/xml:
|
196
|
+
schema: job_xml_schema
|
197
|
+
application/json:
|
198
|
+
schema: json_xml_schema
|
199
|
+
)
|
200
|
+
)
|
201
|
+
}
|
202
|
+
|
203
|
+
it { expect { subject }.to_not raise_error }
|
204
|
+
it 'stores all as Raml::Body instances' do
|
205
|
+
expect( subject.bodies.values ).to all( be_a Raml::Body )
|
206
|
+
subject.bodies.keys.should contain_exactly('text/xml', 'application/json')
|
207
|
+
end
|
208
|
+
end
|
209
|
+
context 'when the body property is not a map' do
|
210
|
+
before { data['body'] = 1 }
|
211
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /body/ }
|
212
|
+
end
|
213
|
+
context 'when the body property is a map with non-string keys' do
|
214
|
+
before { data['body'] = { 1 => {}} }
|
215
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /body/ }
|
216
|
+
end
|
217
|
+
context 'when the body property is a map with non-map values' do
|
218
|
+
before { data['body'] = { 'text/xml' => 1 } }
|
219
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /body/ }
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
context 'when a responses property is given' do
|
224
|
+
context 'when the responses property is well formed' do
|
225
|
+
let(:data) {
|
226
|
+
YAML.load(
|
227
|
+
%q(
|
228
|
+
responses:
|
229
|
+
200:
|
230
|
+
body:
|
231
|
+
application/json:
|
232
|
+
example: !include examples/instagram-v1-media-popular-example.json
|
233
|
+
503:
|
234
|
+
description: The service is currently unavailable.
|
235
|
+
)
|
236
|
+
)
|
237
|
+
}
|
238
|
+
|
239
|
+
it { expect { subject }.to_not raise_error }
|
240
|
+
it 'stores all as Raml::Response instances' do
|
241
|
+
expect( subject.responses.values ).to all( be_a Raml::Response )
|
242
|
+
subject.responses.keys.should contain_exactly(200, 503)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
context 'when the responses property is not a map' do
|
246
|
+
before { data['responses'] = 1 }
|
247
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /responses/ }
|
248
|
+
end
|
249
|
+
context 'when the responses property is not a map with string integer keys' do
|
250
|
+
before { data['responses'] = { '200' => {}} }
|
251
|
+
it { expect { subject }.not_to raise_error }
|
252
|
+
end
|
253
|
+
context 'when the responses property is not a map with non-string keys' do
|
254
|
+
before { data['responses'] = { 200 => 'x'} }
|
255
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /responses/ }
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
context 'when an is property is given' do
|
260
|
+
let(:root) {
|
261
|
+
Raml::Root.new 'title' => 'x', 'baseUri' => 'http://foo.com', 'traits' => [
|
262
|
+
{ 'secured' => {} },
|
263
|
+
{ 'paged' => {} },
|
264
|
+
{ 'rateLimited' => {} }
|
265
|
+
]
|
266
|
+
}
|
267
|
+
context 'when the property is valid' do
|
268
|
+
context 'when the property is an array of trait references' do
|
269
|
+
let(:data) { { 'is' => [ 'secured', 'paged' ] } }
|
270
|
+
it { expect { subject }.to_not raise_error }
|
271
|
+
it 'should store the trait references' do
|
272
|
+
subject.traits.should all( be_a Raml::TraitReference )
|
273
|
+
subject.traits.map(&:name).should contain_exactly('secured', 'paged')
|
274
|
+
end
|
275
|
+
end
|
276
|
+
context 'when the property is an array of trait references with parameters' do
|
277
|
+
let(:data) { {
|
278
|
+
'is' => [
|
279
|
+
{'secured' => {'tokenName' => 'access_token'}},
|
280
|
+
{'paged' => {'maxPages' => 10 }}
|
281
|
+
]
|
282
|
+
} }
|
283
|
+
it { expect { subject }.to_not raise_error }
|
284
|
+
it 'should store the trait references' do
|
285
|
+
subject.traits.should all( be_a Raml::TraitReference )
|
286
|
+
subject.traits.map(&:name).should contain_exactly('secured', 'paged')
|
287
|
+
end
|
288
|
+
end
|
289
|
+
context 'when the property is an array of trait definitions' do
|
290
|
+
let(:data) { {
|
291
|
+
'is' => [
|
292
|
+
{'queryParameters' => {'tokenName' => {'description'=>'foo'}}},
|
293
|
+
{'queryParameters' => {'numPages' => {'description'=>'bar'}}}
|
294
|
+
]
|
295
|
+
} }
|
296
|
+
it { expect { subject }.to_not raise_error }
|
297
|
+
it 'should store the traits' do
|
298
|
+
subject.traits.should all( be_a Raml::Trait )
|
299
|
+
subject.traits[0].value.should eq ({'queryParameters' => {'tokenName' => {'description'=>'foo'}}})
|
300
|
+
subject.traits[1].value.should eq ({'queryParameters' => {'numPages' => {'description'=>'bar'}}})
|
301
|
+
end
|
302
|
+
end
|
303
|
+
context 'when the property is an array of mixed trait refrences, trait refrences with parameters, and trait definitions' do
|
304
|
+
let(:data) { {
|
305
|
+
'is' => [
|
306
|
+
{'secured' => {'tokenName' => 'access_token'}},
|
307
|
+
{'queryParameters' => {'numPages' => {'description'=>'bar'}}},
|
308
|
+
'rateLimited'
|
309
|
+
]
|
310
|
+
} }
|
311
|
+
it { expect { subject }.to_not raise_error }
|
312
|
+
it 'should store the traits' do
|
313
|
+
subject.traits.select {|t| t.is_a? Raml::TraitReference }.map(&:name).should contain_exactly('secured', 'rateLimited')
|
314
|
+
subject.traits.select {|t| t.is_a? Raml::Trait}[0].value.should eq ({'queryParameters' => {'numPages' => {'description'=>'bar'}}})
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
context 'when the property is invalid' do
|
319
|
+
context 'when the property is not an array' do
|
320
|
+
let(:data) { { 'is' => 1 } }
|
321
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /is/ }
|
322
|
+
end
|
323
|
+
context 'when the property is an array with elements other than a string or map' do
|
324
|
+
let(:data) { { 'is' => [1] } }
|
325
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /is/ }
|
326
|
+
end
|
327
|
+
context 'when the property is an array an element that appears to be a trait name with parameters, but the params are not a map' do
|
328
|
+
let(:data) { { 'is' => [ { 'secured' => 1 } ] } }
|
329
|
+
it { expect { subject }.to raise_error Raml::InvalidProperty, /is/ }
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
context 'when the syntax tree contains optional properties' do
|
335
|
+
let(:data) {
|
336
|
+
YAML.load(%q(
|
337
|
+
description: Get a list of users
|
338
|
+
queryParameters:
|
339
|
+
page?:
|
340
|
+
description: Specify the page that you want to retrieve
|
341
|
+
type: integer
|
342
|
+
required: true
|
343
|
+
example: 1
|
344
|
+
protocols: [ HTTP, HTTPS ]
|
345
|
+
responses:
|
346
|
+
200:
|
347
|
+
description: |
|
348
|
+
The list of popular media.
|
349
|
+
))
|
350
|
+
}
|
351
|
+
it { expect {subject }.to raise_error Raml::InvalidProperty, /Optional properties/ }
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
describe '#merge' do
|
356
|
+
let(:method) { Raml::Method.new 'get' , method_data, root }
|
357
|
+
let(:trait ) { Raml::Trait.new( 'trait_name', trait_data , root).instantiate({}) }
|
358
|
+
context 'when the trait has a property set' do
|
359
|
+
context 'when the method does not have that property set' do
|
360
|
+
let(:method_data) { {} }
|
361
|
+
context 'description property' do
|
362
|
+
let(:trait_data) { {description: 'trait description'} }
|
363
|
+
it 'sets the property in the method' do
|
364
|
+
method.merge(trait).description.should eq trait.description
|
365
|
+
end
|
366
|
+
end
|
367
|
+
context 'protocols property' do
|
368
|
+
let(:trait_data) { {protocols: ['HTTPS']} }
|
369
|
+
it 'sets the property in the method' do
|
370
|
+
method.merge(trait).protocols.should eq trait.protocols
|
371
|
+
end
|
372
|
+
end
|
373
|
+
context 'headers properties' do
|
374
|
+
let(:trait_data) { {
|
375
|
+
'headers' => {
|
376
|
+
'header1' => {'description' => 'foo'},
|
377
|
+
'header2' => {'description' => 'bar'}
|
378
|
+
}
|
379
|
+
} }
|
380
|
+
it 'adds the headers to the method' do
|
381
|
+
method.merge(trait).headers.keys.should contain_exactly('header1', 'header2')
|
382
|
+
end
|
383
|
+
end
|
384
|
+
context 'queryParameters properties' do
|
385
|
+
let(:trait_data) { {
|
386
|
+
'queryParameters' => {
|
387
|
+
'param1' => {'description' => 'foo'},
|
388
|
+
'param2' => {'description' => 'bar'}
|
389
|
+
}
|
390
|
+
} }
|
391
|
+
it 'adds the headers to the method' do
|
392
|
+
method.merge(trait).query_parameters.keys.should contain_exactly('param1', 'param2')
|
393
|
+
end
|
394
|
+
end
|
395
|
+
context 'body property' do
|
396
|
+
let(:trait_data) { {
|
397
|
+
'body' => {
|
398
|
+
'text/mime1' => {'schema' => 'foo'},
|
399
|
+
'text/mime2' => {'schema' => 'bar'}
|
400
|
+
}
|
401
|
+
} }
|
402
|
+
it 'adds the body media types to the method' do
|
403
|
+
method.merge(trait).bodies.keys.should contain_exactly('text/mime1', 'text/mime2')
|
404
|
+
end
|
405
|
+
end
|
406
|
+
context 'responses property' do
|
407
|
+
let(:trait_data) { {
|
408
|
+
'responses' => {
|
409
|
+
200 => {'description' => 'foo'},
|
410
|
+
404 => {'description' => 'bar'}
|
411
|
+
}
|
412
|
+
} }
|
413
|
+
it 'adds the responses to the method' do
|
414
|
+
method.merge(trait).responses.keys.should contain_exactly(200, 404)
|
415
|
+
end
|
416
|
+
end
|
417
|
+
context 'usage property' do
|
418
|
+
let(:trait_data) { { 'usage' => 'trait usage' } }
|
419
|
+
it 'does not add the usage property to the method' do
|
420
|
+
method.merge(trait)
|
421
|
+
expect { method.usage }.to raise_error NoMethodError
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
context 'when the method has that property set' do
|
426
|
+
context 'description property' do
|
427
|
+
let(:method_data) { {description: 'method description'} }
|
428
|
+
let(:trait_data ) { {description: 'trait description' } }
|
429
|
+
it 'overwrites the method property' do
|
430
|
+
method.merge(trait).description.should eq 'trait description'
|
431
|
+
end
|
432
|
+
end
|
433
|
+
context 'protocols property' do
|
434
|
+
let(:method_data) { {protocols: ['HTTP' ]} }
|
435
|
+
let(:trait_data ) { {protocols: ['HTTPS']} }
|
436
|
+
it 'overwrites the method property' do
|
437
|
+
method.merge(trait).protocols.should eq ['HTTPS']
|
438
|
+
end
|
439
|
+
end
|
440
|
+
context 'headers properties' do
|
441
|
+
let(:method_data) { {
|
442
|
+
'headers' => {
|
443
|
+
'header1' => {'description' => 'foo'},
|
444
|
+
'header2' => {'description' => 'bar'}
|
445
|
+
}
|
446
|
+
} }
|
447
|
+
context 'when the trait headers are different from the method headers' do
|
448
|
+
let(:trait_data) { {
|
449
|
+
'headers' => {
|
450
|
+
'header3' => {'description' => 'foo2'},
|
451
|
+
'header4' => {'description' => 'bar2'}
|
452
|
+
}
|
453
|
+
} }
|
454
|
+
it 'adds the headers to the method' do
|
455
|
+
method.merge(trait).headers.keys.should contain_exactly('header1', 'header2', 'header3', 'header4')
|
456
|
+
end
|
457
|
+
end
|
458
|
+
context 'when the trait headers overlap the the method headers' do
|
459
|
+
let(:trait_data) { {
|
460
|
+
'headers' => {
|
461
|
+
'header2' => {'displayName' => 'Header 3'},
|
462
|
+
'header3' => {'description' => 'foo2'},
|
463
|
+
'header4' => {'description' => 'bar2'}
|
464
|
+
}
|
465
|
+
} }
|
466
|
+
it 'merges the matching headers and adds the non-matching headers to the method' do
|
467
|
+
method.merge(trait).headers.keys.should contain_exactly('header1', 'header2', 'header3', 'header4')
|
468
|
+
method.headers['header2'].display_name.should eq trait.headers['header2'].display_name
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
context 'queryParameters properties' do
|
473
|
+
let(:method_data) { {
|
474
|
+
'queryParameters' => {
|
475
|
+
'param1' => {'description' => 'foo'},
|
476
|
+
'param2' => {'description' => 'bar'}
|
477
|
+
}
|
478
|
+
} }
|
479
|
+
context 'when the trait query parameters are different from the method headers' do
|
480
|
+
let(:trait_data) { {
|
481
|
+
'queryParameters' => {
|
482
|
+
'param3' => {'description' => 'foo2'},
|
483
|
+
'param4' => {'description' => 'bar2'}
|
484
|
+
}
|
485
|
+
} }
|
486
|
+
it 'adds the query parameters to the method' do
|
487
|
+
method.merge(trait).query_parameters.keys.should contain_exactly('param1', 'param2', 'param3', 'param4')
|
488
|
+
end
|
489
|
+
end
|
490
|
+
context 'when the trait query parameters overlap the the method query parameters' do
|
491
|
+
let(:trait_data) { {
|
492
|
+
'queryParameters' => {
|
493
|
+
'param2' => {'displayName' => 'Param 3'},
|
494
|
+
'param3' => {'description' => 'foo2'},
|
495
|
+
'param4' => {'description' => 'bar2'}
|
496
|
+
}
|
497
|
+
} }
|
498
|
+
it 'merges the matching headers and adds the non-matching headers to the method' do
|
499
|
+
method.merge(trait).query_parameters.keys.should contain_exactly('param1', 'param2', 'param3', 'param4')
|
500
|
+
method.query_parameters['param2'].display_name.should eq trait.query_parameters['param2'].display_name
|
501
|
+
end
|
502
|
+
end
|
503
|
+
end
|
504
|
+
context 'body property' do
|
505
|
+
let(:method_data) { {
|
506
|
+
'body' => {
|
507
|
+
'text/mime1' => {'schema' => 'foo'},
|
508
|
+
'text/mime2' => {'schema' => 'bar'}
|
509
|
+
}
|
510
|
+
} }
|
511
|
+
context 'when the trait query parameters are different from the method headers' do
|
512
|
+
let(:trait_data) { {
|
513
|
+
'body' => {
|
514
|
+
'text/mime3' => {'schema' => 'foo2'},
|
515
|
+
'text/mime4' => {'schema' => 'bar2'}
|
516
|
+
}
|
517
|
+
} }
|
518
|
+
it 'adds the body media types to the method' do
|
519
|
+
method.merge(trait).bodies.keys.should contain_exactly('text/mime1', 'text/mime2', 'text/mime3', 'text/mime4')
|
520
|
+
end
|
521
|
+
end
|
522
|
+
context 'when the trait query parameters overlap the the method query parameters' do
|
523
|
+
let(:trait_data) { {
|
524
|
+
'body' => {
|
525
|
+
'text/mime2' => {'example' => 'Example 2'},
|
526
|
+
'text/mime3' => {'schema' => 'foo2'},
|
527
|
+
'text/mime4' => {'schema' => 'bar2'}
|
528
|
+
}
|
529
|
+
} }
|
530
|
+
it 'merges the matching media types and adds the non-matching media types to the method' do
|
531
|
+
method.merge(trait).bodies.keys.should contain_exactly('text/mime1', 'text/mime2', 'text/mime3', 'text/mime4')
|
532
|
+
method.bodies['text/mime2'].example.should eq trait.bodies['text/mime2'].example
|
533
|
+
end
|
534
|
+
end
|
535
|
+
end
|
536
|
+
context 'responses property' do
|
537
|
+
let(:method_data) { {
|
538
|
+
'responses' => {
|
539
|
+
200 => {'description' => 'foo', 'body' => { 'text/mime1' => {'schema' => 'schema1'} } },
|
540
|
+
404 => {'description' => 'bar'}
|
541
|
+
}
|
542
|
+
} }
|
543
|
+
context 'when the trait response status codes are different from the method responses status codes' do
|
544
|
+
let(:trait_data) { {
|
545
|
+
'responses' => {
|
546
|
+
201 => {'description' => 'foo2'},
|
547
|
+
403 => {'description' => 'bar2'}
|
548
|
+
}
|
549
|
+
} }
|
550
|
+
it 'adds the body media types to the method' do
|
551
|
+
method.merge(trait).responses.keys.should contain_exactly(200, 404, 201, 403)
|
552
|
+
end
|
553
|
+
end
|
554
|
+
context 'when the trait query parameters overlap the the method query parameters' do
|
555
|
+
let(:trait_data) { {
|
556
|
+
'responses' => {
|
557
|
+
200 => {'description' => 'foo', 'body' => { 'text/mime2' => {'schema' => 'schema2'} } },
|
558
|
+
201 => {'description' => 'foo2'},
|
559
|
+
403 => {'description' => 'bar2'}
|
560
|
+
}
|
561
|
+
} }
|
562
|
+
it 'merges the matching media types and adds the non-matching media types to the method' do
|
563
|
+
method.merge(trait).responses.keys.should contain_exactly(200, 404, 201, 403)
|
564
|
+
method.responses[200].bodies.keys.should contain_exactly('text/mime1', 'text/mime2')
|
565
|
+
end
|
566
|
+
end
|
567
|
+
end
|
568
|
+
end
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
describe '#apply_traits' do
|
573
|
+
let(:root_data) { {
|
574
|
+
'title' => 'x',
|
575
|
+
'baseUri' => 'http://foo.com',
|
576
|
+
'/users' => {
|
577
|
+
'/comments' => {
|
578
|
+
'is' => resource_trait_data,
|
579
|
+
'get' => method_data
|
580
|
+
}
|
581
|
+
}
|
582
|
+
} }
|
583
|
+
let(:method) { root.resources['/users'].resources['/comments'].methods['get'] }
|
584
|
+
before { method.apply_traits }
|
585
|
+
describe 'order application' do
|
586
|
+
context 'when given no resource traits' do
|
587
|
+
let(:resource_trait_data) { [] }
|
588
|
+
context 'when method has a trait' do
|
589
|
+
let(:method_data) { { 'is' => [ { 'description' => 'trait description' } ] } }
|
590
|
+
it 'applies the method trait' do
|
591
|
+
method.description.should eq 'trait description'
|
592
|
+
end
|
593
|
+
end
|
594
|
+
context 'when the method has multiple traits' do
|
595
|
+
let(:method_data) { {
|
596
|
+
'is' => [
|
597
|
+
{
|
598
|
+
'description' => 'trait description',
|
599
|
+
'headers' => { 'header1' => { 'description' => 'header1' } }
|
600
|
+
},
|
601
|
+
{
|
602
|
+
'description' => 'trait description 2',
|
603
|
+
'headers' => { 'header2' => { 'description' => 'header2' } }
|
604
|
+
}
|
605
|
+
]
|
606
|
+
} }
|
607
|
+
it 'applies them in order of precedence, right to left' do
|
608
|
+
method.description.should eq 'trait description 2'
|
609
|
+
method.headers.keys.should contain_exactly('header1', 'header2')
|
610
|
+
end
|
611
|
+
end
|
612
|
+
end
|
613
|
+
context 'when given resource traits' do
|
614
|
+
let(:resource_trait_data) { [ { 'description' => 'resource trait description' } ] }
|
615
|
+
context 'when the method has no traits' do
|
616
|
+
let(:method_data) { {} }
|
617
|
+
it 'applies the resource trait' do
|
618
|
+
method.description.should eq 'resource trait description'
|
619
|
+
end
|
620
|
+
end
|
621
|
+
context 'when the method has traits' do
|
622
|
+
let(:resource_trait_data) {
|
623
|
+
[
|
624
|
+
{
|
625
|
+
'description' => 'trait4 description',
|
626
|
+
'headers' => {
|
627
|
+
'header3' => { 'description' => 'trait4' },
|
628
|
+
'header4' => { 'description' => 'trait4' }
|
629
|
+
}
|
630
|
+
},
|
631
|
+
{
|
632
|
+
'description' => 'trait3 description',
|
633
|
+
'headers' => {
|
634
|
+
'header2' => { 'description' => 'trait3' },
|
635
|
+
'header3' => { 'description' => 'trait3' }
|
636
|
+
}
|
637
|
+
}
|
638
|
+
]
|
639
|
+
}
|
640
|
+
let(:method_data) { {
|
641
|
+
'description' => 'method description',
|
642
|
+
'is' => [
|
643
|
+
{
|
644
|
+
'description' => 'trait2 description',
|
645
|
+
'headers' => {
|
646
|
+
'header1' => { 'description' => 'trait2' },
|
647
|
+
'header2' => { 'description' => 'trait2' }
|
648
|
+
}
|
649
|
+
},
|
650
|
+
{
|
651
|
+
'description' => 'trait1 description',
|
652
|
+
'headers' => {
|
653
|
+
'header1' => { 'description' => 'trait1' }
|
654
|
+
}
|
655
|
+
}
|
656
|
+
]
|
657
|
+
} }
|
658
|
+
it 'applies method traits first in reverse order, then resource traits in reverse order' do
|
659
|
+
method.description.should eq 'method description'
|
660
|
+
method.headers.keys.should contain_exactly('header1','header2', 'header3', 'header4')
|
661
|
+
method.headers['header1'].description.should eq 'trait1'
|
662
|
+
method.headers['header2'].description.should eq 'trait2'
|
663
|
+
method.headers['header3'].description.should eq 'trait3'
|
664
|
+
method.headers['header4'].description.should eq 'trait4'
|
665
|
+
end
|
666
|
+
end
|
667
|
+
end
|
668
|
+
end
|
669
|
+
describe 'reserved parameters' do
|
670
|
+
let(:resource_trait_data) { [] }
|
671
|
+
context 'resourcePath' do
|
672
|
+
let(:method_data) { { 'is' => [ { 'description' => 'trait <<resourcePath>>' } ] } }
|
673
|
+
it 'instances traits with the reserved parameter' do
|
674
|
+
method.description.should eq 'trait /users/comments'
|
675
|
+
end
|
676
|
+
end
|
677
|
+
context 'resourcePathName' do
|
678
|
+
let(:method_data) { { 'is' => [ { 'description' => 'trait <<resourcePathName>>' } ] } }
|
679
|
+
it 'instances traits with the reserved parameter' do
|
680
|
+
method.description.should eq 'trait comments'
|
681
|
+
end
|
682
|
+
end
|
683
|
+
context 'methodName' do
|
684
|
+
let(:method_data) { { 'is' => [ { 'description' => 'trait <<methodName>>' } ] } }
|
685
|
+
it 'instances traits with the reserved parameter' do
|
686
|
+
method.description.should eq 'trait get'
|
687
|
+
end
|
688
|
+
end
|
689
|
+
end
|
690
|
+
end
|
691
|
+
|
692
|
+
describe '#document' do
|
693
|
+
it 'returns a String' do
|
694
|
+
subject.document.should be_a String
|
695
|
+
end
|
696
|
+
it 'should render the template' do
|
697
|
+
mock(Slim::Template).new(/templates\/method.slim\z/, is_a(Hash)).mock!.render(is_a(Raml::Node)) { '' }
|
698
|
+
subject.document
|
699
|
+
end
|
700
|
+
end
|
701
|
+
end
|