kt-paperclip 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (198) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.hound.yml +1066 -0
  4. data/.rubocop.yml +1 -0
  5. data/.travis.yml +23 -0
  6. data/Appraisals +11 -0
  7. data/CONTRIBUTING.md +75 -0
  8. data/Gemfile +21 -0
  9. data/LICENSE +24 -0
  10. data/NEWS +420 -0
  11. data/README.md +977 -0
  12. data/RELEASING.md +17 -0
  13. data/Rakefile +44 -0
  14. data/UPGRADING +14 -0
  15. data/cucumber/paperclip_steps.rb +6 -0
  16. data/features/basic_integration.feature +80 -0
  17. data/features/migration.feature +94 -0
  18. data/features/rake_tasks.feature +62 -0
  19. data/features/step_definitions/attachment_steps.rb +110 -0
  20. data/features/step_definitions/html_steps.rb +15 -0
  21. data/features/step_definitions/rails_steps.rb +236 -0
  22. data/features/step_definitions/s3_steps.rb +14 -0
  23. data/features/step_definitions/web_steps.rb +107 -0
  24. data/features/support/env.rb +11 -0
  25. data/features/support/fakeweb.rb +13 -0
  26. data/features/support/file_helpers.rb +34 -0
  27. data/features/support/fixtures/boot_config.txt +15 -0
  28. data/features/support/fixtures/gemfile.txt +5 -0
  29. data/features/support/fixtures/preinitializer.txt +20 -0
  30. data/features/support/paths.rb +28 -0
  31. data/features/support/rails.rb +63 -0
  32. data/features/support/selectors.rb +19 -0
  33. data/gemfiles/3.2.gemfile +19 -0
  34. data/gemfiles/4.1.gemfile +19 -0
  35. data/gemfiles/4.2.gemfile +19 -0
  36. data/lib/generators/paperclip/USAGE +8 -0
  37. data/lib/generators/paperclip/paperclip_generator.rb +30 -0
  38. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  39. data/lib/kt-paperclip.rb +1 -0
  40. data/lib/paperclip/attachment.rb +608 -0
  41. data/lib/paperclip/attachment_registry.rb +59 -0
  42. data/lib/paperclip/callbacks.rb +40 -0
  43. data/lib/paperclip/content_type_detector.rb +79 -0
  44. data/lib/paperclip/deprecations.rb +42 -0
  45. data/lib/paperclip/errors.rb +32 -0
  46. data/lib/paperclip/file_command_content_type_detector.rb +30 -0
  47. data/lib/paperclip/filename_cleaner.rb +16 -0
  48. data/lib/paperclip/geometry.rb +158 -0
  49. data/lib/paperclip/geometry_detector_factory.rb +48 -0
  50. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  51. data/lib/paperclip/glue.rb +17 -0
  52. data/lib/paperclip/has_attached_file.rb +109 -0
  53. data/lib/paperclip/helpers.rb +56 -0
  54. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  55. data/lib/paperclip/interpolations.rb +197 -0
  56. data/lib/paperclip/io_adapters/abstract_adapter.rb +47 -0
  57. data/lib/paperclip/io_adapters/attachment_adapter.rb +36 -0
  58. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  59. data/lib/paperclip/io_adapters/empty_string_adapter.rb +18 -0
  60. data/lib/paperclip/io_adapters/file_adapter.rb +22 -0
  61. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +16 -0
  62. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -0
  63. data/lib/paperclip/io_adapters/nil_adapter.rb +34 -0
  64. data/lib/paperclip/io_adapters/registry.rb +32 -0
  65. data/lib/paperclip/io_adapters/stringio_adapter.rb +33 -0
  66. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +42 -0
  67. data/lib/paperclip/io_adapters/uri_adapter.rb +63 -0
  68. data/lib/paperclip/locales/de.yml +18 -0
  69. data/lib/paperclip/locales/en.yml +18 -0
  70. data/lib/paperclip/locales/es.yml +18 -0
  71. data/lib/paperclip/locales/ja.yml +18 -0
  72. data/lib/paperclip/locales/pt-BR.yml +18 -0
  73. data/lib/paperclip/locales/zh-CN.yml +18 -0
  74. data/lib/paperclip/locales/zh-HK.yml +18 -0
  75. data/lib/paperclip/locales/zh-TW.yml +18 -0
  76. data/lib/paperclip/logger.rb +21 -0
  77. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  78. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +100 -0
  79. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  80. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +96 -0
  81. data/lib/paperclip/matchers.rb +64 -0
  82. data/lib/paperclip/media_type_spoof_detector.rb +89 -0
  83. data/lib/paperclip/missing_attachment_styles.rb +79 -0
  84. data/lib/paperclip/processor.rb +48 -0
  85. data/lib/paperclip/processor_helpers.rb +50 -0
  86. data/lib/paperclip/rails_environment.rb +25 -0
  87. data/lib/paperclip/railtie.rb +31 -0
  88. data/lib/paperclip/schema.rb +83 -0
  89. data/lib/paperclip/storage/filesystem.rb +90 -0
  90. data/lib/paperclip/storage/fog.rb +242 -0
  91. data/lib/paperclip/storage/s3.rb +440 -0
  92. data/lib/paperclip/storage.rb +3 -0
  93. data/lib/paperclip/style.rb +109 -0
  94. data/lib/paperclip/tempfile.rb +43 -0
  95. data/lib/paperclip/tempfile_factory.rb +23 -0
  96. data/lib/paperclip/thumbnail.rb +121 -0
  97. data/lib/paperclip/url_generator.rb +79 -0
  98. data/lib/paperclip/validators/attachment_content_type_validator.rb +88 -0
  99. data/lib/paperclip/validators/attachment_file_name_validator.rb +80 -0
  100. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +29 -0
  101. data/lib/paperclip/validators/attachment_presence_validator.rb +30 -0
  102. data/lib/paperclip/validators/attachment_size_validator.rb +115 -0
  103. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +27 -0
  104. data/lib/paperclip/validators.rb +74 -0
  105. data/lib/paperclip/version.rb +3 -0
  106. data/lib/paperclip.rb +213 -0
  107. data/lib/tasks/paperclip.rake +127 -0
  108. data/paperclip.gemspec +52 -0
  109. data/shoulda_macros/paperclip.rb +134 -0
  110. data/spec/database.yml +4 -0
  111. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  112. data/spec/paperclip/attachment_processing_spec.rb +82 -0
  113. data/spec/paperclip/attachment_registry_spec.rb +130 -0
  114. data/spec/paperclip/attachment_spec.rb +1494 -0
  115. data/spec/paperclip/content_type_detector_spec.rb +48 -0
  116. data/spec/paperclip/deprecations_spec.rb +65 -0
  117. data/spec/paperclip/file_command_content_type_detector_spec.rb +26 -0
  118. data/spec/paperclip/filename_cleaner_spec.rb +14 -0
  119. data/spec/paperclip/geometry_detector_spec.rb +39 -0
  120. data/spec/paperclip/geometry_parser_spec.rb +73 -0
  121. data/spec/paperclip/geometry_spec.rb +255 -0
  122. data/spec/paperclip/glue_spec.rb +44 -0
  123. data/spec/paperclip/has_attached_file_spec.rb +142 -0
  124. data/spec/paperclip/integration_spec.rb +667 -0
  125. data/spec/paperclip/interpolations_spec.rb +262 -0
  126. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +78 -0
  127. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +139 -0
  128. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +83 -0
  129. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  130. data/spec/paperclip/io_adapters/file_adapter_spec.rb +131 -0
  131. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +104 -0
  132. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  133. data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
  134. data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
  135. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
  136. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
  137. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +127 -0
  138. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  139. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +99 -0
  140. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  141. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  142. data/spec/paperclip/media_type_spoof_detector_spec.rb +79 -0
  143. data/spec/paperclip/meta_class_spec.rb +30 -0
  144. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +84 -0
  145. data/spec/paperclip/paperclip_spec.rb +222 -0
  146. data/spec/paperclip/plural_cache_spec.rb +37 -0
  147. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  148. data/spec/paperclip/processor_spec.rb +26 -0
  149. data/spec/paperclip/rails_environment_spec.rb +33 -0
  150. data/spec/paperclip/rake_spec.rb +103 -0
  151. data/spec/paperclip/schema_spec.rb +248 -0
  152. data/spec/paperclip/storage/filesystem_spec.rb +79 -0
  153. data/spec/paperclip/storage/fog_spec.rb +540 -0
  154. data/spec/paperclip/storage/s3_live_spec.rb +182 -0
  155. data/spec/paperclip/storage/s3_spec.rb +1526 -0
  156. data/spec/paperclip/style_spec.rb +255 -0
  157. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  158. data/spec/paperclip/thumbnail_spec.rb +500 -0
  159. data/spec/paperclip/url_generator_spec.rb +221 -0
  160. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  161. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +160 -0
  162. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  163. data/spec/paperclip/validators/attachment_size_validator_spec.rb +229 -0
  164. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +52 -0
  165. data/spec/paperclip/validators_spec.rb +164 -0
  166. data/spec/spec_helper.rb +43 -0
  167. data/spec/support/assertions.rb +71 -0
  168. data/spec/support/deprecations.rb +9 -0
  169. data/spec/support/fake_model.rb +25 -0
  170. data/spec/support/fake_rails.rb +12 -0
  171. data/spec/support/fixtures/12k.png +0 -0
  172. data/spec/support/fixtures/50x50.png +0 -0
  173. data/spec/support/fixtures/5k.png +0 -0
  174. data/spec/support/fixtures/animated +0 -0
  175. data/spec/support/fixtures/animated.gif +0 -0
  176. data/spec/support/fixtures/animated.unknown +0 -0
  177. data/spec/support/fixtures/bad.png +1 -0
  178. data/spec/support/fixtures/empty.html +1 -0
  179. data/spec/support/fixtures/empty.xlsx +0 -0
  180. data/spec/support/fixtures/fog.yml +8 -0
  181. data/spec/support/fixtures/rotated.jpg +0 -0
  182. data/spec/support/fixtures/s3.yml +8 -0
  183. data/spec/support/fixtures/spaced file.jpg +0 -0
  184. data/spec/support/fixtures/spaced file.png +0 -0
  185. data/spec/support/fixtures/text.txt +1 -0
  186. data/spec/support/fixtures/twopage.pdf +0 -0
  187. data/spec/support/fixtures/uppercase.PNG +0 -0
  188. data/spec/support/matchers/accept.rb +5 -0
  189. data/spec/support/matchers/exist.rb +5 -0
  190. data/spec/support/matchers/have_column.rb +23 -0
  191. data/spec/support/mock_attachment.rb +22 -0
  192. data/spec/support/mock_interpolator.rb +24 -0
  193. data/spec/support/mock_url_generator_builder.rb +27 -0
  194. data/spec/support/model_reconstruction.rb +60 -0
  195. data/spec/support/rails_helpers.rb +7 -0
  196. data/spec/support/test_data.rb +13 -0
  197. data/spec/support/version_helper.rb +9 -0
  198. metadata +648 -0
@@ -0,0 +1,1526 @@
1
+ require 'spec_helper'
2
+ require 'aws-sdk'
3
+
4
+ describe Paperclip::Storage::S3 do
5
+ before do
6
+ AWS.stub!
7
+ end
8
+
9
+ context "Parsing S3 credentials" do
10
+ before do
11
+ @proxy_settings = {host: "127.0.0.1", port: 8888, user: "foo", password: "bar"}
12
+ rebuild_model storage: :s3,
13
+ bucket: "testing",
14
+ http_proxy: @proxy_settings,
15
+ s3_credentials: {not: :important}
16
+
17
+ @dummy = Dummy.new
18
+ @avatar = @dummy.avatar
19
+ end
20
+
21
+ it "gets the correct credentials when RAILS_ENV is production" do
22
+ rails_env("production") do
23
+ assert_equal({key: "12345"},
24
+ @avatar.parse_credentials('production' => {key: '12345'},
25
+ development: {key: "54321"}))
26
+ end
27
+ end
28
+
29
+ it "gets the correct credentials when RAILS_ENV is development" do
30
+ rails_env("development") do
31
+ assert_equal({key: "54321"},
32
+ @avatar.parse_credentials('production' => {key: '12345'},
33
+ development: {key: "54321"}))
34
+ end
35
+ end
36
+
37
+ it "returns the argument if the key does not exist" do
38
+ rails_env("not really an env") do
39
+ assert_equal({test: "12345"}, @avatar.parse_credentials(test: "12345"))
40
+ end
41
+ end
42
+
43
+ it "supports HTTP proxy settings" do
44
+ rails_env("development") do
45
+ assert_equal(true, @avatar.using_http_proxy?)
46
+ assert_equal(@proxy_settings[:host], @avatar.http_proxy_host)
47
+ assert_equal(@proxy_settings[:port], @avatar.http_proxy_port)
48
+ assert_equal(@proxy_settings[:user], @avatar.http_proxy_user)
49
+ assert_equal(@proxy_settings[:password], @avatar.http_proxy_password)
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ context ":bucket option via :s3_credentials" do
56
+
57
+ before do
58
+ rebuild_model storage: :s3, s3_credentials: {bucket: 'testing'}
59
+ @dummy = Dummy.new
60
+ end
61
+
62
+ it "populates #bucket_name" do
63
+ assert_equal @dummy.avatar.bucket_name, 'testing'
64
+ end
65
+
66
+ end
67
+
68
+ context ":bucket option" do
69
+
70
+ before do
71
+ rebuild_model storage: :s3, bucket: "testing", s3_credentials: {}
72
+ @dummy = Dummy.new
73
+ end
74
+
75
+ it "populates #bucket_name" do
76
+ assert_equal @dummy.avatar.bucket_name, 'testing'
77
+ end
78
+
79
+ end
80
+
81
+ context "missing :bucket option" do
82
+
83
+ before do
84
+ rebuild_model storage: :s3,
85
+ http_proxy: @proxy_settings,
86
+ s3_credentials: {not: :important}
87
+
88
+ @dummy = Dummy.new
89
+ @dummy.avatar = stringy_file
90
+
91
+ end
92
+
93
+ it "raises an argument error" do
94
+ expect { @dummy.save }.to raise_error(ArgumentError, /missing required :bucket option/)
95
+ end
96
+
97
+ end
98
+
99
+ context "" do
100
+ before do
101
+ rebuild_model storage: :s3,
102
+ s3_credentials: {},
103
+ bucket: "bucket",
104
+ path: ":attachment/:basename:dotextension",
105
+ url: ":s3_path_url"
106
+ @dummy = Dummy.new
107
+ @dummy.avatar = stringy_file
108
+ @dummy.stubs(:new_record?).returns(false)
109
+ end
110
+
111
+ it "returns a url based on an S3 path" do
112
+ assert_match %r{^http://s3.amazonaws.com/bucket/avatars/data[^\.]}, @dummy.avatar.url
113
+ end
114
+
115
+ it "uses the correct bucket" do
116
+ assert_equal "bucket", @dummy.avatar.s3_bucket.name
117
+ end
118
+
119
+ it "uses the correct key" do
120
+ assert_equal "avatars/data", @dummy.avatar.s3_object.key
121
+ end
122
+ end
123
+
124
+ context "s3_protocol" do
125
+ ["http", :http, ""].each do |protocol|
126
+ context "as #{protocol.inspect}" do
127
+ before do
128
+ rebuild_model storage: :s3, s3_protocol: protocol
129
+
130
+ @dummy = Dummy.new
131
+ end
132
+
133
+ it "returns the s3_protocol in string" do
134
+ assert_equal protocol.to_s, @dummy.avatar.s3_protocol
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ context "s3_protocol: 'https'" do
141
+ before do
142
+ rebuild_model storage: :s3,
143
+ s3_credentials: {},
144
+ s3_protocol: 'https',
145
+ bucket: "bucket",
146
+ path: ":attachment/:basename:dotextension"
147
+ @dummy = Dummy.new
148
+ @dummy.avatar = stringy_file
149
+ @dummy.stubs(:new_record?).returns(false)
150
+ end
151
+
152
+ it "returns a url based on an S3 path" do
153
+ assert_match %r{^https://s3.amazonaws.com/bucket/avatars/data[^\.]}, @dummy.avatar.url
154
+ end
155
+ end
156
+
157
+ context "s3_protocol: ''" do
158
+ before do
159
+ rebuild_model storage: :s3,
160
+ s3_credentials: {},
161
+ s3_protocol: '',
162
+ bucket: "bucket",
163
+ path: ":attachment/:basename:dotextension"
164
+ @dummy = Dummy.new
165
+ @dummy.avatar = stringy_file
166
+ @dummy.stubs(:new_record?).returns(false)
167
+ end
168
+
169
+ it "returns a protocol-relative URL" do
170
+ assert_match %r{^//s3.amazonaws.com/bucket/avatars/data[^\.]}, @dummy.avatar.url
171
+ end
172
+ end
173
+
174
+ context "s3_protocol: :https" do
175
+ before do
176
+ rebuild_model storage: :s3,
177
+ s3_credentials: {},
178
+ s3_protocol: :https,
179
+ bucket: "bucket",
180
+ path: ":attachment/:basename:dotextension"
181
+ @dummy = Dummy.new
182
+ @dummy.avatar = stringy_file
183
+ @dummy.stubs(:new_record?).returns(false)
184
+ end
185
+
186
+ it "returns a url based on an S3 path" do
187
+ assert_match %r{^https://s3.amazonaws.com/bucket/avatars/data[^\.]}, @dummy.avatar.url
188
+ end
189
+ end
190
+
191
+ context "s3_protocol: ''" do
192
+ before do
193
+ rebuild_model storage: :s3,
194
+ s3_credentials: {},
195
+ s3_protocol: '',
196
+ bucket: "bucket",
197
+ path: ":attachment/:basename:dotextension"
198
+ @dummy = Dummy.new
199
+ @dummy.avatar = stringy_file
200
+ @dummy.stubs(:new_record?).returns(false)
201
+ end
202
+
203
+ it "returns a url based on an S3 path" do
204
+ assert_match %r{^//s3.amazonaws.com/bucket/avatars/data[^\.]}, @dummy.avatar.url
205
+ end
206
+ end
207
+
208
+ context "An attachment that uses S3 for storage and has the style in the path" do
209
+ before do
210
+ rebuild_model storage: :s3,
211
+ bucket: "testing",
212
+ path: ":attachment/:style/:basename:dotextension",
213
+ styles: {
214
+ thumb: "80x80>"
215
+ },
216
+ s3_credentials: {
217
+ 'access_key_id' => "12345",
218
+ 'secret_access_key' => "54321"
219
+ }
220
+
221
+ @dummy = Dummy.new
222
+ @dummy.avatar = stringy_file
223
+ @avatar = @dummy.avatar
224
+ end
225
+
226
+ it "uses an S3 object based on the correct path for the default style" do
227
+ assert_equal("avatars/original/data", @dummy.avatar.s3_object.key)
228
+ end
229
+
230
+ it "uses an S3 object based on the correct path for the custom style" do
231
+ assert_equal("avatars/thumb/data", @dummy.avatar.s3_object(:thumb).key)
232
+ end
233
+ end
234
+
235
+ context "s3_host_name" do
236
+ before do
237
+ rebuild_model storage: :s3,
238
+ s3_credentials: {},
239
+ bucket: "bucket",
240
+ path: ":attachment/:basename:dotextension",
241
+ s3_host_name: "s3-ap-northeast-1.amazonaws.com"
242
+ @dummy = Dummy.new
243
+ @dummy.avatar = stringy_file
244
+ @dummy.stubs(:new_record?).returns(false)
245
+ end
246
+
247
+ it "returns a url based on an :s3_host_name path" do
248
+ assert_match %r{^http://s3-ap-northeast-1.amazonaws.com/bucket/avatars/data[^\.]}, @dummy.avatar.url
249
+ end
250
+
251
+ it "uses the S3 bucket with the correct host name" do
252
+ assert_equal "s3-ap-northeast-1.amazonaws.com", @dummy.avatar.s3_bucket.config.s3_endpoint
253
+ end
254
+ end
255
+
256
+ context "dynamic s3_host_name" do
257
+ before do
258
+ rebuild_model storage: :s3,
259
+ s3_credentials: {},
260
+ bucket: "bucket",
261
+ path: ":attachment/:basename:dotextension",
262
+ s3_host_name: lambda {|a| a.instance.value }
263
+ @dummy = Dummy.new
264
+ class << @dummy
265
+ attr_accessor :value
266
+ end
267
+ @dummy.avatar = stringy_file
268
+ @dummy.stubs(:new_record?).returns(false)
269
+ end
270
+
271
+ it "uses s3_host_name as a proc if available" do
272
+ @dummy.value = "s3.something.com"
273
+ assert_equal "http://s3.something.com/bucket/avatars/data", @dummy.avatar.url(:original, timestamp: false)
274
+ end
275
+ end
276
+
277
+ context "An attachment that uses S3 for storage and has styles that return different file types" do
278
+ before do
279
+ rebuild_model styles: { large: ['500x500#', :jpg] },
280
+ storage: :s3,
281
+ bucket: "bucket",
282
+ path: ":attachment/:basename:dotextension",
283
+ s3_credentials: {
284
+ 'access_key_id' => "12345",
285
+ 'secret_access_key' => "54321"
286
+ }
287
+
288
+ File.open(fixture_file('5k.png'), 'rb') do |file|
289
+ @dummy = Dummy.new
290
+ @dummy.avatar = file
291
+ @dummy.stubs(:new_record?).returns(false)
292
+ end
293
+ end
294
+
295
+ it "returns a url containing the correct original file mime type" do
296
+ assert_match /.+\/5k.png/, @dummy.avatar.url
297
+ end
298
+
299
+ it 'uses the correct key for the original file mime type' do
300
+ assert_match /.+\/5k.png/, @dummy.avatar.s3_object.key
301
+ end
302
+
303
+ it "returns a url containing the correct processed file mime type" do
304
+ assert_match /.+\/5k.jpg/, @dummy.avatar.url(:large)
305
+ end
306
+
307
+ it "uses the correct key for the processed file mime type" do
308
+ assert_match /.+\/5k.jpg/, @dummy.avatar.s3_object(:large).key
309
+ end
310
+ end
311
+
312
+ context "An attachment that uses S3 for storage and has a proc for styles" do
313
+ before do
314
+ rebuild_model styles: lambda { |attachment| attachment.instance.counter; {thumbnail: { geometry: "50x50#", s3_headers: {'Cache-Control' => 'max-age=31557600'}} }},
315
+ storage: :s3,
316
+ bucket: "bucket",
317
+ path: ":attachment/:style/:basename:dotextension",
318
+ s3_credentials: {
319
+ 'access_key_id' => "12345",
320
+ 'secret_access_key' => "54321"
321
+ }
322
+
323
+ @file = File.new(fixture_file('5k.png'), 'rb')
324
+
325
+ Dummy.class_eval do
326
+ def counter
327
+ @counter ||= 0
328
+ @counter += 1
329
+ @counter
330
+ end
331
+ end
332
+
333
+ @dummy = Dummy.new
334
+ @dummy.avatar = @file
335
+
336
+ object = stub
337
+ @dummy.avatar.stubs(:s3_object).with(:original).returns(object)
338
+ @dummy.avatar.stubs(:s3_object).with(:thumbnail).returns(object)
339
+ object.expects(:write).with(anything, content_type: 'image/png', acl: :public_read)
340
+ object.expects(:write).with(anything, content_type: 'image/png', acl: :public_read, cache_control: 'max-age=31557600')
341
+ @dummy.save
342
+ end
343
+
344
+ after { @file.close }
345
+
346
+ it "succeeds" do
347
+ assert_equal @dummy.counter, 7
348
+ end
349
+ end
350
+
351
+ context "An attachment that uses S3 for storage and has spaces in file name" do
352
+ before do
353
+ rebuild_model(
354
+ styles: { large: ["500x500#", :jpg] },
355
+ storage: :s3,
356
+ bucket: "bucket",
357
+ s3_credentials: { "access_key_id" => "12345",
358
+ "secret_access_key" => "54321" }
359
+ )
360
+
361
+ File.open(fixture_file("spaced file.png"), "rb") do |file|
362
+ @dummy = Dummy.new
363
+ @dummy.avatar = file
364
+ @dummy.stubs(:new_record?).returns(false)
365
+ end
366
+ end
367
+
368
+ it "returns a replaced version for path" do
369
+ assert_match /.+\/spaced_file\.png/, @dummy.avatar.path
370
+ end
371
+
372
+ it "returns a replaced version for url" do
373
+ assert_match /.+\/spaced_file\.png/, @dummy.avatar.url
374
+ end
375
+ end
376
+
377
+ context "An attachment that uses S3 for storage and has a question mark in file name" do
378
+ before do
379
+ rebuild_model styles: { large: ['500x500#', :jpg] },
380
+ storage: :s3,
381
+ bucket: "bucket",
382
+ s3_credentials: {
383
+ 'access_key_id' => "12345",
384
+ 'secret_access_key' => "54321"
385
+ }
386
+
387
+ stringio = stringy_file
388
+ class << stringio
389
+ def original_filename
390
+ "question?mark.png"
391
+ end
392
+ end
393
+ file = Paperclip.io_adapters.for(stringio)
394
+ @dummy = Dummy.new
395
+ @dummy.avatar = file
396
+ @dummy.save
397
+ @dummy.stubs(:new_record?).returns(false)
398
+ end
399
+
400
+ it "returns a replaced version for path" do
401
+ assert_match /.+\/question_mark\.png/, @dummy.avatar.path
402
+ end
403
+
404
+ it "returns a replaced version for url" do
405
+ assert_match /.+\/question_mark\.png/, @dummy.avatar.url
406
+ end
407
+ end
408
+
409
+ context "" do
410
+ before do
411
+ rebuild_model storage: :s3,
412
+ s3_credentials: {},
413
+ bucket: "bucket",
414
+ path: ":attachment/:basename:dotextension",
415
+ url: ":s3_domain_url"
416
+ @dummy = Dummy.new
417
+ @dummy.avatar = stringy_file
418
+ @dummy.stubs(:new_record?).returns(false)
419
+ end
420
+
421
+ it "returns a url based on an S3 subdomain" do
422
+ assert_match %r{^http://bucket.s3.amazonaws.com/avatars/data[^\.]}, @dummy.avatar.url
423
+ end
424
+ end
425
+
426
+ context "" do
427
+ before do
428
+ rebuild_model(
429
+ storage: :s3,
430
+ s3_credentials: {
431
+ production: { bucket: "prod_bucket" },
432
+ development: { bucket: "dev_bucket" }
433
+ },
434
+ bucket: "bucket",
435
+ s3_host_alias: "something.something.com",
436
+ path: ":attachment/:basename:dotextension",
437
+ url: ":s3_alias_url"
438
+ )
439
+ @dummy = Dummy.new
440
+ @dummy.avatar = stringy_file
441
+ @dummy.stubs(:new_record?).returns(false)
442
+ end
443
+
444
+ it "returns a url based on the host_alias" do
445
+ assert_match %r{^http://something.something.com/avatars/data[^\.]}, @dummy.avatar.url
446
+ end
447
+ end
448
+
449
+ context "generating a url with a proc as the host alias" do
450
+ before do
451
+ rebuild_model storage: :s3,
452
+ s3_credentials: { bucket: "prod_bucket" },
453
+ s3_host_alias: Proc.new{|atch| "cdn#{atch.instance.counter % 4}.example.com"},
454
+ path: ":attachment/:basename:dotextension",
455
+ url: ":s3_alias_url"
456
+ Dummy.class_eval do
457
+ def counter
458
+ @counter ||= 0
459
+ @counter += 1
460
+ @counter
461
+ end
462
+ end
463
+ @dummy = Dummy.new
464
+ @dummy.avatar = stringy_file
465
+ @dummy.stubs(:new_record?).returns(false)
466
+ end
467
+
468
+ it "returns a url based on the host_alias" do
469
+ assert_match %r{^http://cdn1.example.com/avatars/data[^\.]}, @dummy.avatar.url
470
+ assert_match %r{^http://cdn2.example.com/avatars/data[^\.]}, @dummy.avatar.url
471
+ end
472
+
473
+ it "still returns the bucket name" do
474
+ assert_equal "prod_bucket", @dummy.avatar.bucket_name
475
+ end
476
+
477
+ end
478
+
479
+ context "" do
480
+ before do
481
+ rebuild_model storage: :s3,
482
+ s3_credentials: {},
483
+ bucket: "bucket",
484
+ path: ":attachment/:basename:dotextension",
485
+ url: ":asset_host"
486
+ @dummy = Dummy.new
487
+ @dummy.avatar = stringy_file
488
+ @dummy.stubs(:new_record?).returns(false)
489
+ end
490
+
491
+ it "returns a relative URL for Rails to calculate assets host" do
492
+ assert_match %r{^avatars/data[^\.]}, @dummy.avatar.url
493
+ end
494
+
495
+ end
496
+
497
+ context "Generating a secure url with an expiration" do
498
+ before do
499
+ @build_model_with_options = lambda {|options|
500
+ base_options = {
501
+ storage: :s3,
502
+ s3_credentials: {
503
+ production: { bucket: "prod_bucket" },
504
+ development: { bucket: "dev_bucket" }
505
+ },
506
+ s3_host_alias: "something.something.com",
507
+ s3_permissions: "private",
508
+ path: ":attachment/:basename:dotextension",
509
+ url: ":s3_alias_url"
510
+ }
511
+
512
+ rebuild_model base_options.merge(options)
513
+ }
514
+ end
515
+
516
+ it "uses default options" do
517
+ @build_model_with_options[{}]
518
+
519
+ rails_env("production") do
520
+ @dummy = Dummy.new
521
+ @dummy.avatar = stringy_file
522
+
523
+ object = stub
524
+ @dummy.avatar.stubs(:s3_object).returns(object)
525
+ object.expects(:url_for).with(:read, expires: 3600, secure: true)
526
+
527
+ @dummy.avatar.expiring_url
528
+ end
529
+ end
530
+
531
+ it "allows overriding s3_url_options" do
532
+ @build_model_with_options[s3_url_options: { response_content_disposition: "inline" }]
533
+
534
+ rails_env("production") do
535
+ @dummy = Dummy.new
536
+ @dummy.avatar = stringy_file
537
+
538
+ object = stub
539
+ @dummy.avatar.stubs(:s3_object).returns(object)
540
+ object.expects(:url_for).with(:read, expires: 3600, secure: true, response_content_disposition: "inline")
541
+
542
+ @dummy.avatar.expiring_url
543
+ end
544
+ end
545
+
546
+ it "allows overriding s3_object options with a proc" do
547
+ @build_model_with_options[s3_url_options: lambda {|attachment| { response_content_type: attachment.avatar_content_type } }]
548
+
549
+ rails_env("production") do
550
+ @dummy = Dummy.new
551
+
552
+ @file = stringy_file
553
+ @file.stubs(:original_filename).returns("5k.png\n\n")
554
+ Paperclip.stubs(:run).returns('image/png')
555
+ @file.stubs(:content_type).returns("image/png\n\n")
556
+ @file.stubs(:to_tempfile).returns(@file)
557
+
558
+ @dummy.avatar = @file
559
+
560
+ object = stub
561
+ @dummy.avatar.stubs(:s3_object).returns(object)
562
+ object.expects(:url_for).with(:read, expires: 3600, secure: true, response_content_type: "image/png")
563
+
564
+ @dummy.avatar.expiring_url
565
+ end
566
+ end
567
+ end
568
+
569
+ context "#expiring_url" do
570
+ before { @dummy = Dummy.new }
571
+
572
+ context "with no attachment" do
573
+ before { assert(!@dummy.avatar.exists?) }
574
+
575
+ it "returns the default URL" do
576
+ assert_equal(@dummy.avatar.url, @dummy.avatar.expiring_url)
577
+ end
578
+
579
+ it 'generates a url for a style when a file does not exist' do
580
+ assert_equal(@dummy.avatar.url(:thumb), @dummy.avatar.expiring_url(3600, :thumb))
581
+ end
582
+ end
583
+
584
+ it "generates the same url when using Times and Integer offsets" do
585
+ assert_equal @dummy.avatar.expiring_url(1234), @dummy.avatar.expiring_url(Time.now + 1234)
586
+ end
587
+ end
588
+
589
+ context "Generating a url with an expiration for each style" do
590
+ before do
591
+ rebuild_model storage: :s3,
592
+ s3_credentials: {
593
+ production: { bucket: "prod_bucket" },
594
+ development: { bucket: "dev_bucket" }
595
+ },
596
+ s3_permissions: :private,
597
+ s3_host_alias: "something.something.com",
598
+ path: ":attachment/:style/:basename:dotextension",
599
+ url: ":s3_alias_url"
600
+
601
+ rails_env("production") do
602
+ @dummy = Dummy.new
603
+ @dummy.avatar = stringy_file
604
+ end
605
+ end
606
+
607
+ it "generates a url for the thumb" do
608
+ object = stub
609
+ @dummy.avatar.stubs(:s3_object).with(:thumb).returns(object)
610
+ object.expects(:url_for).with(:read, expires: 1800, secure: true)
611
+ @dummy.avatar.expiring_url(1800, :thumb)
612
+ end
613
+
614
+ it "generates a url for the default style" do
615
+ object = stub
616
+ @dummy.avatar.stubs(:s3_object).with(:original).returns(object)
617
+ object.expects(:url_for).with(:read, expires: 1800, secure: true)
618
+ @dummy.avatar.expiring_url(1800)
619
+ end
620
+ end
621
+
622
+ context "Parsing S3 credentials with a bucket in them" do
623
+ before do
624
+ rebuild_model storage: :s3,
625
+ s3_credentials: {
626
+ production: { bucket: "prod_bucket" },
627
+ development: { bucket: "dev_bucket" }
628
+ }
629
+ @dummy = Dummy.new
630
+ end
631
+
632
+ it "gets the right bucket in production" do
633
+ rails_env("production") do
634
+ assert_equal "prod_bucket", @dummy.avatar.bucket_name
635
+ assert_equal "prod_bucket", @dummy.avatar.s3_bucket.name
636
+ end
637
+ end
638
+
639
+ it "gets the right bucket in development" do
640
+ rails_env("development") do
641
+ assert_equal "dev_bucket", @dummy.avatar.bucket_name
642
+ assert_equal "dev_bucket", @dummy.avatar.s3_bucket.name
643
+ end
644
+ end
645
+ end
646
+
647
+ context "Parsing S3 credentials with a s3_host_name in them" do
648
+ before do
649
+ rebuild_model storage: :s3,
650
+ bucket: 'testing',
651
+ s3_credentials: {
652
+ production: { s3_host_name: "s3-world-end.amazonaws.com" },
653
+ development: { s3_host_name: "s3-ap-northeast-1.amazonaws.com" }
654
+ }
655
+ @dummy = Dummy.new
656
+ end
657
+
658
+ it "gets the right s3_host_name in production" do
659
+ rails_env("production") do
660
+ assert_match %r{^s3-world-end.amazonaws.com}, @dummy.avatar.s3_host_name
661
+ assert_match %r{^s3-world-end.amazonaws.com}, @dummy.avatar.s3_bucket.config.s3_endpoint
662
+ end
663
+ end
664
+
665
+ it "gets the right s3_host_name in development" do
666
+ rails_env("development") do
667
+ assert_match %r{^s3-ap-northeast-1.amazonaws.com}, @dummy.avatar.s3_host_name
668
+ assert_match %r{^s3-ap-northeast-1.amazonaws.com}, @dummy.avatar.s3_bucket.config.s3_endpoint
669
+ end
670
+ end
671
+
672
+ it "gets the right s3_host_name if the key does not exist" do
673
+ rails_env("test") do
674
+ assert_match %r{^s3.amazonaws.com}, @dummy.avatar.s3_host_name
675
+ assert_match %r{^s3.amazonaws.com}, @dummy.avatar.s3_bucket.config.s3_endpoint
676
+ end
677
+ end
678
+ end
679
+
680
+ context "An attachment with S3 storage" do
681
+ before do
682
+ rebuild_model storage: :s3,
683
+ bucket: "testing",
684
+ path: ":attachment/:style/:basename:dotextension",
685
+ s3_credentials: {
686
+ aws_access_key_id: "12345",
687
+ aws_secret_access_key: "54321"
688
+ }
689
+ end
690
+
691
+ it "is extended by the S3 module" do
692
+ assert Dummy.new.avatar.is_a?(Paperclip::Storage::S3)
693
+ end
694
+
695
+ it "won't be extended by the Filesystem module" do
696
+ assert ! Dummy.new.avatar.is_a?(Paperclip::Storage::Filesystem)
697
+ end
698
+
699
+ context "when assigned" do
700
+ before do
701
+ @file = File.new(fixture_file('5k.png'), 'rb')
702
+ @dummy = Dummy.new
703
+ @dummy.avatar = @file
704
+ @dummy.stubs(:new_record?).returns(false)
705
+ end
706
+
707
+ after { @file.close }
708
+
709
+ it "does not get a bucket to get a URL" do
710
+ @dummy.avatar.expects(:s3).never
711
+ @dummy.avatar.expects(:s3_bucket).never
712
+ assert_match %r{^http://s3\.amazonaws\.com/testing/avatars/original/5k\.png}, @dummy.avatar.url
713
+ end
714
+
715
+ it "is rewound after flush_writes" do
716
+ @dummy.avatar.instance_eval "def after_flush_writes; end"
717
+ @dummy.avatar.stubs(:s3_object).returns(stub(write: true))
718
+
719
+ files = @dummy.avatar.queued_for_write.values.each(&:read)
720
+ @dummy.save
721
+ assert files.none?(&:eof?), "Expect all the files to be rewound."
722
+ end
723
+
724
+ it "is removed after after_flush_writes" do
725
+ @dummy.avatar.stubs(:s3_object).returns(stub(write: true))
726
+ paths = @dummy.avatar.queued_for_write.values.map(&:path)
727
+ @dummy.save
728
+ assert paths.none?{ |path| File.exist?(path) },
729
+ "Expect all the files to be deleted."
730
+ end
731
+
732
+ it "will retry to save again but back off on SlowDown" do
733
+ @dummy.avatar.stubs(:sleep)
734
+ AWS::S3::S3Object.any_instance.stubs(:write).
735
+ raises(AWS::S3::Errors::SlowDown.new(stub, stub(status: 503, body: "")))
736
+
737
+ expect {@dummy.save}.to raise_error(AWS::S3::Errors::SlowDown)
738
+ expect(@dummy.avatar).to have_received(:sleep).with(1)
739
+ expect(@dummy.avatar).to have_received(:sleep).with(2)
740
+ expect(@dummy.avatar).to have_received(:sleep).with(4)
741
+ expect(@dummy.avatar).to have_received(:sleep).with(8)
742
+ expect(@dummy.avatar).to have_received(:sleep).with(16)
743
+ end
744
+
745
+ context "and saved" do
746
+ before do
747
+ object = stub
748
+ @dummy.avatar.stubs(:s3_object).returns(object)
749
+ object.expects(:write).with(anything,
750
+ content_type: "image/png",
751
+ acl: :public_read)
752
+ @dummy.save
753
+ end
754
+
755
+ it "succeeds" do
756
+ assert true
757
+ end
758
+ end
759
+
760
+ context "and saved without a bucket" do
761
+ before do
762
+ AWS::S3::BucketCollection.any_instance.expects(:create).with("testing")
763
+ AWS::S3::S3Object.any_instance.stubs(:write).
764
+ raises(AWS::S3::Errors::NoSuchBucket.new(stub,
765
+ stub(status: 404,
766
+ body: "<foo/>"))).
767
+ then.returns(nil)
768
+ @dummy.save
769
+ end
770
+
771
+ it "succeeds" do
772
+ assert true
773
+ end
774
+ end
775
+
776
+ context "and remove" do
777
+ before do
778
+ AWS::S3::S3Object.any_instance.stubs(:exists?).returns(true)
779
+ AWS::S3::S3Object.any_instance.stubs(:delete)
780
+ @dummy.destroy
781
+ end
782
+
783
+ it "succeeds" do
784
+ assert true
785
+ end
786
+ end
787
+
788
+ context 'that the file were missing' do
789
+ before do
790
+ AWS::S3::S3Object.any_instance.stubs(:exists?).raises(AWS::Errors::Base)
791
+ end
792
+
793
+ it 'returns false on exists?' do
794
+ assert !@dummy.avatar.exists?
795
+ end
796
+ end
797
+ end
798
+ end
799
+
800
+ context "An attachment with S3 storage and bucket defined as a Proc" do
801
+ before do
802
+ rebuild_model storage: :s3,
803
+ bucket: lambda { |attachment| "bucket_#{attachment.instance.other}" },
804
+ s3_credentials: {not: :important}
805
+ end
806
+
807
+ it "gets the right bucket name" do
808
+ assert "bucket_a", Dummy.new(other: 'a').avatar.bucket_name
809
+ assert "bucket_a", Dummy.new(other: 'a').avatar.s3_bucket.name
810
+ assert "bucket_b", Dummy.new(other: 'b').avatar.bucket_name
811
+ assert "bucket_b", Dummy.new(other: 'b').avatar.s3_bucket.name
812
+ end
813
+ end
814
+
815
+ context "An attachment with S3 storage and S3 credentials defined as a Proc" do
816
+ before do
817
+ rebuild_model storage: :s3,
818
+ bucket: {not: :important},
819
+ s3_credentials: lambda { |attachment|
820
+ Hash['access_key_id' => "access#{attachment.instance.other}", 'secret_access_key' => "secret#{attachment.instance.other}"]
821
+ }
822
+ end
823
+
824
+ it "gets the right credentials" do
825
+ assert "access1234", Dummy.new(other: '1234').avatar.s3_credentials[:access_key_id]
826
+ assert "secret1234", Dummy.new(other: '1234').avatar.s3_credentials[:secret_access_key]
827
+ end
828
+ end
829
+
830
+ context "An attachment with S3 storage and S3 credentials with a :credential_provider" do
831
+ before do
832
+ class DummyCredentialProvider; end
833
+
834
+ rebuild_model storage: :s3,
835
+ bucket: "testing",
836
+ s3_credentials: {
837
+ credential_provider: DummyCredentialProvider.new
838
+ }
839
+ @dummy = Dummy.new
840
+ end
841
+
842
+ it "sets the credential-provider" do
843
+ expect(@dummy.avatar.s3_bucket.config.credential_provider).to be_a DummyCredentialProvider
844
+ end
845
+ end
846
+
847
+ context "An attachment with S3 storage and S3 credentials in an unsupported manor" do
848
+ before do
849
+ rebuild_model storage: :s3, bucket: "testing", s3_credentials: ["unsupported"]
850
+ @dummy = Dummy.new
851
+ end
852
+
853
+ it "does not accept the credentials" do
854
+ assert_raises(ArgumentError) do
855
+ @dummy.avatar.s3_credentials
856
+ end
857
+ end
858
+ end
859
+
860
+ context "An attachment with S3 storage and S3 credentials not supplied" do
861
+ before do
862
+ rebuild_model storage: :s3, bucket: "testing"
863
+ @dummy = Dummy.new
864
+ end
865
+
866
+ it "does not parse any credentials" do
867
+ assert_equal({}, @dummy.avatar.s3_credentials)
868
+ end
869
+ end
870
+
871
+ context "An attachment with S3 storage and specific s3 headers set" do
872
+ before do
873
+ rebuild_model storage: :s3,
874
+ bucket: "testing",
875
+ path: ":attachment/:style/:basename:dotextension",
876
+ s3_credentials: {
877
+ 'access_key_id' => "12345",
878
+ 'secret_access_key' => "54321"
879
+ },
880
+ s3_headers: {'Cache-Control' => 'max-age=31557600'}
881
+ end
882
+
883
+ context "when assigned" do
884
+ before do
885
+ @file = File.new(fixture_file('5k.png'), 'rb')
886
+ @dummy = Dummy.new
887
+ @dummy.avatar = @file
888
+ end
889
+
890
+ after { @file.close }
891
+
892
+ context "and saved" do
893
+ before do
894
+ object = stub
895
+ @dummy.avatar.stubs(:s3_object).returns(object)
896
+ object.expects(:write).with(anything,
897
+ content_type: "image/png",
898
+ acl: :public_read,
899
+ cache_control: 'max-age=31557600')
900
+ @dummy.save
901
+ end
902
+
903
+ it "succeeds" do
904
+ assert true
905
+ end
906
+ end
907
+ end
908
+ end
909
+
910
+ context "An attachment with S3 storage and metadata set using header names" do
911
+ before do
912
+ rebuild_model storage: :s3,
913
+ bucket: "testing",
914
+ path: ":attachment/:style/:basename:dotextension",
915
+ s3_credentials: {
916
+ 'access_key_id' => "12345",
917
+ 'secret_access_key' => "54321"
918
+ },
919
+ s3_headers: {'x-amz-meta-color' => 'red'}
920
+ end
921
+
922
+ context "when assigned" do
923
+ before do
924
+ @file = File.new(fixture_file('5k.png'), 'rb')
925
+ @dummy = Dummy.new
926
+ @dummy.avatar = @file
927
+ end
928
+
929
+ after { @file.close }
930
+
931
+ context "and saved" do
932
+ before do
933
+ object = stub
934
+ @dummy.avatar.stubs(:s3_object).returns(object)
935
+ object.expects(:write).with(anything,
936
+ content_type: "image/png",
937
+ acl: :public_read,
938
+ metadata: { "color" => "red" })
939
+ @dummy.save
940
+ end
941
+
942
+ it "succeeds" do
943
+ assert true
944
+ end
945
+ end
946
+ end
947
+ end
948
+
949
+ context "An attachment with S3 storage and metadata set using the :s3_metadata option" do
950
+ before do
951
+ rebuild_model storage: :s3,
952
+ bucket: "testing",
953
+ path: ":attachment/:style/:basename:dotextension",
954
+ s3_credentials: {
955
+ 'access_key_id' => "12345",
956
+ 'secret_access_key' => "54321"
957
+ },
958
+ s3_metadata: { "color" => "red" }
959
+ end
960
+
961
+ context "when assigned" do
962
+ before do
963
+ @file = File.new(fixture_file('5k.png'), 'rb')
964
+ @dummy = Dummy.new
965
+ @dummy.avatar = @file
966
+ end
967
+
968
+ after { @file.close }
969
+
970
+ context "and saved" do
971
+ before do
972
+ object = stub
973
+ @dummy.avatar.stubs(:s3_object).returns(object)
974
+ object.expects(:write).with(anything,
975
+ content_type: "image/png",
976
+ acl: :public_read,
977
+ metadata: { "color" => "red" })
978
+ @dummy.save
979
+ end
980
+
981
+ it "succeeds" do
982
+ assert true
983
+ end
984
+ end
985
+ end
986
+ end
987
+
988
+ context "An attachment with S3 storage and storage class set" do
989
+ context "using the header name" do
990
+ before do
991
+ rebuild_model storage: :s3,
992
+ bucket: "testing",
993
+ path: ":attachment/:style/:basename:dotextension",
994
+ s3_credentials: {
995
+ 'access_key_id' => "12345",
996
+ 'secret_access_key' => "54321"
997
+ },
998
+ s3_headers: { "x-amz-storage-class" => "reduced_redundancy" }
999
+ end
1000
+
1001
+ context "when assigned" do
1002
+ before do
1003
+ @file = File.new(fixture_file('5k.png'), 'rb')
1004
+ @dummy = Dummy.new
1005
+ @dummy.avatar = @file
1006
+ end
1007
+
1008
+ after { @file.close }
1009
+
1010
+ context "and saved" do
1011
+ before do
1012
+ object = stub
1013
+ @dummy.avatar.stubs(:s3_object).returns(object)
1014
+ object.expects(:write).with(anything,
1015
+ content_type: "image/png",
1016
+ acl: :public_read,
1017
+ storage_class: "reduced_redundancy")
1018
+ @dummy.save
1019
+ end
1020
+
1021
+ it "succeeds" do
1022
+ assert true
1023
+ end
1024
+ end
1025
+ end
1026
+ end
1027
+
1028
+ context "using per style hash" do
1029
+ before do
1030
+ rebuild_model :storage => :s3,
1031
+ :bucket => "testing",
1032
+ :path => ":attachment/:style/:basename.:extension",
1033
+ :styles => {
1034
+ :thumb => "80x80>"
1035
+ },
1036
+ :s3_credentials => {
1037
+ 'access_key_id' => "12345",
1038
+ 'secret_access_key' => "54321"
1039
+ },
1040
+ :s3_storage_class => {
1041
+ :thumb => :reduced_redundancy
1042
+ }
1043
+ end
1044
+
1045
+ context "when assigned" do
1046
+ before do
1047
+ @file = File.new(fixture_file('5k.png'), 'rb')
1048
+ @dummy = Dummy.new
1049
+ @dummy.avatar = @file
1050
+ end
1051
+
1052
+ after { @file.close }
1053
+
1054
+ context "and saved" do
1055
+ before do
1056
+ object = stub
1057
+ [:thumb, :original].each do |style|
1058
+ @dummy.avatar.stubs(:s3_object).with(style).returns(object)
1059
+ expected_options = {:content_type => "image/png", :acl => :public_read}
1060
+ expected_options.merge!(:storage_class => :reduced_redundancy) if style == :thumb
1061
+ object.expects(:write).with(anything, expected_options)
1062
+ end
1063
+ @dummy.save
1064
+ end
1065
+
1066
+ it "succeeds" do
1067
+ assert true
1068
+ end
1069
+ end
1070
+ end
1071
+ end
1072
+
1073
+ context "using global hash option" do
1074
+ before do
1075
+ rebuild_model :storage => :s3,
1076
+ :bucket => "testing",
1077
+ :path => ":attachment/:style/:basename.:extension",
1078
+ :styles => {
1079
+ :thumb => "80x80>"
1080
+ },
1081
+ :s3_credentials => {
1082
+ 'access_key_id' => "12345",
1083
+ 'secret_access_key' => "54321"
1084
+ },
1085
+ :s3_storage_class => :reduced_redundancy
1086
+ end
1087
+
1088
+ context "when assigned" do
1089
+ before do
1090
+ @file = File.new(fixture_file('5k.png'), 'rb')
1091
+ @dummy = Dummy.new
1092
+ @dummy.avatar = @file
1093
+ end
1094
+
1095
+ after { @file.close }
1096
+
1097
+ context "and saved" do
1098
+ before do
1099
+ object = stub
1100
+ [:thumb, :original].each do |style|
1101
+ @dummy.avatar.stubs(:s3_object).with(style).returns(object)
1102
+ object.expects(:write).with(anything, :content_type => "image/png",
1103
+ :acl => :public_read,
1104
+ :storage_class => :reduced_redundancy)
1105
+ end
1106
+ @dummy.save
1107
+ end
1108
+
1109
+ it "succeeds" do
1110
+ assert true
1111
+ end
1112
+ end
1113
+ end
1114
+ end
1115
+ end
1116
+
1117
+ context "Can disable AES256 encryption multiple ways" do
1118
+ [nil, false, ''].each do |tech|
1119
+ before do
1120
+ rebuild_model(
1121
+ storage: :s3,
1122
+ bucket: "testing",
1123
+ path: ":attachment/:style/:basename:dotextension",
1124
+ s3_credentials: {
1125
+ 'access_key_id' => "12345",
1126
+ 'secret_access_key' => "54321"},
1127
+ s3_server_side_encryption: tech)
1128
+ end
1129
+
1130
+ context "when assigned" do
1131
+ before do
1132
+ @file = File.new(fixture_file('5k.png'), 'rb')
1133
+ @dummy = Dummy.new
1134
+ @dummy.avatar = @file
1135
+ end
1136
+
1137
+ after { @file.close }
1138
+
1139
+ context "and saved" do
1140
+ before do
1141
+ object = stub
1142
+ @dummy.avatar.stubs(:s3_object).returns(object)
1143
+ object.expects(:write).with(anything,
1144
+ content_type: "image/png",
1145
+ acl: :public_read)
1146
+ @dummy.save
1147
+ end
1148
+
1149
+ it "succeeds" do
1150
+ assert true
1151
+ end
1152
+ end
1153
+ end
1154
+ end
1155
+ end
1156
+
1157
+ context "An attachment with S3 storage and using AES256 encryption" do
1158
+ before do
1159
+ rebuild_model storage: :s3,
1160
+ bucket: "testing",
1161
+ path: ":attachment/:style/:basename:dotextension",
1162
+ s3_credentials: {
1163
+ 'access_key_id' => "12345",
1164
+ 'secret_access_key' => "54321"
1165
+ },
1166
+ s3_server_side_encryption: :aes256
1167
+ end
1168
+
1169
+ context "when assigned" do
1170
+ before do
1171
+ @file = File.new(fixture_file('5k.png'), 'rb')
1172
+ @dummy = Dummy.new
1173
+ @dummy.avatar = @file
1174
+ end
1175
+
1176
+ after { @file.close }
1177
+
1178
+ context "and saved" do
1179
+ before do
1180
+ object = stub
1181
+ @dummy.avatar.stubs(:s3_object).returns(object)
1182
+ object.expects(:write).with(anything,
1183
+ content_type: "image/png",
1184
+ acl: :public_read,
1185
+ server_side_encryption: :aes256)
1186
+ @dummy.save
1187
+ end
1188
+
1189
+ it "succeeds" do
1190
+ assert true
1191
+ end
1192
+ end
1193
+ end
1194
+ end
1195
+
1196
+ context "An attachment with S3 storage and storage class set using the :storage_class option" do
1197
+ before do
1198
+ rebuild_model storage: :s3,
1199
+ bucket: "testing",
1200
+ path: ":attachment/:style/:basename:dotextension",
1201
+ s3_credentials: {
1202
+ 'access_key_id' => "12345",
1203
+ 'secret_access_key' => "54321"
1204
+ },
1205
+ s3_storage_class: :reduced_redundancy
1206
+ end
1207
+
1208
+ context "when assigned" do
1209
+ before do
1210
+ @file = File.new(fixture_file('5k.png'), 'rb')
1211
+ @dummy = Dummy.new
1212
+ @dummy.avatar = @file
1213
+ end
1214
+
1215
+ after { @file.close }
1216
+
1217
+ context "and saved" do
1218
+ before do
1219
+ object = stub
1220
+ @dummy.avatar.stubs(:s3_object).returns(object)
1221
+ object.expects(:write).with(anything,
1222
+ content_type: "image/png",
1223
+ acl: :public_read,
1224
+ storage_class: :reduced_redundancy)
1225
+ @dummy.save
1226
+ end
1227
+
1228
+ it "succeeds" do
1229
+ assert true
1230
+ end
1231
+ end
1232
+ end
1233
+ end
1234
+
1235
+ context "with S3 credentials supplied as Pathname" do
1236
+ before do
1237
+ ENV['S3_KEY'] = 'pathname_key'
1238
+ ENV['S3_BUCKET'] = 'pathname_bucket'
1239
+ ENV['S3_SECRET'] = 'pathname_secret'
1240
+
1241
+ rails_env('test') do
1242
+ rebuild_model storage: :s3,
1243
+ s3_credentials: Pathname.new(fixture_file('s3.yml'))
1244
+
1245
+ Dummy.delete_all
1246
+ @dummy = Dummy.new
1247
+ end
1248
+ end
1249
+
1250
+ it "parses the credentials" do
1251
+ assert_equal 'pathname_bucket', @dummy.avatar.bucket_name
1252
+ assert_equal 'pathname_key', @dummy.avatar.s3_bucket.config.access_key_id
1253
+ assert_equal 'pathname_secret', @dummy.avatar.s3_bucket.config.secret_access_key
1254
+ end
1255
+ end
1256
+
1257
+ context "with S3 credentials in a YAML file" do
1258
+ before do
1259
+ ENV['S3_KEY'] = 'env_key'
1260
+ ENV['S3_BUCKET'] = 'env_bucket'
1261
+ ENV['S3_SECRET'] = 'env_secret'
1262
+
1263
+ rails_env('test') do
1264
+ rebuild_model storage: :s3,
1265
+ s3_credentials: File.new(fixture_file('s3.yml'))
1266
+
1267
+ Dummy.delete_all
1268
+
1269
+ @dummy = Dummy.new
1270
+ end
1271
+ end
1272
+
1273
+ it "runs the file through ERB" do
1274
+ assert_equal 'env_bucket', @dummy.avatar.bucket_name
1275
+ assert_equal 'env_key', @dummy.avatar.s3_bucket.config.access_key_id
1276
+ assert_equal 'env_secret', @dummy.avatar.s3_bucket.config.secret_access_key
1277
+ end
1278
+ end
1279
+
1280
+ context "S3 Permissions" do
1281
+ context "defaults to :public_read" do
1282
+ before do
1283
+ rebuild_model storage: :s3,
1284
+ bucket: "testing",
1285
+ path: ":attachment/:style/:basename:dotextension",
1286
+ s3_credentials: {
1287
+ 'access_key_id' => "12345",
1288
+ 'secret_access_key' => "54321"
1289
+ }
1290
+ end
1291
+
1292
+ context "when assigned" do
1293
+ before do
1294
+ @file = File.new(fixture_file('5k.png'), 'rb')
1295
+ @dummy = Dummy.new
1296
+ @dummy.avatar = @file
1297
+ end
1298
+
1299
+ after { @file.close }
1300
+
1301
+ context "and saved" do
1302
+ before do
1303
+ object = stub
1304
+ @dummy.avatar.stubs(:s3_object).returns(object)
1305
+ object.expects(:write).with(anything,
1306
+ content_type: "image/png",
1307
+ acl: :public_read)
1308
+ @dummy.save
1309
+ end
1310
+
1311
+ it "succeeds" do
1312
+ assert true
1313
+ end
1314
+ end
1315
+ end
1316
+ end
1317
+
1318
+ context "string permissions set" do
1319
+ before do
1320
+ rebuild_model storage: :s3,
1321
+ bucket: "testing",
1322
+ path: ":attachment/:style/:basename:dotextension",
1323
+ s3_credentials: {
1324
+ 'access_key_id' => "12345",
1325
+ 'secret_access_key' => "54321"
1326
+ },
1327
+ s3_permissions: :private
1328
+ end
1329
+
1330
+ context "when assigned" do
1331
+ before do
1332
+ @file = File.new(fixture_file('5k.png'), 'rb')
1333
+ @dummy = Dummy.new
1334
+ @dummy.avatar = @file
1335
+ end
1336
+
1337
+ after { @file.close }
1338
+
1339
+ context "and saved" do
1340
+ before do
1341
+ object = stub
1342
+ @dummy.avatar.stubs(:s3_object).returns(object)
1343
+ object.expects(:write).with(anything,
1344
+ content_type: "image/png",
1345
+ acl: :private)
1346
+ @dummy.save
1347
+ end
1348
+
1349
+ it "succeeds" do
1350
+ assert true
1351
+ end
1352
+ end
1353
+ end
1354
+ end
1355
+
1356
+ context "hash permissions set" do
1357
+ before do
1358
+ rebuild_model storage: :s3,
1359
+ bucket: "testing",
1360
+ path: ":attachment/:style/:basename:dotextension",
1361
+ styles: {
1362
+ thumb: "80x80>"
1363
+ },
1364
+ s3_credentials: {
1365
+ 'access_key_id' => "12345",
1366
+ 'secret_access_key' => "54321"
1367
+ },
1368
+ s3_permissions: {
1369
+ original: :private,
1370
+ thumb: :public_read
1371
+ }
1372
+ end
1373
+
1374
+ context "when assigned" do
1375
+ before do
1376
+ @file = File.new(fixture_file('5k.png'), 'rb')
1377
+ @dummy = Dummy.new
1378
+ @dummy.avatar = @file
1379
+ end
1380
+
1381
+ after { @file.close }
1382
+
1383
+ context "and saved" do
1384
+ before do
1385
+ [:thumb, :original].each do |style|
1386
+ object = stub
1387
+ @dummy.avatar.stubs(:s3_object).with(style).returns(object)
1388
+ object.expects(:write).with(anything,
1389
+ content_type: "image/png",
1390
+ acl: style == :thumb ? :public_read : :private)
1391
+ end
1392
+ @dummy.save
1393
+ end
1394
+
1395
+ it "succeeds" do
1396
+ assert true
1397
+ end
1398
+ end
1399
+ end
1400
+ end
1401
+
1402
+ context "proc permission set" do
1403
+ before do
1404
+ rebuild_model(
1405
+ storage: :s3,
1406
+ bucket: "testing",
1407
+ path: ":attachment/:style/:basename:dotextension",
1408
+ styles: {
1409
+ thumb: "80x80>"
1410
+ },
1411
+ s3_credentials: {
1412
+ 'access_key_id' => "12345",
1413
+ 'secret_access_key' => "54321"
1414
+ },
1415
+ s3_permissions: lambda {|attachment, style|
1416
+ attachment.instance.private_attachment? && style.to_sym != :thumb ? :private : :public_read
1417
+ }
1418
+ )
1419
+ end
1420
+
1421
+ context "when assigned" do
1422
+ before do
1423
+ @file = File.new(fixture_file('5k.png'), 'rb')
1424
+ @dummy = Dummy.new
1425
+ @dummy.stubs(:private_attachment? => true)
1426
+ @dummy.avatar = @file
1427
+ end
1428
+
1429
+ after { @file.close }
1430
+
1431
+ context "and saved" do
1432
+ before do
1433
+ @dummy.save
1434
+ end
1435
+
1436
+ it "succeeds" do
1437
+ assert @dummy.avatar.url().include? "https://"
1438
+ assert @dummy.avatar.url(:thumb).include? "http://"
1439
+ end
1440
+ end
1441
+ end
1442
+
1443
+ end
1444
+ end
1445
+
1446
+ context "An attachment with S3 storage and metadata set using a proc as headers" do
1447
+ before do
1448
+ rebuild_model(
1449
+ storage: :s3,
1450
+ bucket: "testing",
1451
+ path: ":attachment/:style/:basename:dotextension",
1452
+ styles: {
1453
+ thumb: "80x80>"
1454
+ },
1455
+ s3_credentials: {
1456
+ 'access_key_id' => "12345",
1457
+ 'secret_access_key' => "54321"
1458
+ },
1459
+ s3_headers: lambda {|attachment|
1460
+ {'Content-Disposition' => "attachment; filename=\"#{attachment.name}\""}
1461
+ }
1462
+ )
1463
+ end
1464
+
1465
+ context "when assigned" do
1466
+ before do
1467
+ @file = File.new(fixture_file('5k.png'), 'rb')
1468
+ @dummy = Dummy.new
1469
+ @dummy.stubs(name: 'Custom Avatar Name.png')
1470
+ @dummy.avatar = @file
1471
+ end
1472
+
1473
+ after { @file.close }
1474
+
1475
+ context "and saved" do
1476
+ before do
1477
+ [:thumb, :original].each do |style|
1478
+ object = stub
1479
+ @dummy.avatar.stubs(:s3_object).with(style).returns(object)
1480
+ object.expects(:write).with(anything,
1481
+ content_type: "image/png",
1482
+ acl: :public_read,
1483
+ content_disposition: 'attachment; filename="Custom Avatar Name.png"')
1484
+ end
1485
+ @dummy.save
1486
+ end
1487
+
1488
+ it "succeeds" do
1489
+ assert true
1490
+ end
1491
+ end
1492
+ end
1493
+ end
1494
+
1495
+ context "path is a proc" do
1496
+ before do
1497
+ rebuild_model storage: :s3,
1498
+ path: ->(attachment) { attachment.instance.attachment_path }
1499
+
1500
+ @dummy = Dummy.new
1501
+ @dummy.class_eval do
1502
+ def attachment_path
1503
+ '/some/dynamic/path'
1504
+ end
1505
+ end
1506
+ @dummy.avatar = stringy_file
1507
+ end
1508
+
1509
+ it "returns a correct path" do
1510
+ assert_match '/some/dynamic/path', @dummy.avatar.path
1511
+ end
1512
+ end
1513
+
1514
+
1515
+ private
1516
+
1517
+ def rails_env(env)
1518
+ stored_env, Rails.env = Rails.env, env
1519
+ begin
1520
+ yield
1521
+ ensure
1522
+ Rails.env = stored_env
1523
+ end
1524
+ end
1525
+
1526
+ end