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