praxis 0.20.1 → 0.21
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/.ruby-version +1 -1
- data/.travis.yml +2 -2
- data/CHANGELOG.md +36 -0
- data/lib/api_browser/Gruntfile.js +33 -3
- data/lib/api_browser/app/index.html +3 -0
- data/lib/api_browser/app/js/factories/Example.js +4 -0
- data/lib/api_browser/app/js/factories/template_for.js +14 -0
- data/lib/api_browser/app/js/filters/attribute_name.js +3 -2
- data/lib/api_browser/app/js/filters/header_info.js +9 -0
- data/lib/api_browser/app/views/action.html +11 -2
- data/lib/api_browser/app/views/controller.html +5 -5
- data/lib/api_browser/app/views/menu.html +1 -1
- data/lib/api_browser/app/views/type.html +4 -23
- data/lib/api_browser/app/views/type/details.html +7 -4
- data/lib/api_browser/app/views/types/main/array.html +22 -0
- data/lib/api_browser/app/views/types/main/default.html +23 -0
- data/lib/api_browser/app/views/types/main/hash.html +23 -0
- data/lib/praxis.rb +2 -0
- data/lib/praxis/action_definition.rb +0 -8
- data/lib/praxis/api_definition.rb +11 -6
- data/lib/praxis/api_general_info.rb +13 -0
- data/lib/praxis/bootloader.rb +11 -5
- data/lib/praxis/docs/generator.rb +31 -11
- data/lib/praxis/docs/link_builder.rb +30 -0
- data/lib/praxis/extensions/field_expansion.rb +2 -2
- data/lib/praxis/media_type.rb +1 -1
- data/lib/praxis/middleware_app.rb +30 -0
- data/lib/praxis/resource_definition.rb +24 -2
- data/lib/praxis/response.rb +2 -1
- data/lib/praxis/response_definition.rb +2 -2
- data/lib/praxis/responses/http.rb +28 -92
- data/lib/praxis/responses/validation_error.rb +4 -1
- data/lib/praxis/tasks/api_docs.rb +11 -24
- data/lib/praxis/trait.rb +12 -7
- data/lib/praxis/validation_handler.rb +2 -1
- data/lib/praxis/version.rb +1 -1
- data/praxis.gemspec +11 -7
- data/spec/api_browser/filters/attribute_name_spec.js +2 -2
- data/spec/praxis/action_definition_spec.rb +23 -1
- data/spec/praxis/bootloader_spec.rb +28 -0
- data/spec/praxis/extensions/field_expansion_spec.rb +10 -0
- data/spec/praxis/middleware_app_spec.rb +55 -0
- data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +8 -3
- data/spec/praxis/resource_definition_spec.rb +51 -2
- data/spec/praxis/response_definition_spec.rb +16 -4
- data/spec/praxis/response_spec.rb +1 -1
- data/spec/praxis/trait_spec.rb +13 -0
- data/spec/spec_app/config/environment.rb +11 -1
- metadata +30 -25
- data/lib/praxis/restful_doc_generator.rb +0 -439
data/lib/praxis/trait.rb
CHANGED
@@ -12,7 +12,7 @@ module Praxis
|
|
12
12
|
@other = []
|
13
13
|
|
14
14
|
@attribute_groups = Hash.new do |h,k|
|
15
|
-
h[k] =
|
15
|
+
h[k] = []
|
16
16
|
end
|
17
17
|
|
18
18
|
if block_given?
|
@@ -34,7 +34,7 @@ module Praxis
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def create_group(name, &block)
|
37
|
-
@attribute_groups[name]
|
37
|
+
@attribute_groups[name] << block
|
38
38
|
end
|
39
39
|
|
40
40
|
def headers(*args, &block)
|
@@ -68,7 +68,7 @@ module Praxis
|
|
68
68
|
desc[:routing] = ConfigHash.new(&@routing).to_hash
|
69
69
|
end
|
70
70
|
|
71
|
-
@attribute_groups.each_with_object(desc) do |(name,
|
71
|
+
@attribute_groups.each_with_object(desc) do |(name, blocks), hash|
|
72
72
|
type_class = if name == :headers
|
73
73
|
# Headers are special:
|
74
74
|
# Keys are strings, they have a special DSL, and are case insensitive
|
@@ -76,9 +76,12 @@ module Praxis
|
|
76
76
|
dsl_compiler: ActionDefinition::HeadersDSLCompiler,
|
77
77
|
case_insensitive_load: true
|
78
78
|
}
|
79
|
-
Attributor::Hash.of(key: String).construct(
|
79
|
+
Attributor::Hash.of(key: String).construct(Proc.new {}, hash_opts)
|
80
80
|
else
|
81
|
-
Attributor::Hash.construct(
|
81
|
+
Attributor::Hash.construct(Proc.new {})
|
82
|
+
end
|
83
|
+
blocks.each do |block|
|
84
|
+
type_class.construct(block)
|
82
85
|
end
|
83
86
|
hash[name] = type_class.describe[:attributes]
|
84
87
|
end
|
@@ -88,8 +91,10 @@ module Praxis
|
|
88
91
|
|
89
92
|
|
90
93
|
def apply!(target)
|
91
|
-
@attribute_groups.each do |name,
|
92
|
-
|
94
|
+
@attribute_groups.each do |name, blocks|
|
95
|
+
blocks.each do |block|
|
96
|
+
target.send(name, &block)
|
97
|
+
end
|
93
98
|
end
|
94
99
|
|
95
100
|
if @routing
|
@@ -3,7 +3,8 @@ module Praxis
|
|
3
3
|
|
4
4
|
# Should return the Response to send back
|
5
5
|
def handle!(summary:, request:, stage:, errors: nil, exception: nil, **opts)
|
6
|
-
|
6
|
+
documentation = Docs::LinkBuilder.instance.for_request request
|
7
|
+
Responses::ValidationError.new(summary: summary, errors: errors, exception: exception, documentation: documentation, **opts)
|
7
8
|
end
|
8
9
|
|
9
10
|
end
|
data/lib/praxis/version.rb
CHANGED
data/praxis.gemspec
CHANGED
@@ -24,9 +24,9 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_dependency 'mustermann', '~> 0'
|
25
25
|
spec.add_dependency 'activesupport', '>= 3'
|
26
26
|
spec.add_dependency 'mime', '~> 0'
|
27
|
-
spec.add_dependency 'praxis-mapper', '~> 4.
|
28
|
-
spec.add_dependency 'praxis-blueprints', '~> 3.
|
29
|
-
spec.add_dependency 'attributor', '~> 5.
|
27
|
+
spec.add_dependency 'praxis-mapper', '~> 4.3'
|
28
|
+
spec.add_dependency 'praxis-blueprints', '~> 3.3'
|
29
|
+
spec.add_dependency 'attributor', '~> 5.1'
|
30
30
|
spec.add_dependency 'thor', '~> 0.18'
|
31
31
|
spec.add_dependency 'terminal-table', '~> 1.4'
|
32
32
|
spec.add_dependency 'harness', '~> 2'
|
@@ -34,9 +34,14 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
35
35
|
spec.add_development_dependency 'rake', '~> 0.9'
|
36
36
|
spec.add_development_dependency 'rake-notes', '~> 0'
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
if RUBY_PLATFORM !~ /java/
|
38
|
+
spec.add_development_dependency 'pry', '~> 0'
|
39
|
+
spec.add_development_dependency 'pry-byebug', '~> 1'
|
40
|
+
spec.add_development_dependency 'pry-stack_explorer', '~> 0'
|
41
|
+
spec.add_development_dependency 'sqlite3', '~> 1'
|
42
|
+
else
|
43
|
+
spec.add_development_dependency 'jdbc-sqlite3'
|
44
|
+
end
|
40
45
|
spec.add_development_dependency 'rspec', '~> 3'
|
41
46
|
spec.add_development_dependency 'rspec-its', '~> 1'
|
42
47
|
spec.add_development_dependency 'rspec-collection_matchers', '~> 1'
|
@@ -47,6 +52,5 @@ Gem::Specification.new do |spec|
|
|
47
52
|
spec.add_development_dependency 'simplecov', '~> 0'
|
48
53
|
spec.add_development_dependency 'fuubar', '~> 2'
|
49
54
|
spec.add_development_dependency 'yard', '~> 0'
|
50
|
-
spec.add_development_dependency 'sqlite3', '~> 1'
|
51
55
|
spec.add_development_dependency 'coveralls'
|
52
56
|
end
|
@@ -8,10 +8,10 @@ describe('attributeName filter', function() {
|
|
8
8
|
};
|
9
9
|
}));
|
10
10
|
|
11
|
-
it('only modifies input with one
|
11
|
+
it('only modifies input with one or more dots', function() {
|
12
12
|
expect(function(str) {
|
13
13
|
var dotsCount = str.split('.').length - 1;
|
14
|
-
if (dotsCount
|
14
|
+
if (dotsCount === 0) {
|
15
15
|
return str === filter(str);
|
16
16
|
}
|
17
17
|
}).forAll(qc.string);
|
@@ -2,10 +2,15 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Praxis::ActionDefinition do
|
4
4
|
class SpecMediaType < Praxis::MediaType
|
5
|
+
identifier 'application/json'
|
6
|
+
|
5
7
|
attributes do
|
6
8
|
attribute :one, String
|
7
9
|
attribute :two, Integer
|
8
10
|
end
|
11
|
+
view :default do
|
12
|
+
attribute :one
|
13
|
+
end
|
9
14
|
end
|
10
15
|
|
11
16
|
let(:resource_definition) do
|
@@ -28,11 +33,22 @@ describe Praxis::ActionDefinition do
|
|
28
33
|
end
|
29
34
|
|
30
35
|
subject(:action) do
|
36
|
+
|
37
|
+
Praxis::ApiDefinition.define do |api|
|
38
|
+
api.response_template :ok do |media_type: , location: nil, headers: nil, description: nil |
|
39
|
+
status 200
|
40
|
+
|
41
|
+
media_type media_type
|
42
|
+
location location
|
43
|
+
headers headers if headers
|
44
|
+
end
|
45
|
+
end
|
31
46
|
Praxis::ActionDefinition.new(:foo, resource_definition) do
|
32
47
|
routing { get '/:one' }
|
33
48
|
payload { attribute :two, String }
|
34
49
|
headers { header "X_REQUESTED_WITH", 'XMLHttpRequest' }
|
35
50
|
params { attribute :one, String }
|
51
|
+
response :ok, headers: { "Foo" => "Bar"}, location: %r{/some/thing}
|
36
52
|
end
|
37
53
|
end
|
38
54
|
|
@@ -53,11 +69,13 @@ describe Praxis::ActionDefinition do
|
|
53
69
|
before do
|
54
70
|
action.response :ok
|
55
71
|
action.response :internal_server_error
|
72
|
+
action.response :created, location: 'foobar'
|
56
73
|
end
|
57
74
|
|
58
75
|
it { should be_kind_of Hash }
|
59
76
|
it { should include :ok }
|
60
77
|
it { should include :internal_server_error }
|
78
|
+
it { should include :created }
|
61
79
|
end
|
62
80
|
|
63
81
|
describe '#allowed_responses' do
|
@@ -252,6 +270,10 @@ describe Praxis::ActionDefinition do
|
|
252
270
|
end
|
253
271
|
end
|
254
272
|
|
273
|
+
context 'responses' do
|
274
|
+
subject(:response_description) { describe[:responses] }
|
275
|
+
its(:keys) { should include(:ok) }
|
276
|
+
end
|
255
277
|
end
|
256
278
|
|
257
279
|
context 'href generation' do
|
@@ -318,7 +340,7 @@ describe Praxis::ActionDefinition do
|
|
318
340
|
end
|
319
341
|
|
320
342
|
its('routes.first.path.to_s') { should eq '/apps/:app_name/foobars/hello_world/:one' }
|
321
|
-
its('params.attributes.keys') { should
|
343
|
+
its('params.attributes.keys') { should match_array [:inherited, :app_name, :one]}
|
322
344
|
|
323
345
|
context 'where the action overrides a base_param' do
|
324
346
|
|
@@ -70,5 +70,33 @@ describe Praxis::Bootloader do
|
|
70
70
|
bootloader.use(plugin)
|
71
71
|
expect(bootloader.application.plugins[:foo].class).to be(plugin)
|
72
72
|
end
|
73
|
+
|
74
|
+
it "complains if a plugin with same name already registered" do
|
75
|
+
bootloader.use(plugin)
|
76
|
+
expect do
|
77
|
+
bootloader.use(plugin)
|
78
|
+
end.to raise_error /another plugin (.*) is already registered with key: foo/
|
79
|
+
end
|
80
|
+
context "defaults config_key" do
|
81
|
+
let(:plugin_two) do
|
82
|
+
Class.new(Praxis::Plugin) do
|
83
|
+
def self.name
|
84
|
+
"Two" # Need this to avoid creating a true named class.
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
it "to the class name" do
|
90
|
+
bootloader.use(plugin_two)
|
91
|
+
expect(bootloader.application.plugins[:two].class).to be(plugin_two)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'but raises if class is anonymous' do
|
95
|
+
plugin_anon = Class.new(Praxis::Plugin) {}
|
96
|
+
expect do
|
97
|
+
bootloader.use(plugin_anon)
|
98
|
+
end.to raise_error(/It does not have a config_key defined, and its class does not have a name/)
|
99
|
+
end
|
100
|
+
end
|
73
101
|
end
|
74
102
|
end
|
@@ -91,6 +91,16 @@ describe Praxis::Extensions::FieldExpansion do
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
end
|
94
|
+
|
95
|
+
|
96
|
+
context 'with an action with no params' do
|
97
|
+
let(:test_params) { nil }
|
98
|
+
let(:fields){ nil }
|
99
|
+
let(:view){ nil }
|
100
|
+
it 'ignores incoming parameters and expands for the default view' do
|
101
|
+
expect(expansion).to eq({id: true, name: true, links: [true]})
|
102
|
+
end
|
103
|
+
end
|
94
104
|
end
|
95
105
|
|
96
106
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Praxis::MiddlewareApp do
|
4
|
+
|
5
|
+
context '.for' do
|
6
|
+
let(:init_args){ { root: 'here'} }
|
7
|
+
subject(:middleware) { Praxis::MiddlewareApp.for( init_args ) }
|
8
|
+
|
9
|
+
it 'initializes the application singletone with the passed parameters' do
|
10
|
+
expect( Praxis::Application.instance ).to receive(:setup).with( init_args ).once
|
11
|
+
subject
|
12
|
+
end
|
13
|
+
it 'returns its class' do
|
14
|
+
expect( subject ).to be( Praxis::MiddlewareApp )
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'instantiated' do
|
19
|
+
let(:target_response){ [201,{}] }
|
20
|
+
let(:target){ double("target app", call: target_response) }
|
21
|
+
subject(:instance){ Praxis::MiddlewareApp.new(target)}
|
22
|
+
it 'saves the target app' do
|
23
|
+
expect(subject.target).to be(target)
|
24
|
+
end
|
25
|
+
context '.call' do
|
26
|
+
let(:env){ {} }
|
27
|
+
let(:praxis_response){ [200,{}] }
|
28
|
+
subject(:response){ Praxis::MiddlewareApp.new(target).call(env) }
|
29
|
+
before do
|
30
|
+
# always invokes the praxis app
|
31
|
+
expect( Praxis::Application.instance ).to receive(:call).with( env ).once.and_return(praxis_response)
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'properly handled (non-404 and 405) responses from praxis' do
|
35
|
+
it 'are returned straight through' do
|
36
|
+
expect( response ).to be(praxis_response)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context '404/405 responses with X-Cascade = pass' do
|
41
|
+
let(:praxis_response){ [404, {'X-Cascade' => 'pass'}]}
|
42
|
+
it 'are forwarded to the target app' do
|
43
|
+
expect( response ).to be(target_response)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context '404/405 responses without X-Cascade = pass' do
|
48
|
+
let(:praxis_response){ [404, {}]}
|
49
|
+
it 'returned straight through' do
|
50
|
+
expect( response ).to be(praxis_response)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -4,7 +4,6 @@ describe Praxis::Plugins::PraxisMapperPlugin do
|
|
4
4
|
|
5
5
|
subject(:plugin) { Praxis::Plugins::PraxisMapperPlugin::Plugin.instance }
|
6
6
|
let(:config) { plugin.config }
|
7
|
-
|
8
7
|
context 'Plugin' do
|
9
8
|
context 'configuration' do
|
10
9
|
subject { config }
|
@@ -15,8 +14,14 @@ describe Praxis::Plugins::PraxisMapperPlugin do
|
|
15
14
|
context 'default repository' do
|
16
15
|
subject(:default) { config.repositories['default'] }
|
17
16
|
its(['type']) { should eq 'sequel' }
|
18
|
-
|
19
|
-
|
17
|
+
it 'has the right connection settings' do
|
18
|
+
if RUBY_PLATFORM !~ /java/
|
19
|
+
expect(subject['connection_settings'].dump).to eq( 'adapter' => 'sqlite','database' => ':memory:' )
|
20
|
+
else
|
21
|
+
expect(subject['connection_settings'].dump).to eq( 'adapter' => 'jdbc', 'uri' => 'jdbc:sqlite::memory:' )
|
22
|
+
end
|
23
|
+
|
24
|
+
|
20
25
|
end
|
21
26
|
end
|
22
27
|
|
@@ -26,7 +26,7 @@ describe Praxis::ResourceDefinition do
|
|
26
26
|
|
27
27
|
context 'for a resource with a parent' do
|
28
28
|
let(:resource_definition) { ApiResources::VolumeSnapshots}
|
29
|
-
|
29
|
+
|
30
30
|
its([:parent]) { should eq ApiResources::Volumes.id }
|
31
31
|
end
|
32
32
|
|
@@ -78,6 +78,7 @@ describe Praxis::ResourceDefinition do
|
|
78
78
|
include Praxis::ResourceDefinition
|
79
79
|
media_type Person
|
80
80
|
|
81
|
+
version '1.0'
|
81
82
|
def self.name
|
82
83
|
'FooBar'
|
83
84
|
end
|
@@ -101,12 +102,60 @@ describe Praxis::ResourceDefinition do
|
|
101
102
|
end
|
102
103
|
end
|
103
104
|
|
105
|
+
let(:non_singleton_api) do
|
106
|
+
api_def=Praxis::ApiDefinition.__send__(:new)
|
107
|
+
api_def.instance_eval do |api|
|
108
|
+
|
109
|
+
api.info do
|
110
|
+
base_path '/api/:base_param'
|
111
|
+
base_params do
|
112
|
+
attribute :base_param, String
|
113
|
+
attribute :grouped_params do
|
114
|
+
attribute :nested_param, String
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
api.info '1.0' do
|
120
|
+
base_params do
|
121
|
+
attribute :app_name, String
|
122
|
+
end
|
123
|
+
end
|
124
|
+
api.info '2.0' do
|
125
|
+
base_params do
|
126
|
+
attribute :v2_param, String
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
api_def
|
131
|
+
end
|
132
|
+
|
133
|
+
before do
|
134
|
+
allow(Praxis::ApiDefinition).to receive(:instance).and_return(non_singleton_api)
|
135
|
+
end
|
136
|
+
|
104
137
|
it 'are applied to actions' do
|
105
138
|
action = resource_definition.actions[:show]
|
106
139
|
expect(action.params.attributes).to have_key(:id)
|
107
|
-
expect(action.routes.first.path.to_s).to eq '/api/people/:id'
|
140
|
+
expect(action.routes.first.path.to_s).to eq '/api/:base_param/people/:id'
|
108
141
|
end
|
109
142
|
|
143
|
+
context 'includes base_params from the APIDefinition' do
|
144
|
+
let(:show_action_params){ resource_definition.actions[:show].params }
|
145
|
+
|
146
|
+
it 'including globally defined' do
|
147
|
+
expect(show_action_params.attributes).to have_key(:base_param)
|
148
|
+
expect(show_action_params.attributes).to have_key(:grouped_params)
|
149
|
+
grouped = show_action_params.attributes[:grouped_params]
|
150
|
+
expect(grouped.type.ancestors).to include(::Attributor::Struct)
|
151
|
+
expect(grouped.type.attributes.keys).to eq([:nested_param])
|
152
|
+
end
|
153
|
+
it 'including the ones defined for its own version' do
|
154
|
+
expect(show_action_params.attributes).to have_key(:app_name)
|
155
|
+
expect(show_action_params.attributes).to_not have_key(:v2_param)
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
110
159
|
end
|
111
160
|
|
112
161
|
context 'setting other values' do
|
@@ -448,7 +448,7 @@ describe Praxis::ResponseDefinition do
|
|
448
448
|
context 'with legacy multipart response' do
|
449
449
|
subject(:response) { Praxis::Responses::Ok.new(status: response_status, headers: response_headers) }
|
450
450
|
|
451
|
-
let(:part) { Praxis::MultipartPart.new('done', {'Status' => 200, 'Content-Type' => 'application/special'}) }
|
451
|
+
let(:part) { Praxis::MultipartPart.new('done', {'Status' => 200, 'Content-Type' => 'application/special'},{}) }
|
452
452
|
|
453
453
|
before do
|
454
454
|
response_definition.parts like: :ok, media_type: 'application/special'
|
@@ -463,7 +463,7 @@ describe Praxis::ResponseDefinition do
|
|
463
463
|
end
|
464
464
|
|
465
465
|
context 'with invalid part' do
|
466
|
-
let(:part) { Praxis::MultipartPart.new('done', {'Status' => 200, "Location" => "somewhere"}) }
|
466
|
+
let(:part) { Praxis::MultipartPart.new('done', {'Status' => 200, "Location" => "somewhere"},{}) }
|
467
467
|
|
468
468
|
it 'validates' do
|
469
469
|
expect {
|
@@ -520,8 +520,8 @@ describe Praxis::ResponseDefinition do
|
|
520
520
|
its([:name]) { should eq 'Instance' }
|
521
521
|
context 'examples' do
|
522
522
|
subject(:examples) { payload[:examples] }
|
523
|
-
its(['json', :content_type]) { 'application/vnd.acme.instance+json
|
524
|
-
its(['xml', :content_type]) { 'application/vnd.acme.instance+xml' }
|
523
|
+
its(['json', :content_type]) { should eq('application/vnd.acme.instance+json') }
|
524
|
+
its(['xml', :content_type]) { should eq('application/vnd.acme.instance+xml') }
|
525
525
|
|
526
526
|
it 'properly encodes the example bodies' do
|
527
527
|
json = Praxis::Application.instance.handlers['json'].parse(examples['json'][:body])
|
@@ -531,6 +531,18 @@ describe Praxis::ResponseDefinition do
|
|
531
531
|
|
532
532
|
end
|
533
533
|
|
534
|
+
context 'which does not have a identifier' do
|
535
|
+
subject(:examples) { payload[:examples] }
|
536
|
+
before do
|
537
|
+
allow(response.media_type).to receive(:identifier).and_return(nil)
|
538
|
+
end
|
539
|
+
|
540
|
+
it 'still renders examples but as pure handler types for contents' do
|
541
|
+
expect(subject['json'][:content_type]).to eq('application/json')
|
542
|
+
expect(subject['xml'][:content_type]).to eq('application/xml')
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
534
546
|
|
535
547
|
end
|
536
548
|
|