2017-09-21 04:34:12 -04:00
|
|
|
require 'spec_helper'
|
|
|
|
|
|
|
|
describe Ci::JobArtifact do
|
2018-03-20 19:03:50 -04:00
|
|
|
let(:artifact) { create(:ci_job_artifact, :archive) }
|
2017-09-21 04:34:12 -04:00
|
|
|
|
|
|
|
describe "Associations" do
|
|
|
|
it { is_expected.to belong_to(:project) }
|
|
|
|
it { is_expected.to belong_to(:job) }
|
|
|
|
end
|
|
|
|
|
|
|
|
it { is_expected.to respond_to(:file) }
|
|
|
|
it { is_expected.to respond_to(:created_at) }
|
|
|
|
it { is_expected.to respond_to(:updated_at) }
|
|
|
|
|
2018-02-06 09:18:32 -05:00
|
|
|
it { is_expected.to delegate_method(:open).to(:file) }
|
|
|
|
it { is_expected.to delegate_method(:exists?).to(:file) }
|
|
|
|
|
2018-07-27 01:04:35 -04:00
|
|
|
describe '.test_reports' do
|
|
|
|
subject { described_class.test_reports }
|
|
|
|
|
|
|
|
context 'when there is a test report' do
|
|
|
|
let!(:artifact) { create(:ci_job_artifact, :junit) }
|
|
|
|
|
|
|
|
it { is_expected.to eq([artifact]) }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when there are no test reports' do
|
|
|
|
let!(:artifact) { create(:ci_job_artifact, :archive) }
|
|
|
|
|
|
|
|
it { is_expected.to be_empty }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-09-27 17:15:08 -04:00
|
|
|
describe '.erasable' do
|
|
|
|
subject { described_class.erasable }
|
|
|
|
|
2018-10-02 13:01:26 -04:00
|
|
|
context 'when there is an erasable artifact' do
|
2018-09-27 17:15:08 -04:00
|
|
|
let!(:artifact) { create(:ci_job_artifact, :junit) }
|
|
|
|
|
|
|
|
it { is_expected.to eq([artifact]) }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when there are no erasable artifacts' do
|
|
|
|
let!(:artifact) { create(:ci_job_artifact, :trace) }
|
|
|
|
|
|
|
|
it { is_expected.to be_empty }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-12-08 04:09:06 -05:00
|
|
|
describe 'callbacks' do
|
|
|
|
subject { create(:ci_job_artifact, :archive) }
|
|
|
|
|
2018-03-01 16:30:31 -05:00
|
|
|
describe '#schedule_background_upload' do
|
2017-12-08 04:09:06 -05:00
|
|
|
context 'when object storage is disabled' do
|
|
|
|
before do
|
|
|
|
stub_artifacts_object_storage(enabled: false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not schedule the migration' do
|
2018-07-06 11:08:27 -04:00
|
|
|
expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
|
2017-12-08 04:09:06 -05:00
|
|
|
|
|
|
|
subject
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when object storage is enabled' do
|
|
|
|
context 'when background upload is enabled' do
|
2018-03-06 16:19:24 -05:00
|
|
|
before do
|
|
|
|
stub_artifacts_object_storage(background_upload: true)
|
2017-12-08 04:09:06 -05:00
|
|
|
end
|
|
|
|
|
2018-03-06 16:19:24 -05:00
|
|
|
it 'schedules the model for migration' do
|
2018-03-07 13:27:49 -05:00
|
|
|
expect(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async).with('JobArtifactUploader', described_class.name, :file, kind_of(Numeric))
|
2017-12-08 04:09:06 -05:00
|
|
|
|
2018-03-06 16:19:24 -05:00
|
|
|
subject
|
2017-12-08 04:09:06 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when background upload is disabled' do
|
|
|
|
before do
|
|
|
|
stub_artifacts_object_storage(background_upload: false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'schedules the model for migration' do
|
2018-03-07 13:27:49 -05:00
|
|
|
expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
|
2017-12-08 04:09:06 -05:00
|
|
|
|
|
|
|
subject
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-20 19:03:50 -04:00
|
|
|
context 'creating the artifact' do
|
|
|
|
let(:project) { create(:project) }
|
|
|
|
let(:artifact) { create(:ci_job_artifact, :archive, project: project) }
|
|
|
|
|
|
|
|
it 'sets the size from the file size' do
|
2017-09-21 04:34:12 -04:00
|
|
|
expect(artifact.size).to eq(106365)
|
|
|
|
end
|
2018-03-20 19:03:50 -04:00
|
|
|
|
|
|
|
it 'updates the project statistics' do
|
|
|
|
expect { artifact }
|
|
|
|
.to change { project.statistics.reload.build_artifacts_size }
|
|
|
|
.by(106365)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'updating the artifact file' do
|
|
|
|
it 'updates the artifact size' do
|
2018-06-05 17:18:06 -04:00
|
|
|
artifact.update!(file: fixture_file_upload('spec/fixtures/dk.png'))
|
2018-03-20 19:03:50 -04:00
|
|
|
expect(artifact.size).to eq(1062)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'updates the project statistics' do
|
2018-06-05 17:18:06 -04:00
|
|
|
expect { artifact.update!(file: fixture_file_upload('spec/fixtures/dk.png')) }
|
2018-03-20 19:03:50 -04:00
|
|
|
.to change { artifact.project.statistics.reload.build_artifacts_size }
|
|
|
|
.by(1062 - 106365)
|
|
|
|
end
|
2017-09-21 04:34:12 -04:00
|
|
|
end
|
|
|
|
|
2018-07-27 01:04:35 -04:00
|
|
|
describe 'validates file format' do
|
|
|
|
subject { artifact }
|
|
|
|
|
2018-09-27 17:15:08 -04:00
|
|
|
described_class::TYPE_AND_FORMAT_PAIRS.except(:trace).each do |file_type, file_format|
|
|
|
|
context "when #{file_type} type with #{file_format} format" do
|
|
|
|
let(:artifact) { build(:ci_job_artifact, file_type: file_type, file_format: file_format) }
|
2018-07-27 01:04:35 -04:00
|
|
|
|
2018-09-27 17:15:08 -04:00
|
|
|
it { is_expected.to be_valid }
|
|
|
|
end
|
2018-07-27 01:04:35 -04:00
|
|
|
|
2018-09-27 17:15:08 -04:00
|
|
|
context "when #{file_type} type without format specification" do
|
|
|
|
let(:artifact) { build(:ci_job_artifact, file_type: file_type, file_format: nil) }
|
2018-07-27 01:04:35 -04:00
|
|
|
|
2018-09-27 17:15:08 -04:00
|
|
|
it { is_expected.not_to be_valid }
|
|
|
|
end
|
2018-07-27 01:04:35 -04:00
|
|
|
|
2018-09-27 17:15:08 -04:00
|
|
|
context "when #{file_type} type with other formats" do
|
|
|
|
described_class.file_formats.except(file_format).values.each do |other_format|
|
|
|
|
let(:artifact) { build(:ci_job_artifact, file_type: file_type, file_format: other_format) }
|
2018-07-27 01:04:35 -04:00
|
|
|
|
2018-09-27 17:15:08 -04:00
|
|
|
it { is_expected.not_to be_valid }
|
|
|
|
end
|
|
|
|
end
|
2018-07-27 01:04:35 -04:00
|
|
|
end
|
2018-09-27 17:15:08 -04:00
|
|
|
end
|
2018-07-27 01:04:35 -04:00
|
|
|
|
2018-09-27 17:15:08 -04:00
|
|
|
describe 'validates DEFAULT_FILE_NAMES' do
|
|
|
|
subject { described_class::DEFAULT_FILE_NAMES }
|
2018-07-27 01:04:35 -04:00
|
|
|
|
2018-09-27 17:15:08 -04:00
|
|
|
described_class.file_types.each do |file_type, _|
|
|
|
|
it "expects #{file_type} to be included" do
|
|
|
|
is_expected.to include(file_type.to_sym)
|
|
|
|
end
|
2018-07-27 01:04:35 -04:00
|
|
|
end
|
2018-09-27 17:15:08 -04:00
|
|
|
end
|
2018-07-27 01:04:35 -04:00
|
|
|
|
2018-09-27 17:15:08 -04:00
|
|
|
describe 'validates TYPE_AND_FORMAT_PAIRS' do
|
|
|
|
subject { described_class::TYPE_AND_FORMAT_PAIRS }
|
2018-07-27 01:04:35 -04:00
|
|
|
|
2018-09-27 17:15:08 -04:00
|
|
|
described_class.file_types.each do |file_type, _|
|
|
|
|
it "expects #{file_type} to be included" do
|
|
|
|
expect(described_class.file_formats).to include(subject[file_type.to_sym])
|
|
|
|
end
|
2018-07-27 01:04:35 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-09-21 04:34:12 -04:00
|
|
|
describe '#file' do
|
|
|
|
subject { artifact.file }
|
|
|
|
|
|
|
|
context 'the uploader api' do
|
|
|
|
it { is_expected.to respond_to(:store_dir) }
|
|
|
|
it { is_expected.to respond_to(:cache_dir) }
|
|
|
|
it { is_expected.to respond_to(:work_dir) }
|
|
|
|
end
|
|
|
|
end
|
2017-12-03 06:02:11 -05:00
|
|
|
|
2018-08-02 02:05:07 -04:00
|
|
|
describe '#each_blob' do
|
|
|
|
context 'when file format is gzip' do
|
|
|
|
context 'when gzip file contains one file' do
|
|
|
|
let(:artifact) { build(:ci_job_artifact, :junit) }
|
|
|
|
|
|
|
|
it 'iterates blob once' do
|
|
|
|
expect { |b| artifact.each_blob(&b) }.to yield_control.once
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when gzip file contains three files' do
|
|
|
|
let(:artifact) { build(:ci_job_artifact, :junit_with_three_testsuites) }
|
|
|
|
|
|
|
|
it 'iterates blob three times' do
|
|
|
|
expect { |b| artifact.each_blob(&b) }.to yield_control.exactly(3).times
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-10-15 06:17:21 -04:00
|
|
|
context 'when file format is raw' do
|
|
|
|
let(:artifact) { build(:ci_job_artifact, :codequality, file_format: :raw) }
|
|
|
|
|
|
|
|
it 'iterates blob once' do
|
|
|
|
expect { |b| artifact.each_blob(&b) }.to yield_control.once
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-08-02 02:05:07 -04:00
|
|
|
context 'when there are no adapters for the file format' do
|
|
|
|
let(:artifact) { build(:ci_job_artifact, :junit, file_format: :zip) }
|
|
|
|
|
|
|
|
it 'raises an error' do
|
|
|
|
expect { |b| artifact.each_blob(&b) }.to raise_error(described_class::NotSupportedAdapterError)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-12-03 06:02:11 -05:00
|
|
|
describe '#expire_in' do
|
|
|
|
subject { artifact.expire_in }
|
|
|
|
|
|
|
|
it { is_expected.to be_nil }
|
|
|
|
|
|
|
|
context 'when expire_at is specified' do
|
|
|
|
let(:expire_at) { Time.now + 7.days }
|
|
|
|
|
|
|
|
before do
|
|
|
|
artifact.expire_at = expire_at
|
|
|
|
end
|
|
|
|
|
|
|
|
it { is_expected.to be_within(5).of(expire_at - Time.now) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#expire_in=' do
|
|
|
|
subject { artifact.expire_in }
|
|
|
|
|
|
|
|
it 'when assigning valid duration' do
|
|
|
|
artifact.expire_in = '7 days'
|
|
|
|
|
|
|
|
is_expected.to be_within(10).of(7.days.to_i)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'when assigning invalid duration' do
|
|
|
|
expect { artifact.expire_in = '7 elephants' }.to raise_error(ChronicDuration::DurationParseError)
|
2017-12-03 10:21:59 -05:00
|
|
|
|
2017-12-03 06:02:11 -05:00
|
|
|
is_expected.to be_nil
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'when resetting value' do
|
|
|
|
artifact.expire_in = nil
|
|
|
|
|
|
|
|
is_expected.to be_nil
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'when setting to 0' do
|
|
|
|
artifact.expire_in = '0'
|
|
|
|
|
|
|
|
is_expected.to be_nil
|
|
|
|
end
|
|
|
|
end
|
2018-03-20 19:03:50 -04:00
|
|
|
|
|
|
|
context 'when destroying the artifact' do
|
|
|
|
let(:project) { create(:project, :repository) }
|
|
|
|
let(:pipeline) { create(:ci_pipeline, project: project) }
|
|
|
|
let!(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
|
|
|
|
|
|
|
|
it 'updates the project statistics' do
|
|
|
|
artifact = build.job_artifacts.first
|
|
|
|
|
|
|
|
expect(ProjectStatistics)
|
|
|
|
.to receive(:increment_statistic)
|
|
|
|
.and_call_original
|
|
|
|
|
|
|
|
expect { artifact.destroy }
|
|
|
|
.to change { project.statistics.reload.build_artifacts_size }
|
|
|
|
.by(-106365)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when it is destroyed from the project level' do
|
|
|
|
it 'does not update the project statistics' do
|
|
|
|
expect(ProjectStatistics)
|
|
|
|
.not_to receive(:increment_statistic)
|
|
|
|
|
2018-07-02 06:43:06 -04:00
|
|
|
project.update(pending_delete: true)
|
2018-03-20 19:03:50 -04:00
|
|
|
project.destroy!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2018-04-13 04:20:07 -04:00
|
|
|
|
|
|
|
describe 'file is being stored' do
|
|
|
|
subject { create(:ci_job_artifact, :archive) }
|
|
|
|
|
|
|
|
context 'when object has nil store' do
|
|
|
|
before do
|
|
|
|
subject.update_column(:file_store, nil)
|
|
|
|
subject.reload
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'is stored locally' do
|
|
|
|
expect(subject.file_store).to be(nil)
|
|
|
|
expect(subject.file).to be_file_storage
|
|
|
|
expect(subject.file.object_store).to eq(ObjectStorage::Store::LOCAL)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when existing object has local store' do
|
|
|
|
it 'is stored locally' do
|
|
|
|
expect(subject.file_store).to be(ObjectStorage::Store::LOCAL)
|
|
|
|
expect(subject.file).to be_file_storage
|
|
|
|
expect(subject.file.object_store).to eq(ObjectStorage::Store::LOCAL)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when direct upload is enabled' do
|
|
|
|
before do
|
|
|
|
stub_artifacts_object_storage(direct_upload: true)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when file is stored' do
|
|
|
|
it 'is stored remotely' do
|
|
|
|
expect(subject.file_store).to eq(ObjectStorage::Store::REMOTE)
|
|
|
|
expect(subject.file).not_to be_file_storage
|
|
|
|
expect(subject.file.object_store).to eq(ObjectStorage::Store::REMOTE)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2017-09-21 04:34:12 -04:00
|
|
|
end
|