job_artifact_spec.rb 9.15 KB
Newer Older
1 2 3
require 'spec_helper'

describe Ci::JobArtifact do
4
  let(:artifact) { create(:ci_job_artifact, :archive) }
5 6 7 8 9 10 11 12 13 14

  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) }

15 16 17
  it { is_expected.to delegate_method(:open).to(:file) }
  it { is_expected.to delegate_method(:exists?).to(:file) }

Shinya Maeda's avatar
Shinya Maeda committed
18 19
  it_behaves_like 'Unique enum values'

20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
  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

36 37 38
  describe '.erasable' do
    subject { described_class.erasable }

39
    context 'when there is an erasable artifact' do
40 41 42 43 44 45 46 47 48 49 50 51
      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

52 53 54
  describe 'callbacks' do
    subject { create(:ci_job_artifact, :archive) }

55
    describe '#schedule_background_upload' do
56 57 58 59 60 61
      context 'when object storage is disabled' do
        before do
          stub_artifacts_object_storage(enabled: false)
        end

        it 'does not schedule the migration' do
62
          expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
63 64 65 66 67 68 69

          subject
        end
      end

      context 'when object storage is enabled' do
        context 'when background upload is enabled' do
Micaël Bergeron's avatar
Micaël Bergeron committed
70 71
          before do
            stub_artifacts_object_storage(background_upload: true)
72 73
          end

Micaël Bergeron's avatar
Micaël Bergeron committed
74
          it 'schedules the model for migration' do
Micaël Bergeron's avatar
Micaël Bergeron committed
75
            expect(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async).with('JobArtifactUploader', described_class.name, :file, kind_of(Numeric))
76

Micaël Bergeron's avatar
Micaël Bergeron committed
77
            subject
78 79 80 81 82 83 84 85 86
          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
Micaël Bergeron's avatar
Micaël Bergeron committed
87
            expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async)
88 89 90 91 92 93 94 95

            subject
          end
        end
      end
    end
  end

96 97 98 99 100
  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
101 102
      expect(artifact.size).to eq(106365)
    end
103 104 105 106 107 108 109 110 111 112

    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
113
      artifact.update!(file: fixture_file_upload('spec/fixtures/dk.png'))
114 115 116 117
      expect(artifact.size).to eq(1062)
    end

    it 'updates the project statistics' do
118
      expect { artifact.update!(file: fixture_file_upload('spec/fixtures/dk.png')) }
119 120 121
        .to change { artifact.project.statistics.reload.build_artifacts_size }
        .by(1062 - 106365)
    end
122 123
  end

124 125 126
  describe 'validates file format' do
    subject { artifact }

127 128 129
    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) }
130

131 132
        it { is_expected.to be_valid }
      end
133

134 135
      context "when #{file_type} type without format specification" do
        let(:artifact) { build(:ci_job_artifact, file_type: file_type, file_format: nil) }
136

137 138
        it { is_expected.not_to be_valid }
      end
139

140 141 142
      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) }
143

144 145 146
          it { is_expected.not_to be_valid }
        end
      end
147
    end
148
  end
149

150 151
  describe 'validates DEFAULT_FILE_NAMES' do
    subject { described_class::DEFAULT_FILE_NAMES }
152

153 154 155 156
    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
157
    end
158
  end
159

160 161
  describe 'validates TYPE_AND_FORMAT_PAIRS' do
    subject { described_class::TYPE_AND_FORMAT_PAIRS }
162

163 164 165 166
    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
167 168 169
    end
  end

170 171 172 173 174 175 176 177 178
  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
179

180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
  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

199 200 201 202 203 204 205 206
    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

207 208 209 210 211 212 213 214 215
    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

216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
  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)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
243

244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
      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
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281

  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)

Lin Jen-Shin's avatar
Lin Jen-Shin committed
282
        project.update(pending_delete: true)
283 284 285 286
        project.destroy!
      end
    end
  end
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325

  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
326
end