praxis 0.14.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -1
- data/Gemfile +5 -0
- data/bin/praxis +7 -4
- data/lib/api_browser/package.json +1 -1
- data/lib/praxis/action_definition.rb +10 -5
- data/lib/praxis/application.rb +30 -0
- data/lib/praxis/bootloader_stages/subgroup_loader.rb +2 -2
- data/lib/praxis/bootloader_stages/warn_unloaded_files.rb +7 -2
- data/lib/praxis/file_group.rb +1 -1
- data/lib/praxis/handlers/json.rb +32 -0
- data/lib/praxis/handlers/www_form.rb +20 -0
- data/lib/praxis/handlers/xml.rb +78 -0
- data/lib/praxis/links.rb +4 -1
- data/lib/praxis/media_type.rb +55 -0
- data/lib/praxis/media_type_identifier.rb +198 -0
- data/lib/praxis/multipart/part.rb +16 -0
- data/lib/praxis/request.rb +34 -25
- data/lib/praxis/response.rb +22 -3
- data/lib/praxis/response_definition.rb +11 -36
- data/lib/praxis/restful_doc_generator.rb +1 -1
- data/lib/praxis/simple_media_type.rb +6 -1
- data/lib/praxis/types/media_type_common.rb +8 -3
- data/lib/praxis/types/multipart.rb +6 -6
- data/lib/praxis/version.rb +1 -1
- data/lib/praxis.rb +7 -1
- data/praxis.gemspec +1 -1
- data/spec/functional_spec.rb +3 -1
- data/spec/praxis/application_spec.rb +58 -1
- data/spec/praxis/handlers/json_spec.rb +37 -0
- data/spec/praxis/handlers/xml_spec.rb +155 -0
- data/spec/praxis/media_type_identifier_spec.rb +209 -0
- data/spec/praxis/media_type_spec.rb +50 -3
- data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +2 -2
- data/spec/praxis/request_spec.rb +39 -1
- data/spec/praxis/response_definition_spec.rb +12 -9
- data/spec/praxis/response_spec.rb +37 -6
- data/spec/praxis/types/collection_spec.rb +2 -2
- data/spec/praxis/types/multipart_spec.rb +17 -0
- data/spec/spec_app/app/controllers/instances.rb +1 -1
- data/spec/support/spec_media_types.rb +19 -0
- metadata +11 -6
- data/lib/praxis/content_type_parser.rb +0 -62
- data/spec/praxis/content_type_parser_spec.rb +0 -91
@@ -0,0 +1,209 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Praxis::MediaTypeIdentifier do
|
4
|
+
let(:example) { 'application/ice-cream+sundae; nuts="true"; fudge="true"' }
|
5
|
+
|
6
|
+
subject { described_class.new(example) }
|
7
|
+
|
8
|
+
context '.load' do
|
9
|
+
it 'parses type/subtype' do
|
10
|
+
expect(subject.type).to eq('application')
|
11
|
+
expect(subject.subtype).to eq('ice-cream')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'parses suffix' do
|
15
|
+
expect(subject.suffix).to eq('sundae')
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'given parameters' do
|
19
|
+
let(:example) { 'application/vnd.widget; encoding="martian"' }
|
20
|
+
let(:unquoted_example) { 'application/vnd.widget; encoding=martian' }
|
21
|
+
let(:tricky_example) { 'application/vnd.widget; sauce="yes; absolutely"' }
|
22
|
+
|
23
|
+
it 'handles quoted values' do
|
24
|
+
expect(described_class.new(example).parameters['encoding']).to eq('martian')
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'handles unquoted values' do
|
28
|
+
expect(described_class.new(unquoted_example).parameters['encoding']).to eq('martian')
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'handles quoted semicolons in values' do
|
32
|
+
pending("need to stop using a regexp to do a context-free parser's job")
|
33
|
+
expect(described_class.new(tricky_example).parameters['sauce']).to eq('yes; absolutely')
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'given a malformed type' do
|
40
|
+
it 'tolerates extra slash or plus' do
|
41
|
+
['man/bear/pig', 'c/c++', 'application/ice-cream+cone+dipped'].each do |eg|
|
42
|
+
expect(described_class.new(eg).to_s).to eq(eg)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'assumes application/XYZ' do
|
47
|
+
['nachos', 'tacos with extra cheese!'].each do |eg|
|
48
|
+
prefix = eg.split(/\s+/).first
|
49
|
+
expect(described_class.new(eg).to_s).to eq("application/#{prefix}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context '#match' do
|
56
|
+
subject { described_class }
|
57
|
+
|
58
|
+
# @example match anything
|
59
|
+
# MediaTypeIdentifier.load('*/*').match('application/icecream+cone; flavor=vanilla') # => true
|
60
|
+
#
|
61
|
+
# @example match a subtype wildcard
|
62
|
+
# MediaTypeIdentifier.load('image/*').match('image/jpeg') # => true
|
63
|
+
#
|
64
|
+
# @example match a specific type irrespective of structured syntax
|
65
|
+
# MediaTypeIdentifier.load('application/vnd.widget').match('application/vnd.widget+json') # => true
|
66
|
+
#
|
67
|
+
# @example match a specific type, respective of important parameters but irrespective of extra parameters or structured syntax
|
68
|
+
# MediaTypeIdentifier.load('application/vnd.widget; type=collection').match('application/vnd.widget+json; material=steel; type=collection') # => true
|
69
|
+
|
70
|
+
it 'accepts String' do
|
71
|
+
expect(subject.new('image/jpeg').match('image/jpeg')).to be_truthy
|
72
|
+
expect(subject.new('image/jpeg').match('image/png')).to be_falsey
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'accepts MediaTypeIdentifier' do
|
76
|
+
expect(subject.new('image/jpeg').match(subject.new('image/jpeg'))).to be_truthy
|
77
|
+
expect(subject.new('image/jpeg').match(subject.new('image/png'))).to be_falsey
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'understands type wildcards' do
|
81
|
+
expect(subject.new('*/*').match('application/pizza')).to be_truthy
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'understands subtype wildcards' do
|
85
|
+
expect(subject.new('application/*').match('application/pizza')).to be_truthy
|
86
|
+
expect(subject.new('application/*').match('image/jpeg')).to be_falsey
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'understands structured-syntax suffixes' do
|
90
|
+
expect(subject.new('application/vnd.widget').match('application/vnd.widget+json')).to be_truthy
|
91
|
+
expect(subject.new('application/vnd.widget+json').match('application/vnd.widget+xml')).to be_falsey
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'understands parameters' do
|
95
|
+
expect(subject.new('application/vnd.widget; type=collection').match('application/vnd.widget; type=collection; material=steel')).to be_truthy
|
96
|
+
expect(subject.new('application/vnd.widget+json; material=steel').match('application/vnd.widget+xml; material=copper')).to be_falsey
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context '#=~' do
|
101
|
+
subject { described_class.new('image/jpeg') }
|
102
|
+
it 'delegates to #match' do
|
103
|
+
expect(subject).to receive(:match).once
|
104
|
+
(described_class.new('image/jpeg') =~ subject)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context '#==' do
|
109
|
+
let(:example) { 'application/vnd.widget+xml; charset=UTF8' }
|
110
|
+
subject { described_class }
|
111
|
+
|
112
|
+
it 'compares all attributes' do
|
113
|
+
expect(subject.new('application/json')).not_to eq(subject.new('application/xml'))
|
114
|
+
expect(subject.new('application/vnd.widget+json')).not_to eq(subject.new('application/vnd.widget+xml'))
|
115
|
+
expect(subject.new('text/plain; charset=UTF8')).not_to eq(subject.new('text/plain; charset=martian'))
|
116
|
+
|
117
|
+
expect(subject.new(example)).to eq(subject.new(example))
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'ignores parameter ordering' do
|
121
|
+
expect(subject.new('text/plain; a=1; b=2')).to eq(subject.new('text/plain; b=2; a=1'))
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context '#to_s' do
|
126
|
+
subject { described_class.new(example) }
|
127
|
+
|
128
|
+
it 'includes type/subtype' do
|
129
|
+
expect(subject.to_s).to start_with('application/ice-cream')
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'includes suffix' do
|
133
|
+
expect(subject.to_s).to start_with('application/ice-cream+sundae')
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'canonicalizes parameter ordering' do
|
137
|
+
expect(subject.to_s).to end_with('; fudge=true; nuts=true')
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context '#without_parameters' do
|
142
|
+
it 'excludes parameters' do
|
143
|
+
expect(subject.without_parameters.to_s).to eq('application/ice-cream+sundae')
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context '#handler_name' do
|
148
|
+
subject { described_class }
|
149
|
+
|
150
|
+
let(:with_suffix) { 'application/vnd.widget+xml' }
|
151
|
+
let(:with_subtype) { 'text/xml' }
|
152
|
+
let(:with_both) { 'text/json+xml' } #nonsensical but valid!
|
153
|
+
|
154
|
+
it 'uses the suffix' do
|
155
|
+
expect(subject.new(with_suffix).handler_name).to eq('xml')
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'uses the subtype' do
|
159
|
+
expect(subject.new(with_subtype).handler_name).to eq('xml')
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'prefers the suffix' do
|
163
|
+
expect(subject.new(with_both).handler_name).to eq('xml')
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context '#+' do
|
168
|
+
let(:simple_subject) { described_class.new('application/vnd.icecream') }
|
169
|
+
let(:complex_subject) { described_class.new('application/vnd.icecream+json; nuts="true"') }
|
170
|
+
|
171
|
+
it 'adds a suffix' do
|
172
|
+
expect(simple_subject + 'xml').to \
|
173
|
+
eq(described_class.new('application/vnd.icecream+xml'))
|
174
|
+
expect(simple_subject + '+xml').to \
|
175
|
+
eq(described_class.new('application/vnd.icecream+xml'))
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'adds parameters' do
|
179
|
+
expect(simple_subject + 'nuts=true').to \
|
180
|
+
eq(described_class.new('application/vnd.icecream; nuts=true'))
|
181
|
+
|
182
|
+
expect(simple_subject + '; nuts=true').to \
|
183
|
+
eq(described_class.new('application/vnd.icecream; nuts=true'))
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'adds suffix and parameters' do
|
187
|
+
expect(simple_subject + 'xml; nuts=true').to \
|
188
|
+
eq(described_class.new('application/vnd.icecream+xml; nuts=true'))
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'replaces the suffix' do
|
192
|
+
expect(complex_subject + 'xml').to \
|
193
|
+
eq(described_class.new('application/vnd.icecream+xml; nuts=true'))
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'replaces existing parameters and adds new ones' do
|
197
|
+
expect(complex_subject + 'nuts=false; cherry=true').to \
|
198
|
+
eq(described_class.new('application/vnd.icecream+json; cherry=true; nuts=false'))
|
199
|
+
|
200
|
+
expect(complex_subject + '; nuts=false; cherry=true').to \
|
201
|
+
eq(described_class.new('application/vnd.icecream+json; cherry=true; nuts=false'))
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'replaces suffix and parameters and adds new ones' do
|
205
|
+
expect(complex_subject + 'json; nuts=false; cherry=true').to \
|
206
|
+
eq(described_class.new('application/vnd.icecream+json; cherry=true; nuts=false'))
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -3,9 +3,20 @@ require 'spec_helper'
|
|
3
3
|
describe Praxis::MediaType do
|
4
4
|
let(:owner_resource) { instance_double(Person, id: 100, name: /[:name:]/.gen, href: '/', links: ['one','two']) }
|
5
5
|
let(:manager_resource) { instance_double(Person, id: 101, name: /[:name:]/.gen, href: '/', links: []) }
|
6
|
+
let(:custodian_resource) { instance_double(Person, id: 102, name: /[:name:]/.gen, href: '/', links: []) }
|
7
|
+
let(:residents_summary_resource) do
|
8
|
+
instance_double(Person::CollectionSummary, href: "/people", size: 2)
|
9
|
+
end
|
6
10
|
|
7
11
|
let(:resource) do
|
8
|
-
double('address',
|
12
|
+
double('address',
|
13
|
+
id: 1,
|
14
|
+
name: 'Home',
|
15
|
+
owner: owner_resource,
|
16
|
+
manager: manager_resource,
|
17
|
+
custodian: custodian_resource,
|
18
|
+
residents_summary: residents_summary_resource
|
19
|
+
)
|
9
20
|
end
|
10
21
|
|
11
22
|
subject(:address) { Address.new(resource) }
|
@@ -20,7 +31,9 @@ describe Praxis::MediaType do
|
|
20
31
|
it 'respects using' do
|
21
32
|
expect(address.links.super).to be_kind_of(Person)
|
22
33
|
expect(address.links.super.object).to be(resource.manager)
|
34
|
+
expect(address.links.caretaker.object).to be(resource.custodian)
|
23
35
|
end
|
36
|
+
|
24
37
|
end
|
25
38
|
end
|
26
39
|
|
@@ -29,7 +42,25 @@ describe Praxis::MediaType do
|
|
29
42
|
context 'accessor methods' do
|
30
43
|
subject(:address_klass) { address.class }
|
31
44
|
|
32
|
-
|
45
|
+
context '#identifier' do
|
46
|
+
context 'in praxis v0' do
|
47
|
+
it 'should be a kind of String' do
|
48
|
+
if Praxis::VERSION =~ /^0/
|
49
|
+
expect(subject.identifier).to be_kind_of(String)
|
50
|
+
else
|
51
|
+
raise 'Please remove this spec which is no longer pertinent'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'in praxis v1.0 and beyond' do
|
57
|
+
it 'should be a kind of Praxis::MediaTypeIdentifier' do
|
58
|
+
pending('interface-breaking change') if Praxis::VERSION =~ /^0/
|
59
|
+
expect(subject.identifier).to be_kind_of(Praxis::MediaTypeIdentifier)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
33
64
|
its(:description) { should be_kind_of(String) }
|
34
65
|
|
35
66
|
context 'links' do
|
@@ -43,8 +74,24 @@ describe Praxis::MediaType do
|
|
43
74
|
context 'using the links DSL' do
|
44
75
|
subject(:address) { Address.new(resource) }
|
45
76
|
its(:links) { should be_kind_of(Address::Links) }
|
77
|
+
|
78
|
+
it 'inherits types appropriately' do
|
79
|
+
links_attribute = Address::Links.attributes
|
80
|
+
expect(links_attribute[:owner].type).to be(Person)
|
81
|
+
expect(links_attribute[:super].type).to be(Person)
|
82
|
+
expect(links_attribute[:caretaker].type).to be(Person)
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'loading returned values' do
|
86
|
+
subject(:residents) { address.links.residents }
|
87
|
+
let(:residents_summary_resource) do
|
88
|
+
{href: "/people", size: 2}
|
89
|
+
end
|
90
|
+
|
91
|
+
its(:href) { should eq('/people') }
|
92
|
+
its(:size) { should eq(2) }
|
93
|
+
end
|
46
94
|
end
|
47
|
-
|
48
95
|
end
|
49
96
|
end
|
50
97
|
|
@@ -55,8 +55,8 @@ describe Praxis::Plugins::PraxisMapperPlugin do
|
|
55
55
|
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:log).
|
56
56
|
with(kind_of(Praxis::Request),kind_of(Praxis::Mapper::IdentityMap), 'detailed').
|
57
57
|
and_call_original
|
58
|
-
|
59
|
-
get '/clouds/1/instances/2?junk=foo&api_version=1.0', nil, 'global_session' => session
|
58
|
+
the_body = StringIO.new("{}") # This is a funny, GET request expecting a body
|
59
|
+
get '/clouds/1/instances/2?junk=foo&api_version=1.0', nil, 'rack.input' => the_body,'CONTENT_TYPE' => "application/json", 'global_session' => session
|
60
60
|
|
61
61
|
expect(last_response.status).to eq(200)
|
62
62
|
end
|
data/spec/praxis/request_spec.rb
CHANGED
@@ -2,10 +2,11 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Praxis::Request do
|
4
4
|
let(:rack_input) { StringIO.new('something=given') }
|
5
|
+
let(:env_content_type){ 'application/x-www-form-urlencoded' }
|
5
6
|
let(:env) do
|
6
7
|
env = Rack::MockRequest.env_for('/instances/1?junk=foo&api_version=1.0')
|
7
8
|
env['rack.input'] = rack_input
|
8
|
-
env['CONTENT_TYPE'] =
|
9
|
+
env['CONTENT_TYPE'] = env_content_type
|
9
10
|
env['HTTP_VERSION'] = 'HTTP/1.1'
|
10
11
|
env['HTTP_AUTHORIZATION'] = 'Secret'
|
11
12
|
env
|
@@ -174,5 +175,42 @@ describe Praxis::Request do
|
|
174
175
|
expect(request.validate_payload(context[:payload])).to eq(['some_error'])
|
175
176
|
end
|
176
177
|
end
|
178
|
+
|
179
|
+
context '#load_payload' do
|
180
|
+
let(:load_context){ context[:payload] }
|
181
|
+
let(:parsed_result){ double("parsed") }
|
182
|
+
|
183
|
+
before do
|
184
|
+
Praxis::Application.instance.handler 'xml', Praxis::Handlers::XML
|
185
|
+
end
|
186
|
+
|
187
|
+
after do
|
188
|
+
expect(request.action.payload).to receive(:load).with(parsed_result, load_context, content_type: request.content_type.to_s )
|
189
|
+
request.load_payload( load_context )
|
190
|
+
end
|
191
|
+
|
192
|
+
context 'that is url-encoded' do
|
193
|
+
let(:env_content_type){ 'application/x-www-form-urlencoded' }
|
194
|
+
let(:parsed_result) { {"something" => "given"} }
|
195
|
+
|
196
|
+
it 'decodes it the www-form handler' do end
|
197
|
+
end
|
198
|
+
|
199
|
+
context 'that is json encoded' do
|
200
|
+
let(:rack_input) { StringIO.new('{"one":1,"sub_hash":{"first":"hello"}}') }
|
201
|
+
let(:env_content_type){ 'application/json' }
|
202
|
+
let(:parsed_result) { Praxis::Handlers::JSON.new.parse(request.raw_payload) }
|
203
|
+
|
204
|
+
it 'decodes using the JSON handler' do end
|
205
|
+
end
|
206
|
+
context 'that is xml encoded' do
|
207
|
+
let(:rack_input) { StringIO.new('<hash><one type="integer">1</one><sub_hash><first>hello</first></sub_hash></hash>') }
|
208
|
+
let(:env_content_type) { 'application/xml' }
|
209
|
+
let(:parsed_result) { Praxis::Handlers::XML.new.parse(request.raw_payload) }
|
210
|
+
|
211
|
+
it 'decodes using the XML handler' do end
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
177
215
|
end
|
178
216
|
end
|
@@ -8,7 +8,7 @@ describe Praxis::ResponseDefinition do
|
|
8
8
|
Proc.new do
|
9
9
|
status 200
|
10
10
|
description 'test description'
|
11
|
-
headers({ "X-Header" => "value", "Content-Type" => "
|
11
|
+
headers({ "X-Header" => "value", "Content-Type" => "application/some-type" })
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
@@ -16,9 +16,10 @@ describe Praxis::ResponseDefinition do
|
|
16
16
|
its(:description) { should == 'test description' }
|
17
17
|
its(:parts) { should be(nil) }
|
18
18
|
let(:response_status) { 200 }
|
19
|
-
let(:
|
19
|
+
let(:response_content_type) { "application/some-type" }
|
20
|
+
let(:response_headers) { { "X-Header" => "value", "Content-Type" => response_content_type} }
|
20
21
|
|
21
|
-
let(:response) { instance_double("Praxis::Response", status: response_status , headers: response_headers ) }
|
22
|
+
let(:response) { instance_double("Praxis::Response", status: response_status , headers: response_headers, content_type: response_content_type ) }
|
22
23
|
|
23
24
|
|
24
25
|
context '#media_type' do
|
@@ -66,14 +67,14 @@ describe Praxis::ResponseDefinition do
|
|
66
67
|
|
67
68
|
context '#headers' do
|
68
69
|
it 'accepts a Hash' do
|
69
|
-
response_definition.headers Hash["X-Header" => "value", "Content-Type" => "
|
70
|
+
response_definition.headers Hash["X-Header" => "value", "Content-Type" => "application/some-type"]
|
70
71
|
expect(response_definition.headers).to be_a(Hash)
|
71
72
|
end
|
72
73
|
|
73
74
|
it 'accepts an Array' do
|
74
|
-
response_definition.headers ["X-Header: value", "Content-Type:
|
75
|
+
response_definition.headers ["X-Header: value", "Content-Type: application/some-type"]
|
75
76
|
expect(response_definition.headers).to be_a(Hash)
|
76
|
-
expect(response_definition.headers.keys).to include("X-Header: value", "Content-Type:
|
77
|
+
expect(response_definition.headers.keys).to include("X-Header: value", "Content-Type: application/some-type")
|
77
78
|
end
|
78
79
|
|
79
80
|
it 'accepts a String' do
|
@@ -364,7 +365,8 @@ describe Praxis::ResponseDefinition do
|
|
364
365
|
before { response_definition.media_type(media_type) }
|
365
366
|
|
366
367
|
context 'when content type matches the mediatype of the spec' do
|
367
|
-
let(:
|
368
|
+
let(:response_content_type) { content_type }
|
369
|
+
|
368
370
|
it 'validates successfully' do
|
369
371
|
expect {
|
370
372
|
response_definition.validate_content_type!(response)
|
@@ -373,7 +375,7 @@ describe Praxis::ResponseDefinition do
|
|
373
375
|
end
|
374
376
|
|
375
377
|
context 'when content type includes a parameter' do
|
376
|
-
let(:
|
378
|
+
let(:response_content_type) { "#{content_type}; collection=true" }
|
377
379
|
it 'validates successfully' do
|
378
380
|
expect {
|
379
381
|
response_definition.validate_content_type!(response)
|
@@ -382,7 +384,8 @@ describe Praxis::ResponseDefinition do
|
|
382
384
|
end
|
383
385
|
|
384
386
|
context 'when content type does not match' do
|
385
|
-
let(:
|
387
|
+
let(:response_content_type) { "application/will_never_match" }
|
388
|
+
|
386
389
|
it 'should raise error telling you so' do
|
387
390
|
expect {
|
388
391
|
response_definition.validate_content_type!(response)
|
@@ -50,12 +50,6 @@ describe Praxis::Response do
|
|
50
50
|
|
51
51
|
subject(:response) { Praxis::Responses::Ok.new(status: response_status, headers: response_headers) }
|
52
52
|
|
53
|
-
# before :each do
|
54
|
-
# allow(response.class).to receive(:response_name).and_return(:spec)
|
55
|
-
# #allow(response).to receive(:headers).and_return(response_headers)
|
56
|
-
# #allow(response).to receive(:status).and_return(response_status)
|
57
|
-
# end
|
58
|
-
|
59
53
|
describe '#validate' do
|
60
54
|
before do
|
61
55
|
allow(action).to receive(:responses).and_return({response_spec.name => response_spec })
|
@@ -84,6 +78,43 @@ describe Praxis::Response do
|
|
84
78
|
|
85
79
|
end
|
86
80
|
|
81
|
+
describe '#encode!' do
|
82
|
+
let(:response_body_structured_data) { [{"moo" => "bah"}] }
|
83
|
+
let(:response_body_entity) { '[{"moo": "bah"}]' }
|
84
|
+
|
85
|
+
context 'given a String body' do
|
86
|
+
before { response.body = response_body_entity }
|
87
|
+
|
88
|
+
it 'leaves well enough alone' do
|
89
|
+
response.encode!
|
90
|
+
expect(response.body).to eq(response_body_entity)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'given a structured body' do
|
95
|
+
before { response.body = response_body_structured_data }
|
96
|
+
|
97
|
+
it 'encodes using a suitable handler' do
|
98
|
+
response.encode!
|
99
|
+
expect(JSON.load(response.body)).to eq(response_body_structured_data)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'when no Content-Type is defined' do
|
104
|
+
before { response.body = response_body_structured_data }
|
105
|
+
|
106
|
+
let(:response_headers) {
|
107
|
+
{ 'X-Header' => 'Foobar',
|
108
|
+
'Location' => '/api/resources/123' }
|
109
|
+
}
|
110
|
+
it 'still defaults to the default handler' do
|
111
|
+
response.encode!
|
112
|
+
expect(JSON.load(response.body)).to eq(response_body_structured_data)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
end
|
87
118
|
|
88
119
|
context 'multipart responses' do
|
89
120
|
let(:part) { Praxis::MultipartPart.new('not so ok', {'Status' => 400, "Location" => "somewhere"}) }
|
@@ -14,7 +14,7 @@ describe Praxis::Collection do
|
|
14
14
|
context '.of' do
|
15
15
|
let(:media_type) do
|
16
16
|
Class.new(Praxis::MediaType) do
|
17
|
-
identifier 'an-awesome-type'
|
17
|
+
identifier 'application/an-awesome-type'
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -22,7 +22,7 @@ describe Praxis::Collection do
|
|
22
22
|
Praxis::Collection.of(media_type)
|
23
23
|
end
|
24
24
|
|
25
|
-
its(:identifier) { should eq 'an-awesome-type;type=collection' }
|
25
|
+
its(:identifier) { should eq 'application/an-awesome-type; type=collection' }
|
26
26
|
|
27
27
|
it 'sets the collection on the media type' do
|
28
28
|
expect(media_type::Collection).to be(collection)
|
@@ -78,4 +78,21 @@ describe Praxis::Multipart do
|
|
78
78
|
|
79
79
|
end
|
80
80
|
|
81
|
+
context '.load' do
|
82
|
+
it 'returns nil when loading nil' do
|
83
|
+
expect(type.load(nil)).to be nil
|
84
|
+
end
|
85
|
+
it 'returns the same value when loading an instance of a Multipart class' do
|
86
|
+
instance = Praxis::Multipart.example
|
87
|
+
expect(type.load(instance)).to be instance
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'complains when the value is not a String (besides a Multipart instance or nil)' do
|
91
|
+
expect{
|
92
|
+
type.load( {a: "hash"} )
|
93
|
+
}.to raise_error(Attributor::CoercionError)
|
94
|
+
end
|
95
|
+
|
96
|
+
pending 'complete the load tests'
|
97
|
+
end
|
81
98
|
end
|
@@ -46,7 +46,7 @@ class Instances < BaseClass
|
|
46
46
|
|
47
47
|
def show(cloud_id:, id:, junk:, **other_params)
|
48
48
|
payload = request.payload
|
49
|
-
response.body = {cloud_id: cloud_id, id: id, junk: junk, other_params: other_params, payload: payload.dump}
|
49
|
+
response.body = {cloud_id: cloud_id, id: id, junk: junk, other_params: other_params, payload: payload && payload.dump}
|
50
50
|
response.headers['Content-Type'] = 'application/vnd.acme.instance'
|
51
51
|
response
|
52
52
|
end
|
@@ -17,6 +17,19 @@ class Person < Praxis::MediaType
|
|
17
17
|
attribute :name
|
18
18
|
attribute :href
|
19
19
|
end
|
20
|
+
|
21
|
+
class CollectionSummary < Praxis::MediaType
|
22
|
+
attributes do
|
23
|
+
attribute :href, String
|
24
|
+
attribute :size, Integer
|
25
|
+
end
|
26
|
+
|
27
|
+
view :link do
|
28
|
+
attribute :href
|
29
|
+
attribute :size
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
20
33
|
end
|
21
34
|
|
22
35
|
|
@@ -30,10 +43,16 @@ class Address < Praxis::MediaType
|
|
30
43
|
attribute :name, String
|
31
44
|
|
32
45
|
attribute :owner, Person
|
46
|
+
attribute :custodian, Person
|
47
|
+
|
48
|
+
attribute :residents, Praxis::Collection.of(Person)
|
49
|
+
attribute :residents_summary, Person::CollectionSummary
|
33
50
|
|
34
51
|
links do
|
35
52
|
link :owner
|
36
53
|
link :super, Person, using: :manager
|
54
|
+
link :caretaker, using: :custodian
|
55
|
+
link :residents, using: :residents_summary
|
37
56
|
end
|
38
57
|
|
39
58
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: praxis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josep M. Blanquer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-
|
12
|
+
date: 2015-04-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
@@ -101,14 +101,14 @@ dependencies:
|
|
101
101
|
requirements:
|
102
102
|
- - "~>"
|
103
103
|
- !ruby/object:Gem::Version
|
104
|
-
version: 2.6
|
104
|
+
version: '2.6'
|
105
105
|
type: :runtime
|
106
106
|
prerelease: false
|
107
107
|
version_requirements: !ruby/object:Gem::Requirement
|
108
108
|
requirements:
|
109
109
|
- - "~>"
|
110
110
|
- !ruby/object:Gem::Version
|
111
|
-
version: 2.6
|
111
|
+
version: '2.6'
|
112
112
|
- !ruby/object:Gem::Dependency
|
113
113
|
name: thor
|
114
114
|
requirement: !ruby/object:Gem::Requirement
|
@@ -834,7 +834,6 @@ files:
|
|
834
834
|
- lib/praxis/callbacks.rb
|
835
835
|
- lib/praxis/collection.rb
|
836
836
|
- lib/praxis/config.rb
|
837
|
-
- lib/praxis/content_type_parser.rb
|
838
837
|
- lib/praxis/controller.rb
|
839
838
|
- lib/praxis/dispatcher.rb
|
840
839
|
- lib/praxis/error_handler.rb
|
@@ -848,9 +847,13 @@ files:
|
|
848
847
|
- lib/praxis/exceptions/stage_not_found.rb
|
849
848
|
- lib/praxis/exceptions/validation.rb
|
850
849
|
- lib/praxis/file_group.rb
|
850
|
+
- lib/praxis/handlers/json.rb
|
851
|
+
- lib/praxis/handlers/www_form.rb
|
852
|
+
- lib/praxis/handlers/xml.rb
|
851
853
|
- lib/praxis/links.rb
|
852
854
|
- lib/praxis/media_type.rb
|
853
855
|
- lib/praxis/media_type_collection.rb
|
856
|
+
- lib/praxis/media_type_identifier.rb
|
854
857
|
- lib/praxis/multipart/parser.rb
|
855
858
|
- lib/praxis/multipart/part.rb
|
856
859
|
- lib/praxis/notifications.rb
|
@@ -899,12 +902,14 @@ files:
|
|
899
902
|
- spec/praxis/bootloader_spec.rb
|
900
903
|
- spec/praxis/callbacks_spec.rb
|
901
904
|
- spec/praxis/config_spec.rb
|
902
|
-
- spec/praxis/content_type_parser_spec.rb
|
903
905
|
- spec/praxis/controller_spec.rb
|
904
906
|
- spec/praxis/dispatcher_spec.rb
|
905
907
|
- spec/praxis/file_group_spec.rb
|
908
|
+
- spec/praxis/handlers/json_spec.rb
|
909
|
+
- spec/praxis/handlers/xml_spec.rb
|
906
910
|
- spec/praxis/links_spec.rb
|
907
911
|
- spec/praxis/media_type_collection_spec.rb
|
912
|
+
- spec/praxis/media_type_identifier_spec.rb
|
908
913
|
- spec/praxis/media_type_spec.rb
|
909
914
|
- spec/praxis/multipart/parser_spec.rb
|
910
915
|
- spec/praxis/notifications_spec.rb
|