spec/functional_spec.rb in praxis-0.16.1 vs spec/functional_spec.rb in praxis-0.17.0
- old
+ new
@@ -10,16 +10,74 @@
context 'index' do
context 'with a valid request' do
it 'is successful' do
- get '/clouds/1/instances?api_version=1.0', nil, 'global_session' => session
+ get '/api/clouds/1/instances?api_version=1.0', nil, 'global_session' => session
expect(last_response.headers['Content-Type']).to(
eq("application/vnd.acme.instance;type=collection"))
end
end
+ context 'with a path param that can not load' do
+ it 'returns a useful error' do
+ get '/api/clouds/invalid/instances?api_version=1.0', nil, 'global_session' => session
+
+ expect(last_response.status).to eq 400
+
+ response = JSON.parse(last_response.body)
+ expect(response['name']).to eq 'ValidationError'
+ expect(response['summary']).to eq 'Error loading params.'
+ expect(response['errors']).to match_array([/Error loading attribute \$\.params\.cloud_id/])
+ expect(response['cause']['name']).to eq 'ArgumentError'
+ end
+ end
+
+ context 'with a header that can not load' do
+ it 'returns a useful error' do
+ get '/api/clouds/1/instances?api_version=1.0', nil, 'global_session' => session, 'HTTP_ACCOUNT_ID' => 'invalid'
+
+ expect(last_response.status).to eq 400
+
+ response = JSON.parse(last_response.body)
+
+ expect(response['name']).to eq 'ValidationError'
+ expect(response['summary']).to eq 'Error loading headers.'
+ expect(response['errors']).to match_array([/Error loading attribute .*Account-Id"/])
+ expect(response['cause']['name']).to eq 'ArgumentError'
+ end
+ end
+
+ context 'with a param that is invalid' do
+ it 'returns a useful error' do
+ get '/api/clouds/-1/instances?api_version=1.0', nil, 'global_session' => session
+
+ expect(last_response.status).to eq 400
+
+ response = JSON.parse(last_response.body)
+
+ expect(response['name']).to eq 'ValidationError'
+ expect(response['summary']).to eq 'Error validating request data.'
+ expect(response['errors']).to match_array([/.*cloud_id.*is smaller than the allowed min/])
+ end
+
+ end
+
+ context 'with a header that is invalid' do
+ it 'returns a useful error' do
+ get '/api/clouds/1/instances?api_version=1.0', nil, 'global_session' => session, 'HTTP_ACCOUNT_ID' => '-1'
+
+ expect(last_response.status).to eq 400
+
+ response = JSON.parse(last_response.body)
+
+ expect(response['name']).to eq 'ValidationError'
+ expect(response['summary']).to eq 'Error validating request data.'
+ expect(response['errors']).to match_array([/.*headers.*Account-Id.*is smaller than the allowed min/])
+ end
+ end
+
context 'with an incorrect response_content_type param' do
around do |example|
logger = app.logger
app.logger = Logger.new(StringIO.new)
@@ -27,17 +85,17 @@
app.logger = logger
end
it 'fails to validate the response' do
- get '/clouds/1/instances?response_content_type=somejunk&api_version=1.0', nil, 'HTTP_FOO' => "bar", 'global_session' => session
+ get '/api/clouds/1/instances?response_content_type=somejunk&api_version=1.0', nil, 'HTTP_FOO' => "bar", 'global_session' => session
expect(last_response.status).to eq(400)
response = JSON.parse(last_response.body)
expect(response['name']).to eq('ValidationError')
-
- expect(response["message"]).to match(/Bad Content-Type/)
+ expect(response['summary']).to eq("Error validating response")
+ expect(response['errors'].first).to match(/Bad Content-Type/)
end
context 'with response validation disabled' do
let(:praxis_config) { double('praxis_config', validate_responses: false) }
let(:config) { double('config', praxis: praxis_config) }
@@ -46,22 +104,22 @@
expect(Praxis::Application.instance.config).to receive(:praxis).and_return(praxis_config)
end
it 'fails to validate the response' do
expect {
- get '/clouds/1/instances?response_content_type=somejunk&api_version=1.0',nil, 'global_session' => session
+ get '/api/clouds/1/instances?response_content_type=somejunk&api_version=1.0',nil, 'global_session' => session
}.to_not raise_error
end
end
end
end
it 'works' do
the_body = StringIO.new("{}") # This is a funny, GET request expecting a body
- get '/clouds/1/instances/2?junk=foo&api_version=1.0', nil,'rack.input' => the_body,'CONTENT_TYPE' => "application/json", 'global_session' => session
+ get '/api/clouds/1/instances/2?junk=foo&api_version=1.0', nil,'rack.input' => the_body,'CONTENT_TYPE' => "application/json", 'global_session' => session
expect(last_response.status).to eq(200)
expected = {
"cloud_id" => 1,
"id"=>2,
"junk"=>"foo",
@@ -74,24 +132,24 @@
}
expect(JSON.parse(last_response.body)).to eq(expected)
headers = last_response.headers
- expect(headers['Content-Type']).to eq('application/vnd.acme.instance')
+ expect(headers['Content-Type']).to eq('application/json')
expect(headers['Spec-Middleware']).to eq('used')
expect(headers['Content-Length']).to eq(last_response.body.size.to_s)
end
it 'returns early when making the before filter break' do
- get '/clouds/1/instances/2?junk=foo&api_version=1.0&fail_filter=true', nil, 'global_session' => session
+ get '/api/clouds/1/instances/2?junk=foo&api_version=1.0&fail_filter=true', nil, 'global_session' => session
expect(last_response.status).to eq(401)
end
context 'bulk_create multipart' do
let(:instance) { Instance.example }
- let(:instance_json) { JSON.pretty_generate(instance.render(:create)) }
+ let(:instance_json) { JSON.pretty_generate(instance.render(view: :create)) }
let(:form) do
form_data = MIME::Multipart::FormData.new
entity = MIME::Text.new(instance_json)
form_data.add entity, instance.id.to_s
@@ -100,26 +158,26 @@
let(:content_type) { form.headers.get('Content-Type') }
let(:body) { form.body.to_s }
it 'works' do
+ post '/api/clouds/1/instances?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
- post '/clouds/1/instances?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
-
_reponse_preamble, response = Praxis::MultipartParser.parse(last_response.headers, last_response.body)
expect(response).to have(1).item
- response_id, instance_part = response.first
+ instance_part = response.first
+ response_id = instance_part.name
expect(response_id).to eq(instance.id.to_s)
instance_headers = instance_part.headers
expect(instance_headers['Status']).to eq('201')
expect(instance_headers['Location']).to match(%r|/clouds/.*/instances/.*|)
response_instance = JSON.parse(instance_part.body)
expect(response_instance["key"]).to eq(instance.id)
- expect(response_instance["value"].values).to eq(instance.render(:create).values)
+ expect(response_instance["value"].values).to eq(instance.render(view: :create).values)
end
end
context 'attach_file' do
@@ -138,11 +196,11 @@
let(:content_type) { form.headers.get('Content-Type') }
let(:body) { form.body.to_s }
context 'with a valid payload' do
before do
- post '/clouds/1/instances/2/files?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
+ post '/api/clouds/1/instances/2/files?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
end
subject(:response) { JSON.parse(last_response.body) }
its(['destination_path']) { should eq '/etc/defaults' }
@@ -151,11 +209,11 @@
subject(:file) { response['file'] }
its(['filename']) { should eq('docker') }
its(['type']) { should eq('text/plain') }
its(['name']) { should eq('file') }
- its(['tempfile']) { should match(/^\//) }
+ its(['tempfile']) { should eq('DOCKER_HOST=tcp://127.0.0.1:2375') }
end
end
context 'with a missing value in form' do
let(:form) do
@@ -168,11 +226,11 @@
end
let(:body) { form.body.to_s }
it 'returns an error' do
- post '/clouds/1/instances/2/files?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
+ post '/api/clouds/1/instances/2/files?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
response = JSON.parse(last_response.body)
expect(response['name']).to eq('ValidationError')
expect(response['errors']).to eq(["Attribute $.payload.key(\"destination_path\") is required"])
end
@@ -198,26 +256,23 @@
let(:body) { form.body.to_s }
subject(:response) { JSON.parse(last_response.body) }
before do
- post '/clouds/1/instances/2/files?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
+ post '/api/clouds/1/instances/2/files?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
end
its(:keys){ should eq(['destination_path','file','options'])}
its(['options']){ should eq({"extra_thing"=>"I am extra"})}
end
-
-
-
end
context 'not found and API versions' do
context 'when no version is speficied' do
it 'it tells you which available api versions would match' do
- get '/clouds/1/instances/2?junk=foo',nil, 'global_session' => session
+ get '/api/clouds/1/instances/2?junk=foo',nil, 'global_session' => session
expect(last_response.status).to eq(404)
expect(last_response.headers["Content-Type"]).to eq("text/plain")
expect(last_response.body).to eq("NotFound. Your request did not specify an API version. Available versions = \"1.0\".")
end
@@ -230,114 +285,114 @@
end
end
context 'when some version is speficied, but wrong' do
it 'it tells you which possible correcte api versions exist' do
- get '/clouds/1/instances/2?junk=foo&api_version=50.0', nil, 'global_session' => session
+ get '/api/clouds/1/instances/2?junk=foo&api_version=50.0', nil, 'global_session' => session
expect(last_response.status).to eq(404)
expect(last_response.headers["Content-Type"]).to eq("text/plain")
expect(last_response.body).to eq("NotFound. Your request speficied API version = \"50.0\". Available versions = \"1.0\".")
end
end
end
context 'volumes' do
+ before do
+ header 'X-Api-Version', '1.0'
+ end
+
context 'when no authorization header is passed' do
it 'works as expected' do
- get '/v1.0/volumes/123?junk=stuff', nil, 'global_session' => session
+ get '/api/clouds/1/volumes/123?junk=stuff', nil, 'global_session' => session
expect(last_response.status).to eq(200)
- expect(JSON.parse(last_response.body)).to eq({"id"=>123,
- "other_params"=>{
- "junk"=>"stuff",
- "some_date"=>"2012-12-21T00:00:00+00:00"}
- })
+ expect(Volume.load(last_response.body).validate).to be_empty
expect(last_response.headers["Content-Type"]).to eq("application/vnd.acme.volume")
end
end
context 'when an authorization header is passed' do
it 'returns 401 when it does not match "secret" ' do
- get '/v1.0/volumes/123?junk=stuff', nil, 'HTTP_AUTHORIZATION' => 'foobar', 'global_session' => session
+ get '/api/clouds/1/volumes/123?junk=stuff', nil, 'HTTP_AUTHORIZATION' => 'foobar', 'global_session' => session
expect(last_response.status).to eq(401)
expect(last_response.body).to match(/Authentication info is invalid/)
end
it 'succeeds as expected when it matches "secret" ' do
- get '/v1.0/volumes/123?junk=stuff', nil, 'HTTP_AUTHORIZATION' => 'the secret', 'global_session' => session
+ get '/api/clouds/1/volumes/123?junk=stuff', nil, 'HTTP_AUTHORIZATION' => 'the secret', 'global_session' => session
expect(last_response.status).to eq(200)
end
end
context 'index action with no args defined' do
it 'dispatches successfully' do
- get '/v1.0/volumes', nil, 'HTTP_AUTHORIZATION' => 'the secret', 'global_session' => session
+ get '/api/clouds/1/volumes', nil, 'HTTP_AUTHORIZATION' => 'the secret', 'global_session' => session
expect(last_response.status).to eq(200)
end
end
end
context 'wildcard verb routing' do
it 'can terminate instances with POST' do
- post '/clouds/23/instances/1/terminate?api_version=1.0', nil, 'global_session' => session
+ post '/api/clouds/23/instances/1/terminate?api_version=1.0', nil, 'global_session' => session
expect(last_response.status).to eq(200)
end
it 'can terminate instances with DELETE' do
- post '/clouds/23/instances/1/terminate?api_version=1.0', nil, 'global_session' => session
+ post '/api/clouds/23/instances/1/terminate?api_version=1.0', nil, 'global_session' => session
expect(last_response.status).to eq(200)
end
end
context 'route options' do
it 'reach the endpoint that does not match the except clause' do
- get '/clouds/23/otherinstances/_action/test?api_version=1.0', nil, 'global_session' => session
+ get '/api/clouds/23/otherinstances/_action/test?api_version=1.0', nil, 'global_session' => session
expect(last_response.status).to eq(200)
end
it 'does NOT reach the endpoint that matches the except clause' do
- get '/clouds/23/otherinstances/_action/exceptional?api_version=1.0', nil, 'global_session' => session
+ get '/api/clouds/23/otherinstances/_action/exceptional?api_version=1.0', nil, 'global_session' => session
expect(last_response.status).to eq(404)
end
end
context 'auth_plugin' do
it 'can terminate' do
- post '/clouds/23/instances/1/terminate?api_version=1.0', nil, 'global_session' => session
+ post '/api/clouds/23/instances/1/terminate?api_version=1.0', nil, 'global_session' => session
expect(last_response.status).to eq(200)
end
it 'can not stop' do
- post '/clouds/23/instances/1/stop?api_version=1.0', nil, 'global_session' => session
+ post '/api/clouds/23/instances/1/stop?api_version=1.0', nil, 'global_session' => session
expect(last_response.status).to eq(403)
end
end
context 'with mismatch between Content-Type and payload' do
let(:body) { '{}' }
let(:content_type) { 'application/x-www-form-urlencoded' }
before do
- post '/clouds/1/instances/2/terminate?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
+ post '/api/clouds/1/instances/2/terminate?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
end
it 'returns a useful error message' do
body = JSON.parse(last_response.body)
expect(body['name']).to eq('ValidationError')
- expect(body['message']).to match("For request Content-Type: 'application/x-www-form-urlencoded'")
+ expect(body['summary']).to match("Error loading payload. Used Content-Type: 'application/x-www-form-urlencoded'")
+ expect(body['errors']).to_not be_empty
end
end
context 'update' do
-
let(:body) { JSON.pretty_generate(request_payload) }
let(:content_type) { 'application/json' }
before do
- patch '/clouds/1/instances/3?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
+ patch '/api/clouds/1/instances/3?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
end
subject(:response_body) { JSON.parse(last_response.body) }
context 'with an empty payload' do
@@ -346,20 +401,31 @@
it { should_not have_key('name') }
it { should_not have_key('root_volume') }
end
context 'with a provided name' do
- let(:request_payload) { {name: 'My Instance'} }
- its(['name']) { should eq('My Instance') }
+ let(:request_payload) { {name: 'MyInstance'} }
+ its(['name']) { should eq('MyInstance') }
it { should_not have_key('root_volume') }
end
context 'with an explicitly-nil root_volme' do
- let(:request_payload) { {name: 'My Instance', root_volume: nil} }
- its(['name']) { should eq('My Instance') }
+ let(:request_payload) { {name: 'MyInstance', root_volume: nil} }
+ its(['name']) { should eq('MyInstance') }
its(['root_volume']) { should be(nil) }
end
+ context 'with an invalid name' do
+ let(:request_payload) { {name: 'Invalid Name'} }
+
+ its(['name']) { should eq 'ValidationError' }
+ its(['summary']) { should eq 'Error validating response' }
+ its(['errors']) { should match_array [/\$\.name value \(Invalid Name\) does not match regexp/] }
+
+ it 'returns a validation error' do
+ expect(last_response.status).to eq(400)
+ end
+ end
end
end