praxis 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
|