paperclip 3.5.4 → 4.3.7

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of paperclip might be problematic. Click here for more details.

Files changed (198) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -6
  3. data/.hound.yml +1066 -0
  4. data/.rubocop.yml +1 -0
  5. data/.travis.yml +11 -17
  6. data/Appraisals +6 -14
  7. data/CONTRIBUTING.md +13 -8
  8. data/Gemfile +16 -3
  9. data/LICENSE +1 -3
  10. data/NEWS +167 -49
  11. data/README.md +294 -75
  12. data/RELEASING.md +17 -0
  13. data/Rakefile +6 -8
  14. data/features/basic_integration.feature +24 -6
  15. data/features/step_definitions/attachment_steps.rb +30 -22
  16. data/features/step_definitions/html_steps.rb +2 -2
  17. data/features/step_definitions/rails_steps.rb +44 -14
  18. data/features/step_definitions/web_steps.rb +1 -103
  19. data/features/support/env.rb +2 -2
  20. data/features/support/file_helpers.rb +2 -2
  21. data/features/support/fixtures/gemfile.txt +1 -1
  22. data/features/support/rails.rb +2 -1
  23. data/gemfiles/3.2.gemfile +14 -6
  24. data/gemfiles/4.1.gemfile +19 -0
  25. data/gemfiles/4.2.gemfile +19 -0
  26. data/lib/generators/paperclip/paperclip_generator.rb +0 -2
  27. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +1 -1
  28. data/lib/paperclip/attachment.rb +132 -38
  29. data/lib/paperclip/attachment_registry.rb +1 -1
  30. data/lib/paperclip/callbacks.rb +11 -1
  31. data/lib/paperclip/content_type_detector.rb +25 -22
  32. data/lib/paperclip/deprecations.rb +42 -0
  33. data/lib/paperclip/errors.rb +5 -0
  34. data/lib/paperclip/file_command_content_type_detector.rb +6 -8
  35. data/lib/paperclip/geometry_detector_factory.rb +3 -1
  36. data/lib/paperclip/geometry_parser_factory.rb +1 -1
  37. data/lib/paperclip/has_attached_file.rb +10 -0
  38. data/lib/paperclip/interpolations/plural_cache.rb +6 -5
  39. data/lib/paperclip/interpolations.rb +25 -12
  40. data/lib/paperclip/io_adapters/abstract_adapter.rb +3 -1
  41. data/lib/paperclip/io_adapters/attachment_adapter.rb +4 -4
  42. data/lib/paperclip/io_adapters/data_uri_adapter.rb +5 -10
  43. data/lib/paperclip/io_adapters/stringio_adapter.rb +6 -10
  44. data/lib/paperclip/io_adapters/uri_adapter.rb +30 -11
  45. data/lib/paperclip/locales/de.yml +18 -0
  46. data/lib/paperclip/locales/en.yml +1 -0
  47. data/lib/paperclip/locales/es.yml +18 -0
  48. data/lib/paperclip/locales/ja.yml +18 -0
  49. data/lib/paperclip/locales/pt-BR.yml +18 -0
  50. data/lib/paperclip/locales/zh-CN.yml +18 -0
  51. data/lib/paperclip/locales/zh-HK.yml +18 -0
  52. data/lib/paperclip/locales/zh-TW.yml +18 -0
  53. data/lib/paperclip/matchers/have_attached_file_matcher.rb +2 -1
  54. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +2 -1
  55. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +2 -1
  56. data/lib/paperclip/media_type_spoof_detector.rb +89 -0
  57. data/lib/paperclip/processor.rb +0 -37
  58. data/lib/paperclip/processor_helpers.rb +50 -0
  59. data/lib/paperclip/rails_environment.rb +25 -0
  60. data/lib/paperclip/schema.rb +10 -2
  61. data/lib/paperclip/storage/filesystem.rb +1 -1
  62. data/lib/paperclip/storage/fog.rb +18 -7
  63. data/lib/paperclip/storage/s3.rb +53 -22
  64. data/lib/paperclip/style.rb +8 -2
  65. data/lib/paperclip/tempfile_factory.rb +5 -1
  66. data/lib/paperclip/thumbnail.rb +12 -10
  67. data/lib/paperclip/url_generator.rb +11 -3
  68. data/lib/paperclip/validators/attachment_content_type_validator.rb +4 -0
  69. data/lib/paperclip/validators/attachment_file_name_validator.rb +80 -0
  70. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +29 -0
  71. data/lib/paperclip/validators/attachment_presence_validator.rb +4 -0
  72. data/lib/paperclip/validators/attachment_size_validator.rb +11 -3
  73. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +27 -0
  74. data/lib/paperclip/validators.rb +10 -3
  75. data/lib/paperclip/version.rb +1 -1
  76. data/lib/paperclip.rb +26 -8
  77. data/lib/tasks/paperclip.rake +17 -2
  78. data/paperclip.gemspec +16 -14
  79. data/shoulda_macros/paperclip.rb +0 -1
  80. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  81. data/{test/attachment_processing_test.rb → spec/paperclip/attachment_processing_spec.rb} +20 -21
  82. data/spec/paperclip/attachment_registry_spec.rb +130 -0
  83. data/{test/attachment_test.rb → spec/paperclip/attachment_spec.rb} +438 -397
  84. data/{test/content_type_detector_test.rb → spec/paperclip/content_type_detector_spec.rb} +16 -19
  85. data/spec/paperclip/deprecations_spec.rb +65 -0
  86. data/{test/file_command_content_type_detector_test.rb → spec/paperclip/file_command_content_type_detector_spec.rb} +5 -6
  87. data/spec/paperclip/filename_cleaner_spec.rb +14 -0
  88. data/spec/paperclip/geometry_detector_spec.rb +39 -0
  89. data/{test/geometry_parser_test.rb → spec/paperclip/geometry_parser_spec.rb} +27 -27
  90. data/{test/geometry_test.rb → spec/paperclip/geometry_spec.rb} +50 -52
  91. data/spec/paperclip/glue_spec.rb +44 -0
  92. data/{test/has_attached_file_test.rb → spec/paperclip/has_attached_file_spec.rb} +45 -28
  93. data/{test/integration_test.rb → spec/paperclip/integration_spec.rb} +134 -126
  94. data/{test/interpolations_test.rb → spec/paperclip/interpolations_spec.rb} +70 -46
  95. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +78 -0
  96. data/{test/io_adapters/attachment_adapter_test.rb → spec/paperclip/io_adapters/attachment_adapter_spec.rb} +27 -29
  97. data/{test/io_adapters/data_uri_adapter_test.rb → spec/paperclip/io_adapters/data_uri_adapter_spec.rb} +26 -17
  98. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  99. data/{test/io_adapters/file_adapter_test.rb → spec/paperclip/io_adapters/file_adapter_spec.rb} +36 -40
  100. data/{test/io_adapters/http_url_proxy_adapter_test.rb → spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb} +31 -29
  101. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  102. data/{test/io_adapters/nil_adapter_test.rb → spec/paperclip/io_adapters/nil_adapter_spec.rb} +7 -7
  103. data/{test/io_adapters/registry_test.rb → spec/paperclip/io_adapters/registry_spec.rb} +10 -7
  104. data/{test/io_adapters/stringio_adapter_test.rb → spec/paperclip/io_adapters/stringio_adapter_spec.rb} +20 -17
  105. data/{test/io_adapters/uploaded_file_adapter_test.rb → spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb} +41 -41
  106. data/{test/io_adapters/uri_adapter_test.rb → spec/paperclip/io_adapters/uri_adapter_spec.rb} +53 -28
  107. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  108. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +99 -0
  109. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  110. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  111. data/spec/paperclip/media_type_spoof_detector_spec.rb +79 -0
  112. data/spec/paperclip/meta_class_spec.rb +30 -0
  113. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +84 -0
  114. data/{test/paperclip_test.rb → spec/paperclip/paperclip_spec.rb} +53 -48
  115. data/spec/paperclip/plural_cache_spec.rb +37 -0
  116. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  117. data/{test/processor_test.rb → spec/paperclip/processor_spec.rb} +5 -5
  118. data/spec/paperclip/rails_environment_spec.rb +33 -0
  119. data/{test/rake_test.rb → spec/paperclip/rake_spec.rb} +15 -15
  120. data/spec/paperclip/schema_spec.rb +248 -0
  121. data/{test/storage/filesystem_test.rb → spec/paperclip/storage/filesystem_spec.rb} +18 -18
  122. data/spec/paperclip/storage/fog_spec.rb +535 -0
  123. data/spec/paperclip/storage/s3_live_spec.rb +182 -0
  124. data/spec/paperclip/storage/s3_spec.rb +1526 -0
  125. data/spec/paperclip/style_spec.rb +255 -0
  126. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  127. data/{test/thumbnail_test.rb → spec/paperclip/thumbnail_spec.rb} +123 -107
  128. data/spec/paperclip/url_generator_spec.rb +211 -0
  129. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  130. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +160 -0
  131. data/{test/validators/attachment_presence_validator_test.rb → spec/paperclip/validators/attachment_presence_validator_spec.rb} +20 -20
  132. data/{test/validators/attachment_size_validator_test.rb → spec/paperclip/validators/attachment_size_validator_spec.rb} +65 -58
  133. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +52 -0
  134. data/spec/paperclip/validators_spec.rb +164 -0
  135. data/spec/spec_helper.rb +43 -0
  136. data/spec/support/assertions.rb +71 -0
  137. data/spec/support/deprecations.rb +9 -0
  138. data/spec/support/fake_model.rb +25 -0
  139. data/spec/support/fake_rails.rb +12 -0
  140. data/spec/support/fixtures/empty.html +1 -0
  141. data/spec/support/fixtures/empty.xlsx +0 -0
  142. data/spec/support/fixtures/spaced file.jpg +0 -0
  143. data/spec/support/matchers/accept.rb +5 -0
  144. data/spec/support/matchers/exist.rb +5 -0
  145. data/spec/support/matchers/have_column.rb +23 -0
  146. data/spec/support/model_reconstruction.rb +60 -0
  147. data/spec/support/rails_helpers.rb +7 -0
  148. data/spec/support/test_data.rb +13 -0
  149. data/spec/support/version_helper.rb +9 -0
  150. metadata +334 -219
  151. data/RUNNING_TESTS.md +0 -4
  152. data/gemfiles/3.0.gemfile +0 -11
  153. data/gemfiles/3.1.gemfile +0 -11
  154. data/gemfiles/4.0.gemfile +0 -11
  155. data/test/attachment_definitions_test.rb +0 -12
  156. data/test/attachment_registry_test.rb +0 -88
  157. data/test/filename_cleaner_test.rb +0 -14
  158. data/test/generator_test.rb +0 -84
  159. data/test/geometry_detector_test.rb +0 -24
  160. data/test/helper.rb +0 -232
  161. data/test/io_adapters/abstract_adapter_test.rb +0 -58
  162. data/test/io_adapters/empty_string_adapter_test.rb +0 -18
  163. data/test/io_adapters/identity_adapter_test.rb +0 -8
  164. data/test/matchers/have_attached_file_matcher_test.rb +0 -24
  165. data/test/matchers/validate_attachment_content_type_matcher_test.rb +0 -110
  166. data/test/matchers/validate_attachment_presence_matcher_test.rb +0 -69
  167. data/test/matchers/validate_attachment_size_matcher_test.rb +0 -86
  168. data/test/meta_class_test.rb +0 -32
  169. data/test/paperclip_missing_attachment_styles_test.rb +0 -90
  170. data/test/plural_cache_test.rb +0 -36
  171. data/test/schema_test.rb +0 -200
  172. data/test/storage/fog_test.rb +0 -473
  173. data/test/storage/s3_live_test.rb +0 -179
  174. data/test/storage/s3_test.rb +0 -1356
  175. data/test/style_test.rb +0 -213
  176. data/test/support/mock_model.rb +0 -2
  177. data/test/tempfile_factory_test.rb +0 -17
  178. data/test/url_generator_test.rb +0 -187
  179. data/test/validators/attachment_content_type_validator_test.rb +0 -324
  180. data/test/validators_test.rb +0 -61
  181. /data/{test → spec}/database.yml +0 -0
  182. /data/{test → spec/support}/fixtures/12k.png +0 -0
  183. /data/{test → spec/support}/fixtures/50x50.png +0 -0
  184. /data/{test → spec/support}/fixtures/5k.png +0 -0
  185. /data/{test → spec/support}/fixtures/animated +0 -0
  186. /data/{test → spec/support}/fixtures/animated.gif +0 -0
  187. /data/{test → spec/support}/fixtures/animated.unknown +0 -0
  188. /data/{test → spec/support}/fixtures/bad.png +0 -0
  189. /data/{test → spec/support}/fixtures/fog.yml +0 -0
  190. /data/{test → spec/support}/fixtures/rotated.jpg +0 -0
  191. /data/{test → spec/support}/fixtures/s3.yml +0 -0
  192. /data/{test → spec/support}/fixtures/spaced file.png +0 -0
  193. /data/{test → spec/support}/fixtures/text.txt +0 -0
  194. /data/{test → spec/support}/fixtures/twopage.pdf +0 -0
  195. /data/{test → spec/support}/fixtures/uppercase.PNG +0 -0
  196. /data/{test → spec}/support/mock_attachment.rb +0 -0
  197. /data/{test → spec}/support/mock_interpolator.rb +0 -0
  198. /data/{test → spec}/support/mock_url_generator_builder.rb +0 -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