carrierwave_direct 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +356 -0
- data/Rakefile +12 -0
- data/carrierwave_direct.gemspec +31 -0
- data/lib/carrierwave_direct.rb +44 -0
- data/lib/carrierwave_direct/action_view_extensions/form_helper.rb +36 -0
- data/lib/carrierwave_direct/form_builder.rb +17 -0
- data/lib/carrierwave_direct/locale/en.rb +20 -0
- data/lib/carrierwave_direct/locale/en.yml +7 -0
- data/lib/carrierwave_direct/mount.rb +38 -0
- data/lib/carrierwave_direct/orm/activerecord.rb +55 -0
- data/lib/carrierwave_direct/test/capybara_helpers.rb +58 -0
- data/lib/carrierwave_direct/test/helpers.rb +32 -0
- data/lib/carrierwave_direct/uploader.rb +142 -0
- data/lib/carrierwave_direct/uploader/configuration.rb +38 -0
- data/lib/carrierwave_direct/validations/active_model.rb +126 -0
- data/lib/carrierwave_direct/version.rb +6 -0
- data/spec/action_view_extensions/form_helper_spec.rb +28 -0
- data/spec/form_builder_spec.rb +59 -0
- data/spec/mount_spec.rb +57 -0
- data/spec/orm/activerecord_spec.rb +551 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/support/carrier_wave_config.rb +9 -0
- data/spec/support/direct_uploader.rb +4 -0
- data/spec/support/form_builder_helpers.rb +36 -0
- data/spec/support/global_helpers.rb +6 -0
- data/spec/support/model_helpers.rb +80 -0
- data/spec/support/mounted_class.rb +6 -0
- data/spec/support/uploader_helpers.rb +8 -0
- data/spec/support/view_helpers.rb +45 -0
- data/spec/test/capybara_helpers_spec.rb +160 -0
- data/spec/test/helpers_spec.rb +105 -0
- data/spec/uploader_spec.rb +461 -0
- metadata +168 -0
@@ -0,0 +1,461 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CarrierWaveDirect::Uploader do
|
4
|
+
include UploaderHelpers
|
5
|
+
include ModelHelpers
|
6
|
+
|
7
|
+
SAMPLE_DATA = {
|
8
|
+
:path => "upload_dir/bliind.exe",
|
9
|
+
:key => "some key",
|
10
|
+
:guid => "guid",
|
11
|
+
:store_dir => "store_dir",
|
12
|
+
:extension_regexp => "(avi)",
|
13
|
+
:url => "http://example.com/some_url",
|
14
|
+
:expiration => 60,
|
15
|
+
:max_file_size => 10485760,
|
16
|
+
:file_url => "http://anyurl.com/any_path/video_dir/filename.avi",
|
17
|
+
:mounted_model_name => "Porno",
|
18
|
+
:mounted_as => :video,
|
19
|
+
:filename => "filename",
|
20
|
+
:extension => ".avi",
|
21
|
+
:version => :thumb
|
22
|
+
}
|
23
|
+
|
24
|
+
SAMPLE_DATA.merge!(
|
25
|
+
:stored_filename_base => "#{sample(:guid)}/#{sample(:filename)}"
|
26
|
+
)
|
27
|
+
|
28
|
+
SAMPLE_DATA.merge!(
|
29
|
+
:stored_filename => "#{sample(:stored_filename_base)}#{sample(:extension)}",
|
30
|
+
:stored_version_filename => "#{sample(:stored_filename_base)}_#{sample(:version)}#{sample(:extension)}"
|
31
|
+
)
|
32
|
+
|
33
|
+
SAMPLE_DATA.merge!(
|
34
|
+
:s3_key => "#{sample(:store_dir)}/#{sample(:stored_filename)}"
|
35
|
+
)
|
36
|
+
|
37
|
+
SAMPLE_DATA.freeze
|
38
|
+
|
39
|
+
let(:subject) { DirectUploader.new }
|
40
|
+
let(:mounted_model) { mock(sample(:mounted_model_name)) }
|
41
|
+
let(:mounted_subject) { DirectUploader.new(mounted_model, sample(:mounted_as)) }
|
42
|
+
let(:direct_subject) { DirectUploader.new }
|
43
|
+
|
44
|
+
describe ".upload_expiration" do
|
45
|
+
it "should be 10 hours" do
|
46
|
+
subject.class.upload_expiration.should == 36000
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe ".max_file_size" do
|
51
|
+
it "should be 5 MB" do
|
52
|
+
subject.class.max_file_size.should == 5242880
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
DirectUploader.fog_credentials.keys.each do |key|
|
57
|
+
describe "##{key}" do
|
58
|
+
it "should return the #{key.to_s.capitalize}" do
|
59
|
+
subject.send(key).should == DirectUploader.fog_credentials[key]
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should not be nil" do
|
63
|
+
subject.send(key).should_not be_nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
it_should_have_accessor(:success_action_redirect)
|
69
|
+
|
70
|
+
describe "#key=" do
|
71
|
+
before { subject.key = sample(:key) }
|
72
|
+
|
73
|
+
it "should set the key" do
|
74
|
+
subject.key.should == sample(:key)
|
75
|
+
end
|
76
|
+
|
77
|
+
context "the versions keys" do
|
78
|
+
it "should == this subject's key" do
|
79
|
+
subject.versions.each do |name, version_subject|
|
80
|
+
version_subject.key.should == subject.key
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#key" do
|
87
|
+
context "where the key is not set" do
|
88
|
+
before do
|
89
|
+
mounted_subject.key = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should return '*/\#\{guid\}/${filename}'" do
|
93
|
+
mounted_subject.key.should =~ /#{GUID_REGEXP}\/\$\{filename\}$/
|
94
|
+
end
|
95
|
+
|
96
|
+
context "and #store_dir returns '#{sample(:store_dir)}'" do
|
97
|
+
before do
|
98
|
+
mounted_subject.stub(:store_dir).and_return(sample(:store_dir))
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should return '#{sample(:store_dir)}/\#\{guid\}/${filename}'" do
|
102
|
+
mounted_subject.key.should =~ /^#{sample(:store_dir)}\/#{GUID_REGEXP}\/\$\{filename\}$/
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "where the key is set to '#{sample(:key)}'" do
|
108
|
+
before { subject.key = sample(:key) }
|
109
|
+
|
110
|
+
it "should return '#{sample(:key)}'" do
|
111
|
+
subject.key.should == sample(:key)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "#url_scheme_white_list" do
|
117
|
+
it "should return nil" do
|
118
|
+
subject.url_scheme_white_list.should be_nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "#key_regexp" do
|
123
|
+
it "should return a regexp" do
|
124
|
+
subject.key_regexp.should be_a(Regexp)
|
125
|
+
end
|
126
|
+
|
127
|
+
context "where #store_dir returns '#{sample(:store_dir)}'" do
|
128
|
+
before do
|
129
|
+
subject.stub(:store_dir).and_return(sample(:store_dir))
|
130
|
+
end
|
131
|
+
|
132
|
+
context "and #extension_regexp returns '#{sample(:extension_regexp)}'" do
|
133
|
+
before do
|
134
|
+
subject.stub(:extension_regexp).and_return(sample(:extension_regexp))
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should return /\\A#{sample(:store_dir)}\\/#{GUID_REGEXP}\\/.+\\.#{sample(:extension_regexp)}\\z/" do
|
138
|
+
subject.key_regexp.should == /\A#{sample(:store_dir)}\/#{GUID_REGEXP}\/.+\.#{sample(:extension_regexp)}\z/
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "#extension_regexp" do
|
145
|
+
shared_examples_for "a globally allowed file extension" do
|
146
|
+
it "should return '\\w+'" do
|
147
|
+
subject.extension_regexp.should == "\\w+"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should return a string" do
|
152
|
+
subject.extension_regexp.should be_a(String)
|
153
|
+
end
|
154
|
+
|
155
|
+
context "where #extension_white_list returns nil" do
|
156
|
+
before do
|
157
|
+
subject.stub(:extension_white_list).and_return(nil)
|
158
|
+
end
|
159
|
+
|
160
|
+
it_should_behave_like "a globally allowed file extension"
|
161
|
+
end
|
162
|
+
|
163
|
+
context "where #extension_white_list returns []" do
|
164
|
+
before do
|
165
|
+
subject.stub(:extension_white_list).and_return([])
|
166
|
+
end
|
167
|
+
|
168
|
+
it_should_behave_like "a globally allowed file extension"
|
169
|
+
end
|
170
|
+
|
171
|
+
context "where #extension_white_list returns ['exe', 'bmp']" do
|
172
|
+
|
173
|
+
before do
|
174
|
+
subject.stub(:extension_white_list).and_return(%w{exe bmp})
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should return '(exe|bmp)'" do
|
178
|
+
subject.extension_regexp.should == "(exe|bmp)"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe "#has_key?" do
|
184
|
+
context "a key has not been set" do
|
185
|
+
|
186
|
+
it "should return false" do
|
187
|
+
subject.should_not have_key
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
context "the key has been autogenerated" do
|
192
|
+
before { subject.key }
|
193
|
+
|
194
|
+
it "should return false" do
|
195
|
+
subject.should_not have_key
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
context "the key has been set" do
|
200
|
+
before { subject.key = sample_key }
|
201
|
+
|
202
|
+
it "should return true" do
|
203
|
+
subject.should have_key
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe "#direct_fog_url" do
|
209
|
+
it "should return the result from CarrierWave::Storage::Fog::File#public_url" do
|
210
|
+
subject.direct_fog_url.should == CarrierWave::Storage::Fog::File.new(
|
211
|
+
subject, nil, nil
|
212
|
+
).public_url
|
213
|
+
end
|
214
|
+
|
215
|
+
context ":with_path => true" do
|
216
|
+
context "#key is set to '#{sample(:path)}'" do
|
217
|
+
before { subject.key = sample(:path) }
|
218
|
+
|
219
|
+
it "should return the full url with '/#{sample(:path)}' as the path" do
|
220
|
+
URI.parse(subject.direct_fog_url(:with_path => true)).path.should == "/#{sample(:path)}"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
describe "#persisted?" do
|
227
|
+
it "should return false" do
|
228
|
+
subject.should_not be_persisted
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
describe "#filename" do
|
233
|
+
context "key is set to '#{sample(:s3_key)}'" do
|
234
|
+
before { mounted_subject.key = sample(:s3_key) }
|
235
|
+
|
236
|
+
it "should return '#{sample(:stored_filename)}'" do
|
237
|
+
mounted_subject.filename.should == sample(:stored_filename)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
context "key is set to '#{sample(:key)}'" do
|
242
|
+
before { subject.key = sample(:key) }
|
243
|
+
|
244
|
+
it "should return '#{sample(:key)}'" do
|
245
|
+
subject.filename.should == sample(:key)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
context "key is not set" do
|
250
|
+
context "but the model's remote #{sample(:mounted_as)} url is: '#{sample(:file_url)}'" do
|
251
|
+
|
252
|
+
before do
|
253
|
+
mounted_subject.model.stub(
|
254
|
+
"remote_#{mounted_subject.mounted_as}_url"
|
255
|
+
).and_return(sample(:file_url))
|
256
|
+
end
|
257
|
+
|
258
|
+
it "should set the key to contain '#{File.basename(sample(:file_url))}'" do
|
259
|
+
mounted_subject.filename
|
260
|
+
mounted_subject.key.should =~ /#{Regexp.escape(File.basename(sample(:file_url)))}$/
|
261
|
+
end
|
262
|
+
|
263
|
+
it "should return a filename based off the key and remote url" do
|
264
|
+
filename = mounted_subject.filename
|
265
|
+
mounted_subject.key.should =~ /#{Regexp.escape(filename)}$/
|
266
|
+
end
|
267
|
+
|
268
|
+
# this ensures that the version subject keys are updated
|
269
|
+
# see spec for key= for more details
|
270
|
+
it "should set the key explicitly" do
|
271
|
+
mounted_subject.should_receive(:key=)
|
272
|
+
mounted_subject.filename
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
context "and the model's remote #{sample(:mounted_as)} url is blank" do
|
277
|
+
before do
|
278
|
+
mounted_model.stub(
|
279
|
+
"remote_#{mounted_subject.mounted_as}_url"
|
280
|
+
).and_return nil
|
281
|
+
end
|
282
|
+
|
283
|
+
it "should return nil" do
|
284
|
+
mounted_subject.filename.should be_nil
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
describe "#acl" do
|
291
|
+
it "should return the sanitized s3 access policy" do
|
292
|
+
subject.acl.should == subject.s3_access_policy.to_s.gsub("_", "-")
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# http://aws.amazon.com/articles/1434?_encoding=UTF8
|
297
|
+
describe "#policy" do
|
298
|
+
def decoded_policy(options = {})
|
299
|
+
instance = options.delete(:subject) || subject
|
300
|
+
JSON.parse(Base64.decode64(instance.policy(options)))
|
301
|
+
end
|
302
|
+
|
303
|
+
it "should return Base64-encoded JSON" do
|
304
|
+
decoded_policy.should be_a(Hash)
|
305
|
+
end
|
306
|
+
|
307
|
+
it "should not contain any new lines" do
|
308
|
+
subject.policy.should_not include("\n")
|
309
|
+
end
|
310
|
+
|
311
|
+
context "expiration" do
|
312
|
+
def expiration(options = {})
|
313
|
+
decoded_policy(options)["expiration"]
|
314
|
+
end
|
315
|
+
|
316
|
+
# Stolen from rails
|
317
|
+
def string_to_time(str)
|
318
|
+
d = ::Date._parse(str, false).values_at(
|
319
|
+
:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset
|
320
|
+
).map { |arg| arg || 0 }
|
321
|
+
d[6] *= 1000000
|
322
|
+
Time.utc(*d[0..6]) - d[7]
|
323
|
+
end
|
324
|
+
|
325
|
+
|
326
|
+
def have_expiration(expires_in = DirectUploader.upload_expiration)
|
327
|
+
eql(
|
328
|
+
string_to_time(
|
329
|
+
JSON.parse({
|
330
|
+
"expiry" => Time.now + expires_in
|
331
|
+
}.to_json)["expiry"]
|
332
|
+
)
|
333
|
+
)
|
334
|
+
end
|
335
|
+
|
336
|
+
it "should be #{DirectUploader.upload_expiration / 3600} hours from now" do
|
337
|
+
Timecop.freeze(Time.now) do
|
338
|
+
string_to_time(expiration).should have_expiration
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
it "should be #{sample(:expiration) / 60 } minutes from now when passing {:expiration => #{sample(:expiration)}}" do
|
343
|
+
Timecop.freeze(Time.now) do
|
344
|
+
string_to_time(expiration(:expiration => sample(:expiration))).should have_expiration(sample(:expiration))
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
context "conditions" do
|
350
|
+
def conditions(options = {})
|
351
|
+
decoded_policy(options)["conditions"]
|
352
|
+
end
|
353
|
+
|
354
|
+
def have_condition(field, value = nil)
|
355
|
+
field.is_a?(Hash) ? include(field) : include(["starts-with", "$#{field}", value.to_s])
|
356
|
+
end
|
357
|
+
|
358
|
+
context "should include" do
|
359
|
+
# Rails form builder conditions
|
360
|
+
it "'utf8'" do
|
361
|
+
conditions.should have_condition(:utf8)
|
362
|
+
end
|
363
|
+
|
364
|
+
# S3 conditions
|
365
|
+
it "'key'" do
|
366
|
+
mounted_subject.stub(:store_dir).and_return(sample(:s3_key))
|
367
|
+
mounted_subject.key
|
368
|
+
conditions(
|
369
|
+
:subject => mounted_subject
|
370
|
+
).should have_condition(:key, sample(:s3_key))
|
371
|
+
end
|
372
|
+
|
373
|
+
it "'bucket'" do
|
374
|
+
conditions.should have_condition("bucket" => subject.fog_directory)
|
375
|
+
end
|
376
|
+
|
377
|
+
it "'acl'" do
|
378
|
+
conditions.should have_condition("acl" => subject.acl)
|
379
|
+
end
|
380
|
+
|
381
|
+
it "'success_action_redirect'" do
|
382
|
+
subject.success_action_redirect = "http://example.com/some_url"
|
383
|
+
conditions.should have_condition("success_action_redirect" => "http://example.com/some_url")
|
384
|
+
end
|
385
|
+
|
386
|
+
context "'content-length-range of'" do
|
387
|
+
|
388
|
+
def have_content_length_range(max_file_size = DirectUploader.max_file_size)
|
389
|
+
include(["content-length-range", 1, max_file_size])
|
390
|
+
end
|
391
|
+
|
392
|
+
it "#{DirectUploader.max_file_size} bytes" do
|
393
|
+
conditions.should have_content_length_range
|
394
|
+
end
|
395
|
+
|
396
|
+
it "#{sample(:max_file_size)} bytes when passing {:max_file_size => #{sample(:max_file_size)}}" do
|
397
|
+
conditions(
|
398
|
+
:max_file_size => sample(:max_file_size)
|
399
|
+
).should have_content_length_range(sample(:max_file_size))
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
describe "#signature" do
|
407
|
+
it "should not contain any new lines" do
|
408
|
+
subject.signature.should_not include("\n")
|
409
|
+
end
|
410
|
+
|
411
|
+
it "should return a base64 encoded 'sha1' hash of the secret key and policy document" do
|
412
|
+
Base64.decode64(subject.signature).should == OpenSSL::HMAC.digest(
|
413
|
+
OpenSSL::Digest::Digest.new('sha1'),
|
414
|
+
subject.aws_secret_access_key, subject.policy
|
415
|
+
)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
# note that 'video' is hardcoded into the MountedClass support file
|
420
|
+
# so changing the sample will cause the tests to fail
|
421
|
+
context "a class has a '#{sample(:mounted_as)}' mounted" do
|
422
|
+
describe "#{sample(:mounted_as).capitalize}Uploader" do
|
423
|
+
describe "##{sample(:mounted_as)}" do
|
424
|
+
it "should be defined" do
|
425
|
+
direct_subject.should be_respond_to(sample(:mounted_as))
|
426
|
+
end
|
427
|
+
|
428
|
+
it "should return itself" do
|
429
|
+
direct_subject.send(sample(:mounted_as)).should == direct_subject
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
context "has a '#{sample(:version)}' version" do
|
434
|
+
let(:video_subject) { MountedClass.new.video }
|
435
|
+
|
436
|
+
before do
|
437
|
+
DirectUploader.version(sample(:version))
|
438
|
+
end
|
439
|
+
|
440
|
+
context "and the key is '#{sample(:s3_key)}'" do
|
441
|
+
before do
|
442
|
+
video_subject.key = sample(:s3_key)
|
443
|
+
end
|
444
|
+
|
445
|
+
context "the store path" do
|
446
|
+
let(:store_path) { video_subject.send(sample(:version)).store_path }
|
447
|
+
|
448
|
+
it "should be like '#{sample(:stored_version_filename)}'" do
|
449
|
+
store_path.should =~ /#{sample(:stored_version_filename)}$/
|
450
|
+
end
|
451
|
+
|
452
|
+
it "should not be like '#{sample(:version)}_#{sample(:stored_filename_base)}'" do
|
453
|
+
store_path.should_not =~ /#{sample(:version)}_#{sample(:stored_filename_base)}/
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|