Skip to content

Commit 59a81aa

Browse files
committed
Expand ActiveStorage and config parity test coverage
1 parent 664c468 commit 59a81aa

File tree

7 files changed

+335
-1
lines changed

7 files changed

+335
-1
lines changed

spec/uploadcare/rails/active_storage/uploadcare_service_spec.rb

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@
55
require 'logger'
66
require 'base64'
77
require 'digest'
8+
require 'active_storage/errors'
89
require 'active_storage/service/uploadcare_service'
910

1011
RSpec.describe ActiveStorage::Service::UploadcareService do
1112
let(:service) { described_class.new(public_key: 'demopublickey', secret_key: 'demosecretkey') }
13+
let(:public_service) { described_class.new(public_key: 'demopublickey', secret_key: 'demosecretkey', public: true) }
1214
let(:uuid) { '2d33999d-c74a-4ff9-99ea-abc23496b052' }
1315

1416
before do
1517
stub_const('ActiveStorage::Blob', Class.new)
1618
allow(ActiveStorage).to receive(:logger).and_return(Logger.new(nil))
1719
allow(ActiveStorage::Blob).to receive(:where).and_return(double(pluck: []))
20+
allow(ActiveStorage::Blob).to receive(:find_by).and_return(nil)
1821
end
1922

2023
it 'uploads a file and persists uploadcare uuid into blob metadata' do
@@ -32,6 +35,108 @@
3235
service.upload('blob-key', io, checksum: checksum)
3336
end
3437

38+
it 'raises integrity error for invalid checksum' do
39+
io = StringIO.new('test payload')
40+
bad_checksum = Base64.strict_encode64(Digest::MD5.digest('different payload'))
41+
42+
uploadcare_file = double(uuid: uuid)
43+
allow(Uploadcare::Uploader).to receive(:upload_file).and_return(uploadcare_file)
44+
45+
expect { service.upload('blob-key', io, checksum: bad_checksum) }.to raise_error(ActiveStorage::IntegrityError)
46+
end
47+
48+
it 'downloads file body for mapped key' do
49+
blob = double(metadata: { 'uploadcare_uuid' => uuid })
50+
allow(ActiveStorage::Blob).to receive(:find_by).with(key: 'blob-key').and_return(blob)
51+
allow(Uploadcare::File).to receive(:info).with(uuid: uuid, config: kind_of(Uploadcare::Configuration))
52+
.and_return(double(original_file_url: 'https://ucarecdn.com/file.bin', cdn_url: nil))
53+
allow(service).to receive(:request).with('https://ucarecdn.com/file.bin').and_return('file-body')
54+
55+
expect(service.download('blob-key')).to eq('file-body')
56+
end
57+
58+
it 'streams download when block is given' do
59+
blob = double(metadata: { 'uploadcare_uuid' => uuid })
60+
allow(ActiveStorage::Blob).to receive(:find_by).with(key: 'blob-key').and_return(blob)
61+
allow(Uploadcare::File).to receive(:info).with(uuid: uuid, config: kind_of(Uploadcare::Configuration))
62+
.and_return(double(original_file_url: nil, cdn_url: 'https://ucarecdn.com/file.bin'))
63+
response = double
64+
allow(response).to receive(:read_body).and_yield('ab').and_yield('cd')
65+
allow(service).to receive(:request).with('https://ucarecdn.com/file.bin').and_yield(response)
66+
67+
chunks = []
68+
service.download('blob-key') { |chunk| chunks << chunk }
69+
70+
expect(chunks).to eq(%w[ab cd])
71+
end
72+
73+
it 'raises file not found on download for missing blob mapping' do
74+
expect { service.download('missing-key') }.to raise_error(ActiveStorage::FileNotFoundError)
75+
end
76+
77+
it 'raises file not found on download when uploadcare does not know uuid' do
78+
blob = double(metadata: { 'uploadcare_uuid' => uuid })
79+
allow(ActiveStorage::Blob).to receive(:find_by).with(key: 'blob-key').and_return(blob)
80+
allow(Uploadcare::File).to receive(:info).with(uuid: uuid, config: kind_of(Uploadcare::Configuration))
81+
.and_raise(Uploadcare::Exception::NotFoundError)
82+
83+
expect { service.download('blob-key') }.to raise_error(ActiveStorage::FileNotFoundError)
84+
end
85+
86+
it 'downloads chunk with requested range' do
87+
blob = double(metadata: { 'uploadcare_uuid' => uuid })
88+
allow(ActiveStorage::Blob).to receive(:find_by).with(key: 'blob-key').and_return(blob)
89+
allow(Uploadcare::File).to receive(:info).with(uuid: uuid, config: kind_of(Uploadcare::Configuration))
90+
.and_return(double(original_file_url: nil, cdn_url: 'https://ucarecdn.com/file.bin'))
91+
allow(service).to receive(:request).with('https://ucarecdn.com/file.bin', range: 0..3).and_return('data')
92+
93+
expect(service.download_chunk('blob-key', 0..3)).to eq('data')
94+
end
95+
96+
it 'raises file not found on download_chunk when uploadcare does not know uuid' do
97+
blob = double(metadata: { 'uploadcare_uuid' => uuid })
98+
allow(ActiveStorage::Blob).to receive(:find_by).with(key: 'blob-key').and_return(blob)
99+
allow(Uploadcare::File).to receive(:info).with(uuid: uuid, config: kind_of(Uploadcare::Configuration))
100+
.and_raise(Uploadcare::Exception::NotFoundError)
101+
102+
expect { service.download_chunk('blob-key', 0..3) }.to raise_error(ActiveStorage::FileNotFoundError)
103+
end
104+
105+
it 'deletes a mapped file' do
106+
blob = double(metadata: { 'uploadcare_uuid' => uuid })
107+
uploadcare_file = double(delete: true)
108+
allow(ActiveStorage::Blob).to receive(:find_by).with(key: 'blob-key').and_return(blob)
109+
expect(Uploadcare::File).to receive(:new).with({ uuid: uuid }, kind_of(Uploadcare::Configuration)).and_return(uploadcare_file)
110+
111+
service.delete('blob-key')
112+
end
113+
114+
it 'does not delete when mapping is missing' do
115+
expect(Uploadcare::File).not_to receive(:new)
116+
117+
service.delete('missing-key')
118+
end
119+
120+
it 'returns nil when delete gets not found error' do
121+
blob = double(metadata: { 'uploadcare_uuid' => uuid })
122+
uploadcare_file = double
123+
allow(ActiveStorage::Blob).to receive(:find_by).with(key: 'blob-key').and_return(blob)
124+
allow(Uploadcare::File).to receive(:new).with({ uuid: uuid }, kind_of(Uploadcare::Configuration)).and_return(uploadcare_file)
125+
allow(uploadcare_file).to receive(:delete).and_raise(Uploadcare::Exception::NotFoundError)
126+
127+
expect(service.delete('blob-key')).to be_nil
128+
end
129+
130+
it 'deletes all keys with prefix' do
131+
allow(ActiveStorage::Blob).to receive(:where).with('key LIKE ?', 'prefix%').and_return(double(pluck: %w[prefix-1 prefix-2]))
132+
allow(service).to receive(:delete)
133+
134+
service.delete_prefixed('prefix')
135+
136+
expect(service).to have_received(:delete).with('prefix-1')
137+
expect(service).to have_received(:delete).with('prefix-2')
138+
end
139+
35140
it 'supports existence check using mapped uuid' do
36141
blob = double(metadata: { 'uploadcare_uuid' => uuid }, update!: true)
37142
allow(ActiveStorage::Blob).to receive(:find_by).with(key: 'blob-key').and_return(blob)
@@ -40,8 +145,81 @@
40145
expect(service.exist?('blob-key')).to eq(true)
41146
end
42147

148+
it 'returns false from exist? when file is not found in uploadcare' do
149+
blob = double(metadata: { 'uploadcare_uuid' => uuid }, update!: true)
150+
allow(ActiveStorage::Blob).to receive(:find_by).with(key: 'blob-key').and_return(blob)
151+
allow(Uploadcare::File).to receive(:info).with(uuid: uuid, config: kind_of(Uploadcare::Configuration))
152+
.and_raise(Uploadcare::Exception::NotFoundError)
153+
154+
expect(service.exist?('blob-key')).to eq(false)
155+
end
156+
157+
it 'returns false from exist? when key is unknown' do
158+
expect(service.exist?('missing-key')).to eq(false)
159+
end
160+
161+
it 'uses instrumentation payload for exist? true path' do
162+
blob = double(metadata: { 'uploadcare_uuid' => uuid }, update!: true)
163+
allow(ActiveStorage::Blob).to receive(:find_by).with(key: 'blob-key').and_return(blob)
164+
allow(Uploadcare::File).to receive(:info).with(uuid: uuid, config: kind_of(Uploadcare::Configuration)).and_return(double)
165+
166+
payload = {}
167+
expect(service).to receive(:instrument).with(:exist, key: 'blob-key').and_yield(payload)
168+
169+
expect(service.exist?('blob-key')).to eq(true)
170+
expect(payload[:exist]).to eq(true)
171+
end
172+
173+
it 'uses instrumentation payload for exist? false path' do
174+
payload = {}
175+
expect(service).to receive(:instrument).with(:exist, key: 'missing-key').and_yield(payload)
176+
177+
expect(service.exist?('missing-key')).to eq(false)
178+
expect(payload[:exist]).to eq(false)
179+
end
180+
181+
it 'uses uuid key directly when key is a uuid' do
182+
allow(Uploadcare::File).to receive(:info).with(uuid: uuid, config: kind_of(Uploadcare::Configuration)).and_return(double)
183+
184+
expect(service.exist?(uuid)).to eq(true)
185+
end
186+
187+
it 'returns empty headers for direct upload' do
188+
expect(service.headers_for_direct_upload('key', checksum: 'x')).to eq({})
189+
end
190+
43191
it 'raises for direct upload support' do
44192
expect { service.url_for_direct_upload('key', expires_in: 10, content_type: 'text/plain', content_length: 1, checksum: 'x') }
45193
.to raise_error(NotImplementedError)
46194
end
195+
196+
it 'generates url for private service' do
197+
blob = double(metadata: { 'uploadcare_uuid' => uuid })
198+
file = double(cdn_url: 'https://ucarecdn.com/private')
199+
allow(ActiveStorage::Blob).to receive(:find_by).with(key: 'blob-key').and_return(blob)
200+
allow(Uploadcare::File).to receive(:new).with({ uuid: uuid }, kind_of(Uploadcare::Configuration)).and_return(file)
201+
202+
expect(service.url('blob-key', expires_in: 60, filename: 'a.txt', disposition: :inline, content_type: 'text/plain'))
203+
.to eq('https://ucarecdn.com/private')
204+
end
205+
206+
it 'generates url for public service' do
207+
blob = double(metadata: { 'uploadcare_uuid' => uuid })
208+
file = double(cdn_url: 'https://ucarecdn.com/public')
209+
allow(ActiveStorage::Blob).to receive(:find_by).with(key: 'blob-key').and_return(blob)
210+
allow(Uploadcare::File).to receive(:new).with({ uuid: uuid }, kind_of(Uploadcare::Configuration)).and_return(file)
211+
212+
expect(public_service.url('blob-key', expires_in: 60, filename: 'a.txt', disposition: :inline, content_type: 'text/plain'))
213+
.to eq('https://ucarecdn.com/public')
214+
end
215+
216+
it 'uses blob metadata mapping for download after upload mapping persisted' do
217+
blob = double(metadata: { 'uploadcare_uuid' => uuid }, update!: true)
218+
allow(ActiveStorage::Blob).to receive(:find_by).with(key: 'blob-key').and_return(blob)
219+
allow(Uploadcare::File).to receive(:info).with(uuid: uuid, config: kind_of(Uploadcare::Configuration))
220+
.and_return(double(original_file_url: 'https://ucarecdn.com/file.bin', cdn_url: nil))
221+
allow(service).to receive(:request).with('https://ucarecdn.com/file.bin').and_return('file-body')
222+
223+
expect(service.download('blob-key')).to eq('file-body')
224+
end
47225
end

spec/uploadcare/rails/api/rest/file_api_spec.rb

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,41 @@ module Rest
1212

1313
context 'when checking methods' do
1414
it 'responds to expected REST methods' do
15-
%i[get_files get_file delete_file store_file].each do |method|
15+
%i[get_files get_file delete_file store_file local_copy_file remote_copy_file store_files delete_files].each do |method|
1616
expect(subject).to respond_to(method)
1717
end
1818
end
1919
end
2020

21+
context 'when passing custom config' do
22+
let(:custom_config) { Uploadcare::Configuration.new(public_key: 'pk', secret_key: 'sk') }
23+
24+
it 'forwards config to get_file' do
25+
expect(Uploadcare::File).to receive(:info).with(uuid: 'uuid', config: custom_config)
26+
27+
subject.get_file('uuid', config: custom_config)
28+
end
29+
30+
it 'forwards config to get_files' do
31+
expect(Uploadcare::File).to receive(:list).with(options: { limit: 10 }, config: custom_config)
32+
33+
subject.get_files({ limit: 10 }, config: custom_config)
34+
end
35+
36+
it 'forwards config to local_copy_file' do
37+
expect(Uploadcare::File).to receive(:local_copy).with(source: 'source', options: { store: true }, config: custom_config)
38+
39+
subject.local_copy_file('source', { store: true }, config: custom_config)
40+
end
41+
42+
it 'forwards config to remote_copy_file' do
43+
expect(Uploadcare::File).to receive(:remote_copy)
44+
.with(source: 'source', target: 'target', options: { make_public: true }, config: custom_config)
45+
46+
subject.remote_copy_file('source', 'target', { make_public: true }, config: custom_config)
47+
end
48+
end
49+
2150
context 'when sending requests' do
2251
it 'gets file info' do
2352
VCR.use_cassette('file_api_get_file') do

spec/uploadcare/rails/api/upload/upload_api_spec.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,24 @@ module Upload
2020
end
2121
end
2222

23+
context 'when passing custom config' do
24+
let(:custom_config) { Uploadcare::Configuration.new(public_key: 'pk', secret_key: 'sk') }
25+
26+
it 'forwards config for upload_file' do
27+
file = ::File.open('spec/fixtures/kitten.jpeg')
28+
expect(Uploadcare::Uploader).to receive(:upload).with(object: file, config: custom_config, store: true)
29+
30+
subject.upload_file(file, { store: true }, config: custom_config)
31+
end
32+
33+
it 'forwards config for upload_files' do
34+
file = ::File.open('spec/fixtures/kitten.jpeg')
35+
expect(Uploadcare::Uploader).to receive(:upload).with(object: [file], config: custom_config, store: true)
36+
37+
subject.upload_files([file], { store: true }, config: custom_config)
38+
end
39+
end
40+
2341
context 'when sending requests' do
2442
context 'and when uploading a single file' do
2543
context 'and when uploading is successful' do

spec/uploadcare/rails/jobs/delete_file_job_spec.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,29 @@
1212
end.to change(ActiveJob::Base.queue_adapter.enqueued_jobs, :size).by(1)
1313
end
1414
end
15+
16+
describe '#perform' do
17+
it 'deletes a file with default config' do
18+
expect(Uploadcare::FileApi).to receive(:delete_file).with('file-uuid', config: Uploadcare.configuration)
19+
20+
described_class.new.perform('file-uuid')
21+
end
22+
23+
it 'deletes a file with provided config options' do
24+
expect(Uploadcare::FileApi).to receive(:delete_file) do |uuid, config:|
25+
expect(uuid).to eq('file-uuid')
26+
expect(config).to be_a(Uploadcare::Configuration)
27+
expect(config.public_key).to eq('pk')
28+
expect(config.secret_key).to eq('sk')
29+
end
30+
31+
described_class.new.perform('file-uuid', { public_key: 'pk', secret_key: 'sk' })
32+
end
33+
34+
it 'does nothing when uuid is nil' do
35+
expect(Uploadcare::FileApi).not_to receive(:delete_file)
36+
37+
described_class.new.perform(nil)
38+
end
39+
end
1540
end

spec/uploadcare/rails/jobs/store_file_job_spec.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,29 @@
1212
end.to change(ActiveJob::Base.queue_adapter.enqueued_jobs, :size).by(1)
1313
end
1414
end
15+
16+
describe '#perform' do
17+
it 'stores a file with default config' do
18+
expect(Uploadcare::FileApi).to receive(:store_file).with('file-uuid', config: Uploadcare.configuration)
19+
20+
described_class.new.perform('file-uuid')
21+
end
22+
23+
it 'stores a file with provided config options' do
24+
expect(Uploadcare::FileApi).to receive(:store_file) do |uuid, config:|
25+
expect(uuid).to eq('file-uuid')
26+
expect(config).to be_a(Uploadcare::Configuration)
27+
expect(config.public_key).to eq('pk')
28+
expect(config.secret_key).to eq('sk')
29+
end
30+
31+
described_class.new.perform('file-uuid', { public_key: 'pk', secret_key: 'sk' })
32+
end
33+
34+
it 'does nothing when uuid is nil' do
35+
expect(Uploadcare::FileApi).not_to receive(:store_file)
36+
37+
described_class.new.perform(nil)
38+
end
39+
end
1540
end

spec/uploadcare/rails/jobs/store_group_job_spec.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,29 @@
1212
end.to change(ActiveJob::Base.queue_adapter.enqueued_jobs, :size).by(1)
1313
end
1414
end
15+
16+
describe '#perform' do
17+
it 'stores a group with default config' do
18+
expect(Uploadcare::GroupApi).to receive(:store_group).with('group-id', config: Uploadcare.configuration)
19+
20+
described_class.new.perform('group-id')
21+
end
22+
23+
it 'stores a group with provided config options' do
24+
expect(Uploadcare::GroupApi).to receive(:store_group) do |group_id, config:|
25+
expect(group_id).to eq('group-id')
26+
expect(config).to be_a(Uploadcare::Configuration)
27+
expect(config.public_key).to eq('pk')
28+
expect(config.secret_key).to eq('sk')
29+
end
30+
31+
described_class.new.perform('group-id', { public_key: 'pk', secret_key: 'sk' })
32+
end
33+
34+
it 'does nothing when group id is nil' do
35+
expect(Uploadcare::GroupApi).not_to receive(:store_group)
36+
37+
described_class.new.perform(nil)
38+
end
39+
end
1540
end

0 commit comments

Comments
 (0)