summaryrefslogtreecommitdiff
path: root/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
blob: 1bd176280c528d2595d1525d39dd5371c64c5f91 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
shared_context 'with storage' do |store, **stub_params|
  before do
    subject.object_store = store
  end
end

shared_examples "migrates" do |to_store:, from_store: nil|
  let(:to) { to_store }
  let(:from) { from_store || subject.object_store }

  def migrate(to)
    subject.migrate!(to)
  end

  def checksum
    Digest::SHA256.hexdigest(subject.read)
  end

  before do
    migrate(from)
  end

  it 'returns corresponding file type' do
    expect(subject).to be_an(CarrierWave::Uploader::Base)
    expect(subject).to be_a(ObjectStorage::Concern)

    if from == described_class::Store::REMOTE
      expect(subject.file).to be_a(CarrierWave::Storage::Fog::File)
    elsif from == described_class::Store::LOCAL
      expect(subject.file).to be_a(CarrierWave::SanitizedFile)
    else
      raise 'Unexpected file type'
    end
  end

  it 'does nothing when migrating to the current store' do
    expect { migrate(from) }.not_to change { subject.object_store }.from(from)
  end

  it 'migrate to the specified store' do
    from_checksum = checksum

    expect { migrate(to) }.to change { subject.object_store }.from(from).to(to)
    expect(checksum).to eq(from_checksum)
  end

  it 'removes the original file after the migration' do
    original_file = subject.file.path
    migrate(to)

    expect(File.exist?(original_file)).to be_falsey
  end

  it 'can access to the original file during migration' do
    file = subject.file

    allow(subject).to receive(:delete_migrated_file) { } # Remove as a callback of :migrate
    allow(subject).to receive(:record_upload) { } # Remove as a callback of :store (:record_upload)

    expect(file.exists?).to be_truthy
    expect { migrate(to) }.not_to change { file.exists? }
  end

  context 'when migrate! is not occupied by another process' do
    it 'executes migrate!' do
      expect(subject).to receive(:object_store=).at_least(1)

      migrate(to)
    end

    it 'executes use_file' do
      expect(subject).to receive(:unsafe_use_file).once

      subject.use_file
    end
  end

  context 'when migrate! is occupied by another process' do
    include ExclusiveLeaseHelpers

    before do
      stub_exclusive_lease_taken(subject.exclusive_lease_key, timeout: 1.hour.to_i)
    end

    it 'does not execute migrate!' do
      expect(subject).not_to receive(:unsafe_migrate!)

      expect { migrate(to) }.to raise_error(ObjectStorage::ExclusiveLeaseTaken)
    end

    it 'does not execute use_file' do
      expect(subject).not_to receive(:unsafe_use_file)

      expect { subject.use_file }.to raise_error(ObjectStorage::ExclusiveLeaseTaken)
    end
  end

  context 'migration is unsuccessful' do
    shared_examples "handles gracefully" do |error:|
      it 'does not update the object_store' do
        expect { migrate(to) }.to raise_error(error)
        expect(subject.object_store).to eq(from)
      end

      it 'does not delete the original file' do
        expect { migrate(to) }.to raise_error(error)
        expect(subject.exists?).to be_truthy
      end
    end

    context 'when the store is not supported' do
      let(:to) { -1 } # not a valid store

      include_examples "handles gracefully", error: ObjectStorage::UnknownStoreError
    end

    context 'upon a fog failure' do
      before do
        storage_class = subject.send(:storage_for, to).class
        expect_any_instance_of(storage_class).to receive(:store!).and_raise("Store failure.")
      end

      include_examples "handles gracefully", error: "Store failure."
    end

    context 'upon a database failure' do
      before do
        expect(uploader).to receive(:persist_object_store!).and_raise("ActiveRecord failure.")
      end

      include_examples "handles gracefully", error: "ActiveRecord failure."
    end
  end
end