carrierwave_direct 0.0.16 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +14 -9
  3. data/Changelog.md +45 -1
  4. data/README.md +37 -23
  5. data/carrierwave_direct.gemspec +4 -3
  6. data/gemfiles/{3.2.gemfile → 5.1.gemfile} +3 -3
  7. data/gemfiles/{4.0.gemfile → 5.2.gemfile} +3 -3
  8. data/gemfiles/{4.1.gemfile → 6.0.gemfile} +3 -3
  9. data/gemfiles/6.1.gemfile +13 -0
  10. data/lib/carrierwave_direct/action_view_extensions/form_helper.rb +1 -1
  11. data/lib/carrierwave_direct/form_builder.rb +30 -12
  12. data/lib/carrierwave_direct/mount.rb +1 -11
  13. data/lib/carrierwave_direct/policies/aws4_hmac_sha256.rb +93 -0
  14. data/lib/carrierwave_direct/policies/aws_base64_sha1.rb +57 -0
  15. data/lib/carrierwave_direct/policies/base.rb +21 -0
  16. data/lib/carrierwave_direct/test/capybara_helpers.rb +3 -3
  17. data/lib/carrierwave_direct/test/helpers.rb +1 -1
  18. data/lib/carrierwave_direct/uploader.rb +55 -56
  19. data/lib/carrierwave_direct/validations/active_model.rb +2 -2
  20. data/lib/carrierwave_direct/version.rb +1 -1
  21. data/spec/form_builder_spec.rb +24 -15
  22. data/spec/mount_spec.rb +2 -2
  23. data/spec/orm/activerecord_spec.rb +11 -7
  24. data/spec/orm/indirect_activerecord_spec.rb +7 -1
  25. data/spec/policies/aws4_hmac_sha256_spec.rb +243 -0
  26. data/spec/policies/aws_base64_sha1_spec.rb +229 -0
  27. data/spec/spec_helper.rb +5 -0
  28. data/spec/support/carrier_wave_config.rb +1 -0
  29. data/spec/test/capybara_helpers_spec.rb +4 -4
  30. data/spec/test/helpers_spec.rb +3 -3
  31. data/spec/uploader_spec.rb +20 -26
  32. metadata +36 -18
  33. data/lib/carrierwave_direct/uploader/direct_url.rb +0 -15
  34. data/spec/uploader/direct_url_spec.rb +0 -26
data/spec/mount_spec.rb CHANGED
@@ -18,12 +18,12 @@ describe CarrierWaveDirect::Mount do
18
18
 
19
19
  describe "#has_video_upload?" do
20
20
  it "returns false when video does not have a key" do
21
- allow(subject.video).to receive(:has_key?).and_return(false)
21
+ subject.video.key = nil
22
22
  expect(subject).to_not have_video_upload
23
23
  end
24
24
 
25
25
  it "returns true when video has a key" do
26
- allow(subject.video).to receive(:has_key?).and_return(true)
26
+ subject.video.key = sample(:s3_key)
27
27
  expect(subject).to have_video_upload
28
28
  end
29
29
  end
@@ -9,8 +9,12 @@ describe CarrierWaveDirect::ActiveRecord do
9
9
  :adapter => 'sqlite3',
10
10
  :database => ':memory:'
11
11
  }
12
-
13
- class TestMigration < ActiveRecord::Migration
12
+ if ActiveRecord::VERSION::MAJOR >= 5
13
+ migration_class = ::ActiveRecord::Migration[5.0]
14
+ else
15
+ migration_class = ::ActiveRecord::Migration
16
+ end
17
+ class TestMigration < migration_class
14
18
  def self.up
15
19
  create_table :parties, :force => true do |t|
16
20
  t.column :video, :string
@@ -100,8 +104,8 @@ describe CarrierWaveDirect::ActiveRecord do
100
104
  messages = I18n.t("errors.messages.carrierwave_direct_filename_invalid")
101
105
 
102
106
  if i18n_options
103
- if i18n_options[:extension_white_list]
104
- extensions = i18n_options[:extension_white_list].to_sentence
107
+ if i18n_options[:extension_allowlist]
108
+ extensions = i18n_options[:extension_allowlist].to_sentence
105
109
  messages += I18n.t("errors.messages.carrierwave_direct_allowed_extensions", :extensions => extensions)
106
110
  end
107
111
 
@@ -240,7 +244,7 @@ describe CarrierWaveDirect::ActiveRecord do
240
244
 
241
245
  context "where the uploader has an extension white list" do
242
246
  before do
243
- subject.video.stub(:extension_white_list).and_return(%w{avi mp4})
247
+ subject.video.stub(:extension_allowlist).and_return(%w{avi mp4})
244
248
  end
245
249
 
246
250
  context "and the uploaded file's extension is included in the list" do
@@ -298,7 +302,7 @@ describe CarrierWaveDirect::ActiveRecord do
298
302
  context "on create" do
299
303
  context "where the uploader has an extension white list" do
300
304
  before do
301
- subject.video.stub(:extension_white_list).and_return(%w{avi mp4})
305
+ subject.video.stub(:extension_allowlist).and_return(%w{avi mp4})
302
306
  end
303
307
 
304
308
  context "and the url's extension is included in the list" do
@@ -321,7 +325,7 @@ describe CarrierWaveDirect::ActiveRecord do
321
325
  end
322
326
 
323
327
  it_should_behave_like "a remote net url i18n error message" do
324
- let(:i18n_options) { {:extension_white_list => %w{avi mp4} } }
328
+ let(:i18n_options) { {:extension_allowlist => %w{avi mp4} } }
325
329
  end
326
330
 
327
331
  it "should include the white listed extensions in the error message" do
@@ -10,7 +10,13 @@ describe CarrierWave::ActiveRecord do
10
10
  :database => ':memory:'
11
11
  }
12
12
 
13
- class OtherTestMigration < ActiveRecord::Migration
13
+ if ActiveRecord::VERSION::MAJOR >= 5
14
+ migration_class = ::ActiveRecord::Migration[5.0]
15
+ else
16
+ migration_class = ::ActiveRecord::Migration
17
+ end
18
+
19
+ class OtherTestMigration < migration_class
14
20
  def self.up
15
21
  create_table :other_parties, :force => true do |t|
16
22
  t.column :video, :string
@@ -0,0 +1,243 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'data/sample_data'
4
+
5
+ describe CarrierWaveDirect::Policies::Aws4HmacSha256 do
6
+ let(:subject) { described_class.new(uploader) }
7
+ let(:uploader) { DirectUploader.new }
8
+ let(:mounted_model) { MountedClass.new }
9
+ let(:mounted_subject) { DirectUploader.new(mounted_model, sample(:mounted_as)) }
10
+
11
+ describe "#direct_fog_hash" do
12
+ it "should return the policy hash" do
13
+ expect(subject.direct_fog_hash.keys).to eq([:key, :acl, :policy, :"X-Amz-Signature", :"X-Amz-Credential", :"X-Amz-Algorithm", :"X-Amz-Date", :uri])
14
+ expect(subject.direct_fog_hash[:acl]).to eq 'public-read'
15
+ expect(subject.direct_fog_hash[:key]).to match /\$\{filename\}/
16
+ expect(subject.direct_fog_hash[:"X-Amz-Algorithm"]).to eq "AWS4-HMAC-SHA256"
17
+ expect(subject.direct_fog_hash[:uri]).to eq "https://s3.amazonaws.com/AWS_FOG_DIRECTORY/"
18
+ end
19
+ end
20
+
21
+ # http://aws.amazon.com/articles/1434?_encoding=UTF8
22
+ #http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html
23
+ describe "#policy" do
24
+ def decoded_policy(options = {}, &block)
25
+ instance = options.delete(:subject) || subject
26
+ JSON.parse(Base64.decode64(instance.policy(options, &block)))
27
+ end
28
+
29
+ context "policy is given a block" do
30
+ it "should yield the options to the block" do
31
+ number = 0
32
+ subject.policy do |conditions|
33
+ number+=1
34
+ end
35
+ expect(number).to eq 1
36
+ end
37
+ it "should include new options in the conditions" do
38
+ policy = subject.policy do |conditions|
39
+ conditions << {"x-aws-storage-class" => "STANDARD"}
40
+ end
41
+ decoded = JSON.parse(Base64.decode64(policy))
42
+ expect(decoded['conditions'].last['x-aws-storage-class']).to eq "STANDARD"
43
+ end
44
+ end
45
+
46
+ it "should return Base64-encoded JSON" do
47
+ expect(decoded_policy).to be_a(Hash)
48
+ end
49
+
50
+ it "should not contain any new lines" do
51
+ expect(subject.policy).to_not include("\n")
52
+ end
53
+
54
+ it "should be cached" do
55
+ Timecop.freeze(Time.now) do
56
+ @policy_now = subject.policy
57
+ end
58
+ Timecop.freeze(1.second.from_now) do
59
+ @policy_later = subject.policy
60
+ end
61
+ expect(@policy_later).to eql @policy_now
62
+ end
63
+
64
+ context "expiration" do
65
+ def expiration(options = {})
66
+ decoded_policy(options)["expiration"]
67
+ end
68
+
69
+ # JSON times have no seconds, so accept upto one second inaccuracy
70
+ def have_expiration(expires_in = DirectUploader.upload_expiration)
71
+ be_within(1.second).of (Time.now + expires_in)
72
+ end
73
+
74
+ it "should be valid ISO8601 and not use default Time#to_json" do
75
+ Time.any_instance.stub(:to_json) { '"Invalid time"' } # JSON gem
76
+ Time.any_instance.stub(:as_json) { '"Invalid time"' } # Active Support
77
+ expect { Time.iso8601(expiration) }.to_not raise_error
78
+ end
79
+
80
+ it "should be #{DirectUploader.upload_expiration / 3600} hours from now" do
81
+ Timecop.freeze(Time.now) do
82
+ expect(Time.parse(expiration)).to have_expiration
83
+ end
84
+ end
85
+
86
+ it "should be encoded as a utc time" do
87
+ expect(Time.parse(expiration)).to be_utc
88
+ end
89
+
90
+ it "should be #{sample(:expiration) / 60 } minutes from now when passing {:expiration => #{sample(:expiration)}}" do
91
+ Timecop.freeze(Time.now) do
92
+ expect(Time.parse(expiration(:expiration => sample(:expiration)))).to have_expiration(sample(:expiration))
93
+ end
94
+ end
95
+ end
96
+
97
+ context "conditions" do
98
+ def conditions(options = {})
99
+ decoded_policy(options)["conditions"]
100
+ end
101
+
102
+ def have_condition(field, value = nil)
103
+ field.is_a?(Hash) ? include(field) : include(["starts-with", "$#{field}", value.to_s])
104
+ end
105
+
106
+ context "should include" do
107
+ it "'utf8' if enforce_ut8 is set" do
108
+ expect(conditions(enforce_utf8: true)).to have_condition(:utf8)
109
+ end
110
+
111
+ it "'utf8' if enforce_ut8 is set" do
112
+ expect(conditions).to_not have_condition(:utf8)
113
+ end
114
+
115
+ # S3 conditions
116
+ it "'key'" do
117
+ allow(mounted_subject).to receive(:key).and_return(sample(:s3_key))
118
+ expect(conditions(
119
+ :subject => mounted_subject
120
+ )).to have_condition(:key, sample(:s3_key))
121
+ end
122
+
123
+ it "'key' without FILENAME_WILDCARD" do
124
+ expect(conditions(
125
+ :subject => mounted_subject
126
+ )).to have_condition(:key, mounted_subject.key.sub("${filename}", ""))
127
+ end
128
+
129
+ it "'bucket'" do
130
+ expect(conditions).to have_condition("bucket" => uploader.fog_directory)
131
+ end
132
+
133
+ it "'acl'" do
134
+ expect(conditions).to have_condition("acl" => uploader.acl)
135
+ end
136
+
137
+ it "'success_action_redirect'" do
138
+ uploader.success_action_redirect = "http://example.com/some_url"
139
+ expect(conditions).to have_condition("success_action_redirect" => "http://example.com/some_url")
140
+ end
141
+
142
+ it "does not have 'content-type' when will_include_content_type is false" do
143
+ allow(uploader.class).to receive(:will_include_content_type).and_return(false)
144
+ expect(conditions).to_not have_condition('Content-Type')
145
+ end
146
+
147
+ it "has 'content-type' when will_include_content_type is true" do
148
+ allow(uploader.class).to receive(:will_include_content_type).and_return(true)
149
+ expect(conditions).to have_condition('Content-Type')
150
+ end
151
+
152
+ context 'when use_action_status is true' do
153
+ before(:all) do
154
+ DirectUploader.use_action_status = true
155
+ end
156
+
157
+ after(:all) do
158
+ DirectUploader.use_action_status = false
159
+ end
160
+
161
+ it "'success_action_status'" do
162
+ uploader.success_action_status = '200'
163
+ expect(conditions).to have_condition("success_action_status" => "200")
164
+ end
165
+
166
+ it "does not have 'success_action_redirect'" do
167
+ uploader.success_action_redirect = "http://example.com/some_url"
168
+ expect(conditions).to_not have_condition("success_action_redirect" => "http://example.com/some_url")
169
+ end
170
+ end
171
+
172
+ context "'content-length-range of'" do
173
+ def have_content_length_range(options = {})
174
+ include([
175
+ "content-length-range",
176
+ options[:min_file_size] || DirectUploader.min_file_size,
177
+ options[:max_file_size] || DirectUploader.max_file_size,
178
+ ])
179
+ end
180
+
181
+ it "#{DirectUploader.min_file_size} bytes" do
182
+ expect(conditions).to have_content_length_range
183
+ end
184
+
185
+ it "#{DirectUploader.max_file_size} bytes" do
186
+ expect(conditions).to have_content_length_range
187
+ end
188
+
189
+ it "#{sample(:min_file_size)} bytes when passing {:min_file_size => #{sample(:min_file_size)}}" do
190
+ expect(conditions(
191
+ :min_file_size => sample(:min_file_size)
192
+ )).to have_content_length_range(:min_file_size => sample(:min_file_size))
193
+ end
194
+
195
+ it "#{sample(:max_file_size)} bytes when passing {:max_file_size => #{sample(:max_file_size)}}" do
196
+ expect(conditions(
197
+ :max_file_size => sample(:max_file_size)
198
+ )).to have_content_length_range(:max_file_size => sample(:max_file_size))
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ describe "clear!" do
206
+ it "should reset the cached policy string" do
207
+ Timecop.freeze(Time.now) do
208
+ @policy_now = subject.policy
209
+ end
210
+ subject.clear!
211
+
212
+ Timecop.freeze(1.second.from_now) do
213
+ @policy_after_reset = subject.policy
214
+ end
215
+ expect(@policy_after_reset).not_to eql @policy_now
216
+ end
217
+ end
218
+
219
+ describe "#signature" do
220
+ it "should not contain any new lines" do
221
+ expect(subject.signature).to_not include("\n")
222
+ end
223
+
224
+ it "should return a HMAC hexdigest encoded 'sha256' hash of the secret key and policy document" do
225
+ expect(subject.signature).to eq OpenSSL::HMAC.hexdigest(
226
+ OpenSSL::Digest.new('sha256'),
227
+ subject.signing_key, subject.policy
228
+ )
229
+ end
230
+ end
231
+ #http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html
232
+ describe "#signature_key" do
233
+ it "should include correct signature_key elements" do
234
+ kDate = OpenSSL::HMAC.digest('sha256', "AWS4" + uploader.aws_secret_access_key, Time.now.utc.strftime("%Y%m%d"))
235
+ kRegion = OpenSSL::HMAC.digest('sha256', kDate, uploader.region)
236
+ kService = OpenSSL::HMAC.digest('sha256', kRegion, 's3')
237
+ kSigning = OpenSSL::HMAC.digest('sha256', kService, "aws4_request")
238
+
239
+ expect(subject.signing_key).to eq (kSigning)
240
+ end
241
+ end
242
+ end
243
+
@@ -0,0 +1,229 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'data/sample_data'
4
+
5
+ describe CarrierWaveDirect::Policies::AwsBase64Sha1 do
6
+ let(:subject) { described_class.new(uploader) }
7
+ let(:uploader) { DirectUploader.new }
8
+ let(:mounted_model) { MountedClass.new }
9
+ let(:mounted_subject) { DirectUploader.new(mounted_model, sample(:mounted_as)) }
10
+
11
+ describe "#direct_fog_hash" do
12
+ it "should return the policy hash" do
13
+ expect(subject.direct_fog_hash.keys).to eq([:key, :AWSAccessKeyId, :acl, :policy, :signature, :uri])
14
+ expect(subject.direct_fog_hash[:acl]).to eq 'public-read'
15
+ expect(subject.direct_fog_hash[:key]).to match /\$\{filename\}/
16
+ expect(subject.direct_fog_hash[:uri]).to eq "https://s3.amazonaws.com/AWS_FOG_DIRECTORY/"
17
+ end
18
+ end
19
+
20
+ describe "#policy" do
21
+ def decoded_policy(options = {}, &block)
22
+ instance = options.delete(:subject) || subject
23
+ JSON.parse(Base64.decode64(instance.policy(options, &block)))
24
+ end
25
+
26
+ context "policy is given a block" do
27
+ it "should yield the options to the block" do
28
+ number = 0
29
+ subject.policy do |conditions|
30
+ number+=1
31
+ end
32
+ expect(number).to eq 1
33
+ end
34
+ it "should include new options in the conditions" do
35
+ policy = subject.policy do |conditions|
36
+ conditions << {"x-aws-storage-class" => "STANDARD"}
37
+ end
38
+ decoded = JSON.parse(Base64.decode64(policy))
39
+ expect(decoded['conditions'].last['x-aws-storage-class']).to eq "STANDARD"
40
+ end
41
+ end
42
+
43
+ it "should return Base64-encoded JSON" do
44
+ expect(decoded_policy).to be_a(Hash)
45
+ end
46
+
47
+ it "should not contain any new lines" do
48
+ expect(subject.policy).to_not include("\n")
49
+ end
50
+
51
+ it "should be cached" do
52
+ Timecop.freeze(Time.now) do
53
+ @policy_now = subject.policy
54
+ end
55
+ Timecop.freeze(1.second.from_now) do
56
+ @policy_later = subject.policy
57
+ end
58
+ expect(@policy_later).to eql @policy_now
59
+ end
60
+
61
+ context "expiration" do
62
+ def expiration(options = {})
63
+ decoded_policy(options)["expiration"]
64
+ end
65
+
66
+ # JSON times have no seconds, so accept upto one second inaccuracy
67
+ def have_expiration(expires_in = DirectUploader.upload_expiration)
68
+ be_within(1.second).of (Time.now + expires_in)
69
+ end
70
+
71
+ it "should be valid ISO8601 and not use default Time#to_json" do
72
+ Time.any_instance.stub(:to_json) { '"Invalid time"' } # JSON gem
73
+ Time.any_instance.stub(:as_json) { '"Invalid time"' } # Active Support
74
+ expect { Time.iso8601(expiration) }.to_not raise_error
75
+ end
76
+
77
+ it "should be #{DirectUploader.upload_expiration / 3600} hours from now" do
78
+ Timecop.freeze(Time.now) do
79
+ expect(Time.parse(expiration)).to have_expiration
80
+ end
81
+ end
82
+
83
+ it "should be encoded as a utc time" do
84
+ expect(Time.parse(expiration)).to be_utc
85
+ end
86
+
87
+ it "should be #{sample(:expiration) / 60 } minutes from now when passing {:expiration => #{sample(:expiration)}}" do
88
+ Timecop.freeze(Time.now) do
89
+ expect(Time.parse(expiration(:expiration => sample(:expiration)))).to have_expiration(sample(:expiration))
90
+ end
91
+ end
92
+ end
93
+
94
+ context "conditions" do
95
+ def conditions(options = {})
96
+ decoded_policy(options)["conditions"]
97
+ end
98
+
99
+ def have_condition(field, value = nil)
100
+ field.is_a?(Hash) ? include(field) : include(["starts-with", "$#{field}", value.to_s])
101
+ end
102
+
103
+ context "should include" do
104
+ it "'utf8' if enforce_ut8 is set" do
105
+ expect(conditions(enforce_utf8: true)).to have_condition(:utf8)
106
+ end
107
+
108
+ it "'utf8' if enforce_ut8 is set" do
109
+ expect(conditions).to_not have_condition(:utf8)
110
+ end
111
+
112
+ # S3 conditions
113
+ it "'key'" do
114
+ allow(mounted_subject).to receive(:key).and_return(sample(:s3_key))
115
+ expect(conditions(
116
+ :subject => mounted_subject
117
+ )).to have_condition(:key, sample(:s3_key))
118
+ end
119
+
120
+ it "'key' without FILENAME_WILDCARD" do
121
+ expect(conditions(
122
+ :subject => mounted_subject
123
+ )).to have_condition(:key, mounted_subject.key.sub("${filename}", ""))
124
+ end
125
+
126
+ it "'bucket'" do
127
+ expect(conditions).to have_condition("bucket" => uploader.fog_directory)
128
+ end
129
+
130
+ it "'acl'" do
131
+ expect(conditions).to have_condition("acl" => uploader.acl)
132
+ end
133
+
134
+ it "'success_action_redirect'" do
135
+ uploader.success_action_redirect = "http://example.com/some_url"
136
+ expect(conditions).to have_condition("success_action_redirect" => "http://example.com/some_url")
137
+ end
138
+
139
+ it "does not have 'content-type' when will_include_content_type is false" do
140
+ allow(uploader.class).to receive(:will_include_content_type).and_return(false)
141
+ expect(conditions).to_not have_condition('Content-Type')
142
+ end
143
+
144
+ it "has 'content-type' when will_include_content_type is true" do
145
+ allow(uploader.class).to receive(:will_include_content_type).and_return(true)
146
+ expect(conditions).to have_condition('Content-Type')
147
+ end
148
+
149
+ context 'when use_action_status is true' do
150
+ before(:all) do
151
+ DirectUploader.use_action_status = true
152
+ end
153
+
154
+ after(:all) do
155
+ DirectUploader.use_action_status = false
156
+ end
157
+
158
+ it "'success_action_status'" do
159
+ uploader.success_action_status = '200'
160
+ expect(conditions).to have_condition("success_action_status" => "200")
161
+ end
162
+
163
+ it "does not have 'success_action_redirect'" do
164
+ uploader.success_action_redirect = "http://example.com/some_url"
165
+ expect(conditions).to_not have_condition("success_action_redirect" => "http://example.com/some_url")
166
+ end
167
+ end
168
+
169
+ context "'content-length-range of'" do
170
+ def have_content_length_range(options = {})
171
+ include([
172
+ "content-length-range",
173
+ options[:min_file_size] || DirectUploader.min_file_size,
174
+ options[:max_file_size] || DirectUploader.max_file_size,
175
+ ])
176
+ end
177
+
178
+ it "#{DirectUploader.min_file_size} bytes" do
179
+ expect(conditions).to have_content_length_range
180
+ end
181
+
182
+ it "#{DirectUploader.max_file_size} bytes" do
183
+ expect(conditions).to have_content_length_range
184
+ end
185
+
186
+ it "#{sample(:min_file_size)} bytes when passing {:min_file_size => #{sample(:min_file_size)}}" do
187
+ expect(conditions(
188
+ :min_file_size => sample(:min_file_size)
189
+ )).to have_content_length_range(:min_file_size => sample(:min_file_size))
190
+ end
191
+
192
+ it "#{sample(:max_file_size)} bytes when passing {:max_file_size => #{sample(:max_file_size)}}" do
193
+ expect(conditions(
194
+ :max_file_size => sample(:max_file_size)
195
+ )).to have_content_length_range(:max_file_size => sample(:max_file_size))
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
201
+
202
+ describe "clear!" do
203
+ it "should reset the cached policy string" do
204
+ Timecop.freeze(Time.now) do
205
+ @policy_now = subject.policy
206
+ end
207
+ subject.clear!
208
+
209
+ Timecop.freeze(1.second.from_now) do
210
+ @policy_after_reset = subject.policy
211
+ end
212
+ expect(@policy_after_reset).not_to eql @policy_now
213
+ end
214
+ end
215
+
216
+ describe "#signature" do
217
+ it "should not contain any new lines" do
218
+ expect(subject.signature).to_not include("\n")
219
+ end
220
+
221
+ it "should return a base64 encoded 'sha1' hash of the secret key and policy document" do
222
+ expect(Base64.decode64(subject.signature)).to eq OpenSSL::HMAC.digest(
223
+ OpenSSL::Digest.new('sha1'),
224
+ uploader.aws_secret_access_key, subject.policy
225
+ )
226
+ end
227
+ end
228
+ end
229
+