diff --git a/lib/travis/api/v3.rb b/lib/travis/api/v3.rb index 0c7c3ae7..610de9cd 100644 --- a/lib/travis/api/v3.rb +++ b/lib/travis/api/v3.rb @@ -29,11 +29,11 @@ module Travis NotFound = ClientError .create(:resource, status: 404, template: '%s not found (or insufficient access)') AlreadySyncing = ClientError .create('sync already in progress', status: 409) - BuildAlreadyRunning = ClientError .create('build already running', status: 409) + BuildAlreadyRunning = ClientError .create('build already running, cannot restart', status: 409) BuildNotCancelable = ClientError .create('build is not running, cannot cancel', status: 409) EntityMissing = NotFound .create(type: 'not_found') InsufficientAccess = ClientError .create(status: 403) - JobAlreadyRunning = ClientError .create('job already running', status: 409) + JobAlreadyRunning = ClientError .create('job already running, cannot restart', status: 409) JobNotCancelable = ClientError .create('job is not running, cannot cancel', status: 409) LoginRequired = ClientError .create('login required', status: 403) MethodNotAllowed = ClientError .create('method not allowed', status: 405) diff --git a/spec/v3/services/build/cancel_spec.rb b/spec/v3/services/build/cancel_spec.rb index f49d27cd..90e9a107 100644 --- a/spec/v3/services/build/cancel_spec.rb +++ b/spec/v3/services/build/cancel_spec.rb @@ -164,7 +164,7 @@ describe Travis::API::V3::Services::Build::Cancel do example { expect(Sidekiq::Client.last['class']).to be == 'Travis::Sidekiq::BuildCancellation' } end - describe "started queued" do + describe "queued state" do before { build.update_attribute(:state, "queued") } before { post("/v3/build/#{build.id}/cancel", params, headers) } diff --git a/spec/v3/services/build/restart_spec.rb b/spec/v3/services/build/restart_spec.rb index 40e7ba5d..121c9f9b 100644 --- a/spec/v3/services/build/restart_spec.rb +++ b/spec/v3/services/build/restart_spec.rb @@ -77,36 +77,165 @@ describe Travis::API::V3::Services::Build::Restart do }} end - describe "existing repository, push access" do + describe "existing repository, push access, build already running" do let(:params) {{}} let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) } let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }} before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: true) } - before { post("/v3/build/#{build.id}/restart", params, headers) } - example { expect(last_response.status).to be == 202 } - example { expect(JSON.load(body).to_s).to include( - "@type", - "pending", - "build", - "@href", - "@representation", - "minimal", - "restart", - "id", - "state_change") - } + describe "started state" do + before { build.update_attribute(:state, "started") } + before { post("/v3/build/#{build.id}/restart", params, headers) } - example { expect(sidekiq_payload).to be == { - "id" => "#{build.id}", - "user_id"=> repo.owner_id, - "source" => "api"} - } + example { expect(last_response.status).to be == 409 } + example { expect(JSON.load(body)).to be == { + "@type" => "error", + "error_type" => "build_already_running", + "error_message" => "build already running, cannot restart" + }} + end - example { expect(Sidekiq::Client.last['queue']).to be == 'build_restarts' } - example { expect(Sidekiq::Client.last['class']).to be == 'Travis::Sidekiq::BuildRestart' } + describe "queued state" do + before { build.update_attribute(:state, "queued") } + before { post("/v3/build/#{build.id}/restart", params, headers) } + + example { expect(last_response.status).to be == 409 } + example { expect(JSON.load(body)).to be == { + "@type" => "error", + "error_type" => "build_already_running", + "error_message" => "build already running, cannot restart" + }} + end + + describe "received state" do + before { build.update_attribute(:state, "received") } + before { post("/v3/build/#{build.id}/restart", params, headers) } + + example { expect(last_response.status).to be == 409 } + example { expect(JSON.load(body)).to be == { + "@type" => "error", + "error_type" => "build_already_running", + "error_message" => "build already running, cannot restart" + }} + end + end + + describe "existing repository, push access, build not already running" do + let(:params) {{}} + let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) } + let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }} + before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: true) } + + describe "errored state" do + before { build.update_attribute(:state, "errored") } + before { post("/v3/build/#{build.id}/restart", params, headers) } + + example { expect(last_response.status).to be == 202 } + example { expect(JSON.load(body).to_s).to include( + "@type", + "pending", + "build", + "@href", + "@representation", + "minimal", + "restart", + "id", + "state_change") + } + + example { expect(sidekiq_payload).to be == { + "id" => "#{build.id}", + "user_id"=> repo.owner_id, + "source" => "api"} + } + + example { expect(Sidekiq::Client.last['queue']).to be == 'build_restarts' } + example { expect(Sidekiq::Client.last['class']).to be == 'Travis::Sidekiq::BuildRestart' } + end + + describe "passed state" do + before { build.update_attribute(:state, "passed") } + before { post("/v3/build/#{build.id}/restart", params, headers) } + + example { expect(last_response.status).to be == 202 } + example { expect(JSON.load(body).to_s).to include( + "@type", + "pending", + "build", + "@href", + "@representation", + "minimal", + "restart", + "id", + "state_change") + } + + example { expect(sidekiq_payload).to be == { + "id" => "#{build.id}", + "user_id"=> repo.owner_id, + "source" => "api"} + } + + example { expect(Sidekiq::Client.last['queue']).to be == 'build_restarts' } + example { expect(Sidekiq::Client.last['class']).to be == 'Travis::Sidekiq::BuildRestart' } + end + + describe "failed state" do + before { build.update_attribute(:state, "failed") } + before { post("/v3/build/#{build.id}/restart", params, headers) } + + example { expect(last_response.status).to be == 202 } + example { expect(JSON.load(body).to_s).to include( + "@type", + "pending", + "build", + "@href", + "@representation", + "minimal", + "restart", + "id", + "state_change") + } + + example { expect(sidekiq_payload).to be == { + "id" => "#{build.id}", + "user_id"=> repo.owner_id, + "source" => "api"} + } + + example { expect(Sidekiq::Client.last['queue']).to be == 'build_restarts' } + example { expect(Sidekiq::Client.last['class']).to be == 'Travis::Sidekiq::BuildRestart' } + end + + describe "canceled state" do + before { build.update_attribute(:state, "canceled") } + before { post("/v3/build/#{build.id}/restart", params, headers) } + + example { expect(last_response.status).to be == 202 } + example { expect(JSON.load(body).to_s).to include( + "@type", + "pending", + "build", + "@href", + "@representation", + "minimal", + "restart", + "id", + "state_change") + } + + example { expect(sidekiq_payload).to be == { + "id" => "#{build.id}", + "user_id"=> repo.owner_id, + "source" => "api"} + } + + example { expect(Sidekiq::Client.last['queue']).to be == 'build_restarts' } + example { expect(Sidekiq::Client.last['class']).to be == 'Travis::Sidekiq::BuildRestart' } + end describe "setting id has no effect" do + before { post("/v3/build/#{build.id}/restart", params, headers) } let(:params) {{ id: 42 }} example { expect(sidekiq_payload).to be == { "id" => "#{build.id}", diff --git a/spec/v3/services/job/cancel_spec.rb b/spec/v3/services/job/cancel_spec.rb index 6eb30301..48faac52 100644 --- a/spec/v3/services/job/cancel_spec.rb +++ b/spec/v3/services/job/cancel_spec.rb @@ -78,35 +78,94 @@ describe Travis::API::V3::Services::Job::Cancel do }} end - describe "existing repository, push access" do + describe "existing repository, push access, job cancelable" do let(:params) {{}} let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) } let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }} before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: true) } - before { post("/v3/job/#{job.id}/cancel", params, headers) } - example { expect(last_response.status).to be == 202 } - example { expect(JSON.load(body).to_s).to include( - "@type", - "job", - "@href", - "@representation", - "minimal", - "cancel", - "id", - "state_change") - } + describe "started state" do + before { job.update_attribute(:state, "started") } + before { post("/v3/job/#{job.id}/cancel", params, headers) } - example { expect(sidekiq_payload).to be == { - "id" => "#{job.id}", - "user_id"=> repo.owner_id, - "source" => "api"} - } + example { expect(last_response.status).to be == 202 } + example { expect(JSON.load(body).to_s).to include( + "@type", + "pending", + "job", + "@href", + "@representation", + "minimal", + "cancel", + "id", + "state_change") + } - example { expect(Sidekiq::Client.last['queue']).to be == 'job_cancellations' } - example { expect(Sidekiq::Client.last['class']).to be == 'Travis::Sidekiq::JobCancellation' } + example { expect(sidekiq_payload).to be == { + "id" => "#{job.id}", + "user_id"=> repo.owner_id, + "source" => "api"} + } + + example { expect(Sidekiq::Client.last['queue']).to be == 'job_cancellations' } + example { expect(Sidekiq::Client.last['class']).to be == 'Travis::Sidekiq::JobCancellation' } + end + describe "queued state" do + before { job.update_attribute(:state, "queued") } + before { post("/v3/job/#{job.id}/cancel", params, headers) } + + example { expect(last_response.status).to be == 202 } + example { expect(JSON.load(body).to_s).to include( + "@type", + "pending", + "job", + "@href", + "@representation", + "minimal", + "cancel", + "id", + "state_change") + } + + example { expect(sidekiq_payload).to be == { + "id" => "#{job.id}", + "user_id"=> repo.owner_id, + "source" => "api"} + } + + example { expect(Sidekiq::Client.last['queue']).to be == 'job_cancellations' } + example { expect(Sidekiq::Client.last['class']).to be == 'Travis::Sidekiq::JobCancellation' } + end + + describe "received state" do + before { job.update_attribute(:state, "received") } + before { post("/v3/job/#{job.id}/cancel", params, headers) } + + example { expect(last_response.status).to be == 202 } + example { expect(JSON.load(body).to_s).to include( + "@type", + "pending", + "job", + "@href", + "@representation", + "minimal", + "cancel", + "id", + "state_change") + } + + example { expect(sidekiq_payload).to be == { + "id" => "#{job.id}", + "user_id"=> repo.owner_id, + "source" => "api"} + } + + example { expect(Sidekiq::Client.last['queue']).to be == 'job_cancellations' } + example { expect(Sidekiq::Client.last['class']).to be == 'Travis::Sidekiq::JobCancellation' } + end describe "setting id has no effect" do + before { post("/v3/job/#{job.id}/cancel", params, headers) } let(:params) {{ id: 42 }} example { expect(sidekiq_payload).to be == { "id" => "#{job.id}", @@ -115,6 +174,60 @@ describe Travis::API::V3::Services::Job::Cancel do } end end + describe "existing repository, push access, not cancelable" do + let(:params) {{}} + let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) } + let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }} + before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: true) } + + describe "passed state" do + before { job.update_attribute(:state, "passed") } + before { post("/v3/job/#{job.id}/cancel", params, headers) } + + example { expect(last_response.status).to be == 409 } + example { expect(JSON.load(body)).to be == { + "@type" => "error", + "error_type" => "job_not_cancelable", + "error_message" => "job is not running, cannot cancel" + }} + end + + describe "errored state" do + before { job.update_attribute(:state, "errored") } + before { post("/v3/job/#{job.id}/cancel", params, headers) } + + example { expect(last_response.status).to be == 409 } + example { expect(JSON.load(body)).to be == { + "@type" => "error", + "error_type" => "job_not_cancelable", + "error_message" => "job is not running, cannot cancel" + }} + end + + describe "failed state" do + before { job.update_attribute(:state, "failed") } + before { post("/v3/job/#{job.id}/cancel", params, headers) } + + example { expect(last_response.status).to be == 409 } + example { expect(JSON.load(body)).to be == { + "@type" => "error", + "error_type" => "job_not_cancelable", + "error_message" => "job is not running, cannot cancel" + }} + end + + describe "canceled state" do + before { job.update_attribute(:state, "canceled") } + before { post("/v3/job/#{job.id}/cancel", params, headers) } + + example { expect(last_response.status).to be == 409 } + example { expect(JSON.load(body)).to be == { + "@type" => "error", + "error_type" => "job_not_cancelable", + "error_message" => "job is not running, cannot cancel" + }} + end + end # TODO decided to discuss further with rkh as this use case doesn't really exist at the moment # and 'fixing' the query requires modifying workers that v2 uses, thereby running the risk of breaking v2, diff --git a/spec/v3/services/job/restart_spec.rb b/spec/v3/services/job/restart_spec.rb index c4884f41..a07f0b82 100644 --- a/spec/v3/services/job/restart_spec.rb +++ b/spec/v3/services/job/restart_spec.rb @@ -78,36 +78,119 @@ describe Travis::API::V3::Services::Job::Restart do }} end - describe "existing repository, push access" do + describe "existing repository, push access, job not already running" do let(:params) {{}} let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) } let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }} before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: true) } - before { post("/v3/job/#{job.id}/restart", params, headers) } - example { expect(last_response.status).to be == 202 } - example { expect(JSON.load(body).to_s).to include( - "@type", - "pending", - "job", - "@href", - "@representation", - "minimal", - "restart", - "id", - "state_change") - } + describe "canceled state" do + before { job.update_attribute(:state, "canceled") } + before { post("/v3/job/#{job.id}/restart", params, headers) } - example { expect(sidekiq_payload).to be == { - "id" => "#{job.id}", - "user_id"=> repo.owner_id, - "source" => "api"} - } + example { expect(last_response.status).to be == 202 } + example { expect(JSON.load(body).to_s).to include( + "@type", + "pending", + "job", + "@href", + "@representation", + "minimal", + "restart", + "id", + "state_change") + } - example { expect(Sidekiq::Client.last['queue']).to be == 'job_restarts' } - example { expect(Sidekiq::Client.last['class']).to be == 'Travis::Sidekiq::JobRestart' } + example { expect(sidekiq_payload).to be == { + "id" => "#{job.id}", + "user_id"=> repo.owner_id, + "source" => "api"} + } + + example { expect(Sidekiq::Client.last['queue']).to be == 'job_restarts' } + example { expect(Sidekiq::Client.last['class']).to be == 'Travis::Sidekiq::JobRestart' } + end + describe "errored state" do + before { job.update_attribute(:state, "errored") } + before { post("/v3/job/#{job.id}/restart", params, headers) } + + example { expect(last_response.status).to be == 202 } + example { expect(JSON.load(body).to_s).to include( + "@type", + "pending", + "job", + "@href", + "@representation", + "minimal", + "restart", + "id", + "state_change") + } + + example { expect(sidekiq_payload).to be == { + "id" => "#{job.id}", + "user_id"=> repo.owner_id, + "source" => "api"} + } + + example { expect(Sidekiq::Client.last['queue']).to be == 'job_restarts' } + example { expect(Sidekiq::Client.last['class']).to be == 'Travis::Sidekiq::JobRestart' } + end + describe "failed state" do + before { job.update_attribute(:state, "failed") } + before { post("/v3/job/#{job.id}/restart", params, headers) } + + example { expect(last_response.status).to be == 202 } + example { expect(JSON.load(body).to_s).to include( + "@type", + "pending", + "job", + "@href", + "@representation", + "minimal", + "restart", + "id", + "state_change") + } + + example { expect(sidekiq_payload).to be == { + "id" => "#{job.id}", + "user_id"=> repo.owner_id, + "source" => "api"} + } + + example { expect(Sidekiq::Client.last['queue']).to be == 'job_restarts' } + example { expect(Sidekiq::Client.last['class']).to be == 'Travis::Sidekiq::JobRestart' } + end + describe "passed state" do + before { job.update_attribute(:state, "passed") } + before { post("/v3/job/#{job.id}/restart", params, headers) } + + example { expect(last_response.status).to be == 202 } + example { expect(JSON.load(body).to_s).to include( + "@type", + "pending", + "job", + "@href", + "@representation", + "minimal", + "restart", + "id", + "state_change") + } + + example { expect(sidekiq_payload).to be == { + "id" => "#{job.id}", + "user_id"=> repo.owner_id, + "source" => "api"} + } + + example { expect(Sidekiq::Client.last['queue']).to be == 'job_restarts' } + example { expect(Sidekiq::Client.last['class']).to be == 'Travis::Sidekiq::JobRestart' } + end describe "setting id has no effect" do + before { post("/v3/job/#{job.id}/restart", params, headers) } let(:params) {{ id: 42 }} example { expect(sidekiq_payload).to be == { "id" => "#{job.id}", @@ -117,6 +200,49 @@ describe Travis::API::V3::Services::Job::Restart do end end + describe "existing repository, push access, job already running" do + let(:params) {{}} + let(:token) { Travis::Api::App::AccessToken.create(user: repo.owner, app_id: 1) } + let(:headers) {{ 'HTTP_AUTHORIZATION' => "token #{token}" }} + before { Travis::API::V3::Models::Permission.create(repository: repo, user: repo.owner, push: true) } + + describe "started state" do + before { job.update_attribute(:state, "started") } + before { post("/v3/job/#{job.id}/restart", params, headers) } + + example { expect(last_response.status).to be == 409 } + example { expect(JSON.load(body)).to be == { + "@type" => "error", + "error_type" => "job_already_running", + "error_message" => "job already running, cannot restart" + }} + end + + describe "queued state" do + before { job.update_attribute(:state, "queued") } + before { post("/v3/job/#{job.id}/restart", params, headers) } + + example { expect(last_response.status).to be == 409 } + example { expect(JSON.load(body)).to be == { + "@type" => "error", + "error_type" => "job_already_running", + "error_message" => "job already running, cannot restart" + }} + end + + describe "received state" do + before { job.update_attribute(:state, "received") } + before { post("/v3/job/#{job.id}/restart", params, headers) } + + example { expect(last_response.status).to be == 409 } + example { expect(JSON.load(body)).to be == { + "@type" => "error", + "error_type" => "job_already_running", + "error_message" => "job already running, cannot restart" + }} + end + end + # TODO decided to discuss further with rkh as this use case doesn't really exist at the moment # and 'fixing' the query requires modifying workers that v2 uses, thereby running the risk of breaking v2, # and also because in 6 months or so travis-hub will be able to cancel builds without using travis-core at all.