134 lines
3.8 KiB
Ruby
134 lines
3.8 KiB
Ruby
![]() |
# frozen_string_literal: true
|
||
|
|
||
|
require 'spec_helper'
|
||
|
|
||
|
RSpec.describe Ci::DeleteObjectsService, :aggregate_failure do
|
||
|
let(:service) { described_class.new }
|
||
|
let(:artifact) { create(:ci_job_artifact, :archive) }
|
||
|
let(:data) { [artifact] }
|
||
|
|
||
|
describe '#execute' do
|
||
|
before do
|
||
|
Ci::DeletedObject.bulk_import(data)
|
||
|
# We disable the check because the specs are wrapped in a transaction
|
||
|
allow(service).to receive(:transaction_open?).and_return(false)
|
||
|
end
|
||
|
|
||
|
subject(:execute) { service.execute }
|
||
|
|
||
|
it 'deletes records' do
|
||
|
expect { execute }.to change { Ci::DeletedObject.count }.by(-1)
|
||
|
end
|
||
|
|
||
|
it 'deletes files' do
|
||
|
expect { execute }.to change { artifact.file.exists? }
|
||
|
end
|
||
|
|
||
|
context 'when trying to execute without records' do
|
||
|
let(:data) { [] }
|
||
|
|
||
|
it 'does not change the number of objects' do
|
||
|
expect { execute }.not_to change { Ci::DeletedObject.count }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'when trying to remove the same file multiple times' do
|
||
|
let(:objects) { Ci::DeletedObject.all.to_a }
|
||
|
|
||
|
before do
|
||
|
expect(service).to receive(:load_next_batch).twice.and_return(objects)
|
||
|
end
|
||
|
|
||
|
it 'executes successfully' do
|
||
|
2.times { expect(service.execute).to be_truthy }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'with artifacts both ready and not ready for deletion' do
|
||
|
let(:data) { [] }
|
||
|
|
||
|
let_it_be(:past_ready) { create(:ci_deleted_object, pick_up_at: 2.days.ago) }
|
||
|
let_it_be(:ready) { create(:ci_deleted_object, pick_up_at: 1.day.ago) }
|
||
|
|
||
|
it 'skips records with pick_up_at in the future' do
|
||
|
not_ready = create(:ci_deleted_object, pick_up_at: 1.day.from_now)
|
||
|
|
||
|
expect { execute }.to change { Ci::DeletedObject.count }.from(3).to(1)
|
||
|
expect(not_ready.reload.present?).to be_truthy
|
||
|
end
|
||
|
|
||
|
it 'limits the number of records removed' do
|
||
|
stub_const("#{described_class}::BATCH_SIZE", 1)
|
||
|
|
||
|
expect { execute }.to change { Ci::DeletedObject.count }.by(-1)
|
||
|
end
|
||
|
|
||
|
it 'removes records in order' do
|
||
|
stub_const("#{described_class}::BATCH_SIZE", 1)
|
||
|
|
||
|
execute
|
||
|
|
||
|
expect { past_ready.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||
|
expect(ready.reload.present?).to be_truthy
|
||
|
end
|
||
|
|
||
|
it 'updates pick_up_at timestamp' do
|
||
|
allow(service).to receive(:destroy_everything)
|
||
|
|
||
|
execute
|
||
|
|
||
|
expect(past_ready.reload.pick_up_at).to be_like_time(10.minutes.from_now)
|
||
|
end
|
||
|
|
||
|
it 'does not delete objects for which file deletion has failed' do
|
||
|
expect(past_ready)
|
||
|
.to receive(:delete_file_from_storage)
|
||
|
.and_return(false)
|
||
|
|
||
|
expect(service)
|
||
|
.to receive(:load_next_batch)
|
||
|
.and_return([past_ready, ready])
|
||
|
|
||
|
expect { execute }.to change { Ci::DeletedObject.count }.from(2).to(1)
|
||
|
expect(past_ready.reload.present?).to be_truthy
|
||
|
end
|
||
|
end
|
||
|
|
||
|
context 'with an open database transaction' do
|
||
|
it 'raises an exception and does not remove records' do
|
||
|
expect(service).to receive(:transaction_open?).and_return(true)
|
||
|
|
||
|
expect { execute }
|
||
|
.to raise_error(Ci::DeleteObjectsService::TransactionInProgressError)
|
||
|
.and change { Ci::DeletedObject.count }.by(0)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe '#remaining_batches_count' do
|
||
|
subject { service.remaining_batches_count(max_batch_count: 3) }
|
||
|
|
||
|
context 'when there is less than one batch size' do
|
||
|
before do
|
||
|
Ci::DeletedObject.bulk_import(data)
|
||
|
end
|
||
|
|
||
|
it { is_expected.to eq(1) }
|
||
|
end
|
||
|
|
||
|
context 'when there is more than one batch size' do
|
||
|
before do
|
||
|
objects_scope = double
|
||
|
|
||
|
expect(Ci::DeletedObject)
|
||
|
.to receive(:ready_for_destruction)
|
||
|
.and_return(objects_scope)
|
||
|
|
||
|
expect(objects_scope).to receive(:size).and_return(110)
|
||
|
end
|
||
|
|
||
|
it { is_expected.to eq(2) }
|
||
|
end
|
||
|
end
|
||
|
end
|