jr-paperclip 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. checksums.yaml +7 -0
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  4. data/.github/ISSUE_TEMPLATE/custom.md +10 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  6. data/.github/workflows/reviewdog.yml +23 -0
  7. data/.github/workflows/test.yml +46 -0
  8. data/.gitignore +19 -0
  9. data/.qlty/.gitignore +7 -0
  10. data/.qlty/qlty.toml +89 -0
  11. data/.rubocop.yml +1060 -0
  12. data/Appraisals +29 -0
  13. data/CONTRIBUTING.md +85 -0
  14. data/Gemfile +17 -0
  15. data/LICENSE +25 -0
  16. data/NEWS +567 -0
  17. data/README.md +1083 -0
  18. data/RELEASING.md +17 -0
  19. data/Rakefile +52 -0
  20. data/bin/console +11 -0
  21. data/features/basic_integration.feature +85 -0
  22. data/features/migration.feature +29 -0
  23. data/features/rake_tasks.feature +62 -0
  24. data/features/step_definitions/attachment_steps.rb +121 -0
  25. data/features/step_definitions/html_steps.rb +15 -0
  26. data/features/step_definitions/rails_steps.rb +271 -0
  27. data/features/step_definitions/s3_steps.rb +16 -0
  28. data/features/step_definitions/web_steps.rb +106 -0
  29. data/features/support/env.rb +12 -0
  30. data/features/support/file_helpers.rb +34 -0
  31. data/features/support/fixtures/boot_config.txt +15 -0
  32. data/features/support/fixtures/gemfile.txt +5 -0
  33. data/features/support/fixtures/preinitializer.txt +20 -0
  34. data/features/support/paths.rb +28 -0
  35. data/features/support/rails.rb +39 -0
  36. data/features/support/selectors.rb +19 -0
  37. data/features/support/webmock_setup.rb +8 -0
  38. data/gemfiles/7.0.gemfile +20 -0
  39. data/gemfiles/7.1.gemfile +20 -0
  40. data/gemfiles/7.2.gemfile +20 -0
  41. data/gemfiles/8.0.gemfile +20 -0
  42. data/gemfiles/8.1.gemfile +20 -0
  43. data/lib/generators/paperclip/USAGE +8 -0
  44. data/lib/generators/paperclip/paperclip_generator.rb +36 -0
  45. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  46. data/lib/jr-paperclip.rb +1 -0
  47. data/lib/paperclip/attachment.rb +634 -0
  48. data/lib/paperclip/attachment_registry.rb +60 -0
  49. data/lib/paperclip/callbacks.rb +42 -0
  50. data/lib/paperclip/content_type_detector.rb +85 -0
  51. data/lib/paperclip/errors.rb +34 -0
  52. data/lib/paperclip/file_command_content_type_detector.rb +28 -0
  53. data/lib/paperclip/filename_cleaner.rb +15 -0
  54. data/lib/paperclip/geometry.rb +157 -0
  55. data/lib/paperclip/geometry_detector_factory.rb +45 -0
  56. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  57. data/lib/paperclip/glue.rb +18 -0
  58. data/lib/paperclip/has_attached_file.rb +116 -0
  59. data/lib/paperclip/helpers.rb +60 -0
  60. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  61. data/lib/paperclip/interpolations.rb +205 -0
  62. data/lib/paperclip/io_adapters/abstract_adapter.rb +75 -0
  63. data/lib/paperclip/io_adapters/attachment_adapter.rb +56 -0
  64. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  65. data/lib/paperclip/io_adapters/empty_string_adapter.rb +19 -0
  66. data/lib/paperclip/io_adapters/file_adapter.rb +26 -0
  67. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +16 -0
  68. data/lib/paperclip/io_adapters/identity_adapter.rb +17 -0
  69. data/lib/paperclip/io_adapters/nil_adapter.rb +37 -0
  70. data/lib/paperclip/io_adapters/registry.rb +36 -0
  71. data/lib/paperclip/io_adapters/stringio_adapter.rb +36 -0
  72. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +44 -0
  73. data/lib/paperclip/io_adapters/uri_adapter.rb +78 -0
  74. data/lib/paperclip/locales/en.yml +18 -0
  75. data/lib/paperclip/locales/fr.yml +18 -0
  76. data/lib/paperclip/locales/gd.yml +20 -0
  77. data/lib/paperclip/logger.rb +21 -0
  78. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  79. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +101 -0
  80. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  81. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +97 -0
  82. data/lib/paperclip/matchers.rb +64 -0
  83. data/lib/paperclip/media_type_spoof_detector.rb +93 -0
  84. data/lib/paperclip/missing_attachment_styles.rb +84 -0
  85. data/lib/paperclip/processor.rb +56 -0
  86. data/lib/paperclip/processor_helpers.rb +52 -0
  87. data/lib/paperclip/rails_environment.rb +21 -0
  88. data/lib/paperclip/railtie.rb +31 -0
  89. data/lib/paperclip/schema.rb +104 -0
  90. data/lib/paperclip/storage/filesystem.rb +99 -0
  91. data/lib/paperclip/storage/fog.rb +262 -0
  92. data/lib/paperclip/storage/s3.rb +497 -0
  93. data/lib/paperclip/storage.rb +3 -0
  94. data/lib/paperclip/style.rb +106 -0
  95. data/lib/paperclip/tempfile.rb +42 -0
  96. data/lib/paperclip/tempfile_factory.rb +22 -0
  97. data/lib/paperclip/thumbnail.rb +131 -0
  98. data/lib/paperclip/url_generator.rb +83 -0
  99. data/lib/paperclip/validators/attachment_content_type_validator.rb +95 -0
  100. data/lib/paperclip/validators/attachment_file_name_validator.rb +82 -0
  101. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +28 -0
  102. data/lib/paperclip/validators/attachment_presence_validator.rb +28 -0
  103. data/lib/paperclip/validators/attachment_size_validator.rb +126 -0
  104. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +29 -0
  105. data/lib/paperclip/validators.rb +73 -0
  106. data/lib/paperclip/version.rb +3 -0
  107. data/lib/paperclip.rb +215 -0
  108. data/lib/tasks/paperclip.rake +140 -0
  109. data/paperclip.gemspec +51 -0
  110. data/shoulda_macros/paperclip.rb +134 -0
  111. data/spec/database.yml +4 -0
  112. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  113. data/spec/paperclip/attachment_processing_spec.rb +79 -0
  114. data/spec/paperclip/attachment_registry_spec.rb +158 -0
  115. data/spec/paperclip/attachment_spec.rb +1617 -0
  116. data/spec/paperclip/content_type_detector_spec.rb +58 -0
  117. data/spec/paperclip/file_command_content_type_detector_spec.rb +40 -0
  118. data/spec/paperclip/filename_cleaner_spec.rb +13 -0
  119. data/spec/paperclip/geometry_detector_spec.rb +47 -0
  120. data/spec/paperclip/geometry_parser_spec.rb +73 -0
  121. data/spec/paperclip/geometry_spec.rb +267 -0
  122. data/spec/paperclip/glue_spec.rb +63 -0
  123. data/spec/paperclip/has_attached_file_spec.rb +78 -0
  124. data/spec/paperclip/integration_spec.rb +702 -0
  125. data/spec/paperclip/interpolations_spec.rb +270 -0
  126. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +160 -0
  127. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +167 -0
  128. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +88 -0
  129. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  130. data/spec/paperclip/io_adapters/file_adapter_spec.rb +134 -0
  131. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +142 -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 +231 -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 +108 -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 +126 -0
  143. data/spec/paperclip/meta_class_spec.rb +30 -0
  144. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +88 -0
  145. data/spec/paperclip/paperclip_spec.rb +196 -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 +30 -0
  150. data/spec/paperclip/rake_spec.rb +103 -0
  151. data/spec/paperclip/schema_spec.rb +298 -0
  152. data/spec/paperclip/storage/filesystem_spec.rb +102 -0
  153. data/spec/paperclip/storage/fog_spec.rb +606 -0
  154. data/spec/paperclip/storage/s3_live_spec.rb +188 -0
  155. data/spec/paperclip/storage/s3_spec.rb +1974 -0
  156. data/spec/paperclip/style_spec.rb +251 -0
  157. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  158. data/spec/paperclip/tempfile_spec.rb +35 -0
  159. data/spec/paperclip/thumbnail_spec.rb +504 -0
  160. data/spec/paperclip/url_generator_spec.rb +231 -0
  161. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +410 -0
  162. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +249 -0
  163. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  164. data/spec/paperclip/validators/attachment_size_validator_spec.rb +325 -0
  165. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +48 -0
  166. data/spec/paperclip/validators_spec.rb +179 -0
  167. data/spec/spec_helper.rb +52 -0
  168. data/spec/support/assertions.rb +84 -0
  169. data/spec/support/fake_model.rb +24 -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/aws_s3.yml +13 -0
  178. data/spec/support/fixtures/bad.png +1 -0
  179. data/spec/support/fixtures/empty.html +1 -0
  180. data/spec/support/fixtures/empty.xlsx +0 -0
  181. data/spec/support/fixtures/fog.yml +8 -0
  182. data/spec/support/fixtures/rotated.jpg +0 -0
  183. data/spec/support/fixtures/s3.yml +8 -0
  184. data/spec/support/fixtures/sample.xlsm +0 -0
  185. data/spec/support/fixtures/spaced file.jpg +0 -0
  186. data/spec/support/fixtures/spaced file.png +0 -0
  187. data/spec/support/fixtures/text.txt +1 -0
  188. data/spec/support/fixtures/twopage.pdf +0 -0
  189. data/spec/support/fixtures/uppercase.PNG +0 -0
  190. data/spec/support/matchers/accept.rb +5 -0
  191. data/spec/support/matchers/exist.rb +5 -0
  192. data/spec/support/matchers/have_column.rb +23 -0
  193. data/spec/support/mock_attachment.rb +24 -0
  194. data/spec/support/mock_interpolator.rb +24 -0
  195. data/spec/support/mock_url_generator_builder.rb +26 -0
  196. data/spec/support/model_reconstruction.rb +72 -0
  197. data/spec/support/reporting.rb +11 -0
  198. data/spec/support/test_data.rb +13 -0
  199. data/spec/support/version_helper.rb +9 -0
  200. metadata +702 -0
@@ -0,0 +1,1974 @@
1
+ require "spec_helper"
2
+ require "aws-sdk-s3"
3
+
4
+ describe Paperclip::Storage::S3 do
5
+ before do
6
+ Aws.config[:stub_responses] = true
7
+ end
8
+
9
+ def s3_uses_transfer_manager?
10
+ defined?(Aws::S3::TransferManager)
11
+ end
12
+
13
+ def aws2_add_region
14
+ { s3_region: "us-east-1" }
15
+ end
16
+
17
+ context "Parsing S3 credentials" do
18
+ before do
19
+ @proxy_settings = { host: "127.0.0.1", port: 8888, user: "foo", password: "bar" }
20
+ rebuild_model aws2_add_region.merge storage: :s3,
21
+ bucket: "testing",
22
+ http_proxy: @proxy_settings,
23
+ s3_credentials: { not: :important }
24
+ @dummy = Dummy.new
25
+ @avatar = @dummy.avatar
26
+ end
27
+
28
+ it "gets the correct credentials when RAILS_ENV is production" do
29
+ rails_env("production") do
30
+ assert_equal({ key: "12345" },
31
+ @avatar.parse_credentials("production" => { key: "12345" },
32
+ development: { key: "54321" }))
33
+ end
34
+ end
35
+
36
+ it "gets the correct credentials when RAILS_ENV is development" do
37
+ rails_env("development") do
38
+ assert_equal({ key: "54321" },
39
+ @avatar.parse_credentials("production" => { key: "12345" },
40
+ development: { key: "54321" }))
41
+ end
42
+ end
43
+
44
+ it "returns the argument if the key does not exist" do
45
+ rails_env("not really an env") do
46
+ assert_equal({ test: "12345" }, @avatar.parse_credentials(test: "12345"))
47
+ end
48
+ end
49
+
50
+ it "supports HTTP proxy settings" do
51
+ rails_env("development") do
52
+ assert_equal(true, @avatar.using_http_proxy?)
53
+ assert_equal(@proxy_settings[:host], @avatar.http_proxy_host)
54
+ assert_equal(@proxy_settings[:port], @avatar.http_proxy_port)
55
+ assert_equal(@proxy_settings[:user], @avatar.http_proxy_user)
56
+ assert_equal(@proxy_settings[:password], @avatar.http_proxy_password)
57
+ end
58
+ end
59
+ end
60
+
61
+ context ":bucket option via :s3_credentials" do
62
+ before do
63
+ rebuild_model aws2_add_region.merge storage: :s3,
64
+ s3_credentials: { bucket: "testing" }
65
+ @dummy = Dummy.new
66
+ end
67
+
68
+ it "populates #bucket_name" do
69
+ assert_equal @dummy.avatar.bucket_name, "testing"
70
+ end
71
+ end
72
+
73
+ context ":bucket option" do
74
+ before do
75
+ rebuild_model aws2_add_region.merge storage: :s3,
76
+ bucket: "testing", s3_credentials: {}
77
+ @dummy = Dummy.new
78
+ end
79
+
80
+ it "populates #bucket_name" do
81
+ assert_equal @dummy.avatar.bucket_name, "testing"
82
+ end
83
+ end
84
+
85
+ context "missing :bucket option" do
86
+ before do
87
+ rebuild_model aws2_add_region.merge storage: :s3,
88
+ http_proxy: @proxy_settings,
89
+ s3_credentials: { not: :important }
90
+
91
+ @dummy = Dummy.new
92
+ @dummy.avatar = stringy_file
93
+ end
94
+
95
+ it "raises an argument error" do
96
+ expect { @dummy.save }.to raise_error(ArgumentError, /missing required :bucket option/)
97
+ end
98
+ end
99
+
100
+ context "" do
101
+ before do
102
+ rebuild_model aws2_add_region.merge storage: :s3,
103
+ s3_credentials: {},
104
+ bucket: "bucket",
105
+ path: ":attachment/:basename:dotextension",
106
+ url: ":s3_path_url"
107
+ @dummy = Dummy.new
108
+ @dummy.avatar = stringy_file
109
+ allow(@dummy).to receive(:new_record?).and_return(false)
110
+ end
111
+
112
+ it "returns a url based on an S3 path" do
113
+ assert_match %r{^//s3.amazonaws.com/bucket/avatars/data[^\.]}, @dummy.avatar.url
114
+ end
115
+
116
+ it "uses the correct bucket" do
117
+ assert_equal "bucket", @dummy.avatar.s3_bucket.name
118
+ end
119
+
120
+ it "uses the correct key" do
121
+ assert_equal "avatars/data", @dummy.avatar.s3_object.key
122
+ end
123
+ end
124
+
125
+ context "s3_protocol" do
126
+ ["http", :http, ""].each do |protocol|
127
+ context "as #{protocol.inspect}" do
128
+ before do
129
+ rebuild_model aws2_add_region.merge storage: :s3,
130
+ s3_protocol: protocol
131
+ @dummy = Dummy.new
132
+ end
133
+
134
+ it "returns the s3_protocol in string" do
135
+ assert_equal protocol.to_s, @dummy.avatar.s3_protocol
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ context "s3_protocol: 'https'" do
142
+ before do
143
+ rebuild_model aws2_add_region.merge storage: :s3,
144
+ s3_credentials: {},
145
+ s3_protocol: "https",
146
+ bucket: "bucket",
147
+ path: ":attachment/:basename:dotextension"
148
+ @dummy = Dummy.new
149
+ @dummy.avatar = stringy_file
150
+ allow(@dummy).to receive(:new_record?).and_return(false)
151
+ end
152
+
153
+ it "returns a url based on an S3 path" do
154
+ assert_match %r{^https://s3.amazonaws.com/bucket/avatars/data[^\.]}, @dummy.avatar.url
155
+ end
156
+ end
157
+
158
+ context "s3_protocol: ''" do
159
+ before do
160
+ rebuild_model aws2_add_region.merge storage: :s3,
161
+ s3_credentials: {},
162
+ s3_protocol: "",
163
+ bucket: "bucket",
164
+ path: ":attachment/:basename:dotextension"
165
+ @dummy = Dummy.new
166
+ @dummy.avatar = stringy_file
167
+ allow(@dummy).to receive(:new_record?).and_return(false)
168
+ end
169
+
170
+ it "returns a protocol-relative URL" do
171
+ assert_match %r{^//s3.amazonaws.com/bucket/avatars/data[^\.]}, @dummy.avatar.url
172
+ end
173
+ end
174
+
175
+ context "s3_protocol: :https" do
176
+ before do
177
+ rebuild_model aws2_add_region.merge storage: :s3,
178
+ s3_credentials: {},
179
+ s3_protocol: :https,
180
+ bucket: "bucket",
181
+ path: ":attachment/:basename:dotextension"
182
+ @dummy = Dummy.new
183
+ @dummy.avatar = stringy_file
184
+ allow(@dummy).to receive(:new_record?).and_return(false)
185
+ end
186
+
187
+ it "returns a url based on an S3 path" do
188
+ assert_match %r{^https://s3.amazonaws.com/bucket/avatars/data[^\.]}, @dummy.avatar.url
189
+ end
190
+ end
191
+
192
+ context "s3_protocol: ''" do
193
+ before do
194
+ rebuild_model aws2_add_region.merge storage: :s3,
195
+ s3_credentials: {},
196
+ s3_protocol: "",
197
+ bucket: "bucket",
198
+ path: ":attachment/:basename:dotextension"
199
+ @dummy = Dummy.new
200
+ @dummy.avatar = stringy_file
201
+ allow(@dummy).to receive(:new_record?).and_return(false)
202
+ end
203
+
204
+ it "returns a url based on an S3 path" do
205
+ assert_match %r{^//s3.amazonaws.com/bucket/avatars/data[^\.]}, @dummy.avatar.url
206
+ end
207
+ end
208
+
209
+ context "An attachment that uses S3 for storage and has the style in the path" do
210
+ before do
211
+ rebuild_model aws2_add_region.merge storage: :s3,
212
+ bucket: "testing",
213
+ path: ":attachment/:style/:basename:dotextension",
214
+ styles: {
215
+ thumb: "80x80>"
216
+ },
217
+ s3_credentials: {
218
+ "access_key_id" => "12345",
219
+ "secret_access_key" => "54321"
220
+ }
221
+
222
+ @dummy = Dummy.new
223
+ @dummy.avatar = stringy_file
224
+ @avatar = @dummy.avatar
225
+ end
226
+
227
+ it "uses an S3 object based on the correct path for the default style" do
228
+ assert_equal("avatars/original/data", @dummy.avatar.s3_object.key)
229
+ end
230
+
231
+ it "uses an S3 object based on the correct path for the custom style" do
232
+ assert_equal("avatars/thumb/data", @dummy.avatar.s3_object(:thumb).key)
233
+ end
234
+ end
235
+
236
+ # the s3_host_name will be defined by the s3_region
237
+ context "s3_host_name" do
238
+ before do
239
+ rebuild_model storage: :s3,
240
+ s3_credentials: {},
241
+ bucket: "bucket",
242
+ path: ":attachment/:basename:dotextension",
243
+ s3_host_name: "s3-ap-northeast-1.amazonaws.com",
244
+ s3_region: "ap-northeast-1"
245
+ @dummy = Dummy.new
246
+ @dummy.avatar = stringy_file
247
+ allow(@dummy).to receive(:new_record?).and_return(false)
248
+ end
249
+
250
+ it "returns a url based on an :s3_host_name path" do
251
+ assert_match %r{^//s3-ap-northeast-1.amazonaws.com/bucket/avatars/data[^\.]}, @dummy.avatar.url
252
+ end
253
+
254
+ it "uses the S3 bucket with the correct host name" do
255
+ assert_equal "s3.ap-northeast-1.amazonaws.com",
256
+ @dummy.avatar.s3_bucket.client.config.endpoint.host
257
+ end
258
+ end
259
+
260
+ context "dynamic s3_host_name" do
261
+ before do
262
+ rebuild_model aws2_add_region.merge storage: :s3,
263
+ s3_credentials: {},
264
+ bucket: "bucket",
265
+ path: ":attachment/:basename:dotextension",
266
+ s3_host_name: lambda { |a| a.instance.value }
267
+ @dummy = Dummy.new
268
+ class << @dummy
269
+ attr_accessor :value
270
+ end
271
+ @dummy.avatar = stringy_file
272
+ allow(@dummy).to receive(:new_record?).and_return(false)
273
+ end
274
+
275
+ it "uses s3_host_name as a proc if available" do
276
+ @dummy.value = "s3.something.com"
277
+ assert_equal "//s3.something.com/bucket/avatars/data", @dummy.avatar.url(:original, timestamp: false)
278
+ end
279
+ end
280
+
281
+ context "use_accelerate_endpoint" do
282
+ context "defaults to false" do
283
+ before do
284
+ rebuild_model(
285
+ storage: :s3,
286
+ s3_credentials: {},
287
+ bucket: "bucket",
288
+ path: ":attachment/:basename:dotextension",
289
+ s3_host_name: "s3-ap-northeast-1.amazonaws.com",
290
+ s3_region: "ap-northeast-1"
291
+ )
292
+ @dummy = Dummy.new
293
+ @dummy.avatar = stringy_file
294
+ allow(@dummy).to receive(:new_record?).and_return(false)
295
+ end
296
+
297
+ it "returns a url based on an :s3_host_name path" do
298
+ assert_match %r{^//s3-ap-northeast-1.amazonaws.com/bucket/avatars/data[^\.]},
299
+ @dummy.avatar.url
300
+ end
301
+
302
+ it "uses the S3 client with the use_accelerate_endpoint config is false" do
303
+ expect(@dummy.avatar.s3_bucket.client.config.use_accelerate_endpoint).to be(false)
304
+ end
305
+ end
306
+
307
+ context "set to true" do
308
+ before do
309
+ rebuild_model(
310
+ storage: :s3,
311
+ s3_credentials: {},
312
+ bucket: "bucket",
313
+ path: ":attachment/:basename:dotextension",
314
+ s3_host_name: "s3-accelerate.amazonaws.com",
315
+ s3_region: "ap-northeast-1",
316
+ use_accelerate_endpoint: true
317
+ )
318
+ @dummy = Dummy.new
319
+ @dummy.avatar = stringy_file
320
+ allow(@dummy).to receive(:new_record?).and_return(false)
321
+ end
322
+
323
+ it "returns a url based on an :s3_host_name path" do
324
+ assert_match %r{^//s3-accelerate.amazonaws.com/bucket/avatars/data[^\.]},
325
+ @dummy.avatar.url
326
+ end
327
+
328
+ it "uses the S3 client with the use_accelerate_endpoint config is true" do
329
+ expect(@dummy.avatar.s3_bucket.client.config.use_accelerate_endpoint).to be(true)
330
+ end
331
+ end
332
+ end
333
+
334
+ context "An attachment that uses S3 for storage and has styles that return different file types" do
335
+ before do
336
+ rebuild_model aws2_add_region.merge storage: :s3,
337
+ styles: { large: ["500x500#", :jpg] },
338
+ bucket: "bucket",
339
+ path: ":attachment/:basename:dotextension",
340
+ s3_credentials: {
341
+ "access_key_id" => "12345",
342
+ "secret_access_key" => "54321"
343
+ }
344
+
345
+ File.open(fixture_file("5k.png"), "rb") do |file|
346
+ @dummy = Dummy.new
347
+ @dummy.avatar = file
348
+ allow(@dummy).to receive(:new_record?).and_return(false)
349
+ end
350
+ end
351
+
352
+ it "returns a url containing the correct original file mime type" do
353
+ assert_match /.+\/5k.png/, @dummy.avatar.url
354
+ end
355
+
356
+ it "uses the correct key for the original file mime type" do
357
+ assert_match /.+\/5k.png/, @dummy.avatar.s3_object.key
358
+ end
359
+
360
+ it "returns a url containing the correct processed file mime type" do
361
+ assert_match /.+\/5k.jpg/, @dummy.avatar.url(:large)
362
+ end
363
+
364
+ it "uses the correct key for the processed file mime type" do
365
+ assert_match /.+\/5k.jpg/, @dummy.avatar.s3_object(:large).key
366
+ end
367
+ end
368
+
369
+ context "An attachment that uses S3 for storage and has a proc for styles" do
370
+ before do
371
+ rebuild_model aws2_add_region.merge storage: :s3,
372
+ styles: lambda { |attachment|
373
+ attachment.instance.counter
374
+ { thumbnail: { geometry: "50x50#",
375
+ s3_headers: { "Cache-Control" => "max-age=31557600" } } }
376
+ },
377
+ bucket: "bucket",
378
+ path: ":attachment/:style/:basename:dotextension",
379
+ s3_credentials: {
380
+ "access_key_id" => "12345",
381
+ "secret_access_key" => "54321"
382
+ }
383
+
384
+ @file = File.new(fixture_file("5k.png"), "rb")
385
+
386
+ Dummy.class_eval do
387
+ def counter
388
+ @counter ||= 0
389
+ @counter += 1
390
+ @counter
391
+ end
392
+ end
393
+
394
+ @dummy = Dummy.new
395
+ @dummy.avatar = @file
396
+ object = double
397
+
398
+ if s3_uses_transfer_manager?
399
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(object)
400
+ else
401
+ allow(@dummy.avatar).to receive(:s3_object).with(:original).and_return(object)
402
+ allow(@dummy.avatar).to receive(:s3_object).with(:thumbnail).and_return(object)
403
+ end
404
+ expect(object).to receive(:upload_file).
405
+ with(
406
+ anything,
407
+ hash_including(
408
+ content_type: "image/png",
409
+ acl: :"public-read",
410
+ ),
411
+ )
412
+ expect(object).to receive(:upload_file).
413
+ with(
414
+ anything,
415
+ hash_including(
416
+ content_type: "image/png",
417
+ acl: :"public-read",
418
+ cache_control: "max-age=31557600",
419
+ ),
420
+ )
421
+ @dummy.save
422
+ end
423
+
424
+ after { @file.close }
425
+
426
+ it "succeeds" do
427
+ if s3_uses_transfer_manager?
428
+ count = 9
429
+ else
430
+ count = 7
431
+ end
432
+ assert_equal @dummy.counter, count
433
+ end
434
+ end
435
+
436
+ context "An attachment that uses S3 for storage with acl disabled" do
437
+ before do
438
+ rebuild_model(
439
+ aws2_add_region.merge(
440
+ storage: :s3,
441
+ styles: { thumb: ["90x90#", :jpg] },
442
+ bucket: "bucket",
443
+ s3_acl_enabled: false,
444
+ s3_credentials: {
445
+ "access_key_id" => "12345",
446
+ "secret_access_key" => "54321"
447
+ }
448
+ )
449
+ )
450
+
451
+ @file = File.new(fixture_file("5k.png"), "rb")
452
+ @dummy = Dummy.new
453
+ @dummy.avatar = @file
454
+ @dummy.save
455
+ end
456
+
457
+ context "reprocess" do
458
+ before do
459
+ @object = double
460
+ if s3_uses_transfer_manager?
461
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(@object)
462
+ allow(@object).to receive(:download_file).with(any_args)
463
+ else
464
+ allow(@dummy.avatar).to receive(:s3_object).with(:original).and_return(@object)
465
+ allow(@dummy.avatar).to receive(:s3_object).with(:thumb).and_return(@object)
466
+ allow(@object).to receive(:get).and_yield(@file.read)
467
+ allow(@object).to receive(:exists?).and_return(true)
468
+ allow(@object).to receive(:download_file).with(anything)
469
+ end
470
+ end
471
+
472
+ it "uploads original" do
473
+ expect(@object).to receive(:upload_file).with(
474
+ anything,
475
+ hash_including(content_type: "image/png"),
476
+ ).and_return(true)
477
+ @dummy.avatar.reprocess!
478
+ expect(@object).to receive(:upload_file).with(
479
+ anything,
480
+ hash_including(content_type: "image/png"),
481
+ ).and_return(true)
482
+ @dummy.avatar.reprocess!
483
+ end
484
+
485
+ it "doesn't upload original" do
486
+ expect(@object).to receive(:upload_file).with(
487
+ anything,
488
+ hash_including(content_type: "image/png"),
489
+ ).and_return(true)
490
+ @dummy.avatar.reprocess!
491
+ end
492
+ end
493
+
494
+ after { @file.close }
495
+ end
496
+
497
+ context "An attachment that uses S3 for storage and has styles" do
498
+ before do
499
+ rebuild_model(
500
+ aws2_add_region.merge(
501
+ storage: :s3,
502
+ styles: { thumb: ["90x90#", :jpg] },
503
+ bucket: "bucket",
504
+ s3_credentials: {
505
+ "access_key_id" => "12345",
506
+ "secret_access_key" => "54321"
507
+ }
508
+ )
509
+ )
510
+
511
+ @file = File.new(fixture_file("5k.png"), "rb")
512
+ @dummy = Dummy.new
513
+ @dummy.avatar = @file
514
+ @dummy.save
515
+ end
516
+
517
+ context "reprocess" do
518
+ before do
519
+ @object = double
520
+ if s3_uses_transfer_manager?
521
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(@object)
522
+ allow(@object).to receive(:download_file).with(any_args)
523
+ else
524
+ allow(@dummy.avatar).to receive(:s3_object).with(:original).and_return(@object)
525
+ allow(@dummy.avatar).to receive(:s3_object).with(:thumb).and_return(@object)
526
+ allow(@object).to receive(:get).and_yield(@file.read)
527
+ allow(@object).to receive(:exists?).and_return(true)
528
+ allow(@object).to receive(:download_file).with(anything)
529
+ end
530
+ end
531
+
532
+ it "uploads original" do
533
+ expect(@object).to receive(:upload_file).with(
534
+ anything,
535
+ hash_including(
536
+ content_type: "image/png",
537
+ acl: :"public-read",
538
+ ),
539
+ ).and_return(true)
540
+ @dummy.avatar.reprocess!
541
+ expect(@object).to receive(:upload_file).with(
542
+ anything,
543
+ hash_including(
544
+ content_type: "image/png",
545
+ acl: :"public-read",
546
+ ),
547
+ ).and_return(true)
548
+ @dummy.avatar.reprocess!
549
+ end
550
+
551
+ it "doesn't upload original" do
552
+ expect(@object).to receive(:upload_file).with(
553
+ anything,
554
+ hash_including(
555
+ content_type: "image/png",
556
+ acl: :"public-read",
557
+ ),
558
+ ).and_return(true)
559
+ @dummy.avatar.reprocess!
560
+ end
561
+ end
562
+
563
+ after { @file.close }
564
+ end
565
+
566
+ context "An attachment that uses S3 for storage and has spaces in file name" do
567
+ before do
568
+ rebuild_model(
569
+ aws2_add_region.merge(storage: :s3,
570
+ styles: { large: ["500x500#", :jpg] },
571
+ bucket: "bucket",
572
+ s3_credentials: { "access_key_id" => "12345",
573
+ "secret_access_key" => "54321" })
574
+ )
575
+
576
+ File.open(fixture_file("spaced file.png"), "rb") do |file|
577
+ @dummy = Dummy.new
578
+ @dummy.avatar = file
579
+ allow(@dummy).to receive(:new_record?).and_return(false)
580
+ end
581
+ end
582
+
583
+ it "returns a replaced version for path" do
584
+ assert_match /.+\/spaced_file\.png/, @dummy.avatar.path
585
+ end
586
+
587
+ it "returns a replaced version for url" do
588
+ assert_match /.+\/spaced_file\.png/, @dummy.avatar.url
589
+ end
590
+ end
591
+
592
+ context "An attachment that uses S3 for storage and has a question mark in file name" do
593
+ before do
594
+ rebuild_model aws2_add_region.merge storage: :s3,
595
+ styles: { large: ["500x500#", :jpg] },
596
+ bucket: "bucket",
597
+ s3_credentials: {
598
+ "access_key_id" => "12345",
599
+ "secret_access_key" => "54321"
600
+ }
601
+
602
+ stringio = stringy_file
603
+ class << stringio
604
+ def original_filename
605
+ "question?mark.png"
606
+ end
607
+ end
608
+ file = Paperclip.io_adapters.for(stringio, hash_digest: Digest::MD5)
609
+ @dummy = Dummy.new
610
+ @dummy.avatar = file
611
+ @dummy.save
612
+ allow(@dummy).to receive(:new_record?).and_return(false)
613
+ end
614
+
615
+ it "returns a replaced version for path" do
616
+ assert_match /.+\/question_mark\.png/, @dummy.avatar.path
617
+ end
618
+
619
+ it "returns a replaced version for url" do
620
+ assert_match /.+\/question_mark\.png/, @dummy.avatar.url
621
+ end
622
+ end
623
+
624
+ context "" do
625
+ before do
626
+ rebuild_model aws2_add_region.merge storage: :s3,
627
+ s3_credentials: {},
628
+ bucket: "bucket",
629
+ path: ":attachment/:basename:dotextension",
630
+ url: ":s3_domain_url"
631
+ @dummy = Dummy.new
632
+ @dummy.avatar = stringy_file
633
+ allow(@dummy).to receive(:new_record?).and_return(false)
634
+ end
635
+
636
+ it "returns a url based on an S3 subdomain" do
637
+ assert_match %r{^//bucket.s3.amazonaws.com/avatars/data[^\.]}, @dummy.avatar.url
638
+ end
639
+ end
640
+
641
+ context "" do
642
+ before do
643
+ rebuild_model(
644
+ aws2_add_region.merge(storage: :s3,
645
+ s3_credentials: {
646
+ production: { bucket: "prod_bucket" },
647
+ development: { bucket: "dev_bucket" }
648
+ },
649
+ bucket: "bucket",
650
+ s3_host_alias: "something.something.com",
651
+ path: ":attachment/:basename:dotextension",
652
+ url: ":s3_alias_url")
653
+ )
654
+ @dummy = Dummy.new
655
+ @dummy.avatar = stringy_file
656
+ allow(@dummy).to receive(:new_record?).and_return(false)
657
+ end
658
+
659
+ it "returns a url based on the host_alias" do
660
+ assert_match %r{^//something.something.com/avatars/data[^\.]}, @dummy.avatar.url
661
+ end
662
+ end
663
+
664
+ context "generating a url with a prefixed host alias" do
665
+ before do
666
+ rebuild_model(
667
+ aws2_add_region.merge(
668
+ storage: :s3,
669
+ s3_credentials: {
670
+ production: { bucket: "prod_bucket" },
671
+ development: { bucket: "dev_bucket" },
672
+ },
673
+ bucket: "bucket",
674
+ s3_host_alias: "something.something.com",
675
+ s3_prefixes_in_alias: 2,
676
+ path: "prefix1/prefix2/:attachment/:basename:dotextension",
677
+ url: ":s3_alias_url"
678
+ )
679
+ )
680
+ @dummy = Dummy.new
681
+ @dummy.avatar = stringy_file
682
+ allow(@dummy).to receive(:new_record?).and_return(false)
683
+ end
684
+
685
+ it "returns a url with the prefixes removed" do
686
+ assert_match %r{^//something.something.com/avatars/data[^\.]},
687
+ @dummy.avatar.url
688
+ end
689
+ end
690
+
691
+ context "generating a url with a proc as the host alias" do
692
+ before do
693
+ rebuild_model aws2_add_region.merge storage: :s3,
694
+ s3_credentials: { bucket: "prod_bucket" },
695
+ s3_host_alias: Proc.new { |atch| "cdn#{atch.instance.counter % 4}.example.com" },
696
+ path: ":attachment/:basename:dotextension",
697
+ url: ":s3_alias_url"
698
+ Dummy.class_eval do
699
+ def counter
700
+ @counter ||= 0
701
+ @counter += 1
702
+ @counter
703
+ end
704
+ end
705
+ @dummy = Dummy.new
706
+ @dummy.avatar = stringy_file
707
+ allow(@dummy).to receive(:new_record?).and_return(false)
708
+ end
709
+
710
+ it "returns a url based on the host_alias" do
711
+ assert_match %r{^//cdn1.example.com/avatars/data[^\.]}, @dummy.avatar.url
712
+ assert_match %r{^//cdn2.example.com/avatars/data[^\.]}, @dummy.avatar.url
713
+ end
714
+
715
+ it "still returns the bucket name" do
716
+ assert_equal "prod_bucket", @dummy.avatar.bucket_name
717
+ end
718
+ end
719
+
720
+ context "" do
721
+ before do
722
+ rebuild_model aws2_add_region.merge storage: :s3,
723
+ s3_credentials: {},
724
+ bucket: "bucket",
725
+ path: ":attachment/:basename:dotextension",
726
+ url: ":asset_host"
727
+ @dummy = Dummy.new
728
+ @dummy.avatar = stringy_file
729
+ allow(@dummy).to receive(:new_record?).and_return(false)
730
+ end
731
+
732
+ it "returns a relative URL for Rails to calculate assets host" do
733
+ assert_match %r{^avatars/data[^\.]}, @dummy.avatar.url
734
+ end
735
+ end
736
+
737
+ context "Generating a secure url with an expiration" do
738
+ before do
739
+ @build_model_with_options = lambda { |options|
740
+ base_options = {
741
+ storage: :s3,
742
+ s3_credentials: {
743
+ production: { bucket: "prod_bucket" },
744
+ development: { bucket: "dev_bucket" }
745
+ },
746
+ s3_host_alias: "something.something.com",
747
+ s3_permissions: "private",
748
+ path: ":attachment/:basename:dotextension",
749
+ url: ":s3_alias_url"
750
+ }
751
+
752
+ rebuild_model aws2_add_region.merge base_options.merge(options)
753
+ }
754
+ end
755
+
756
+ it "uses default options" do
757
+ @build_model_with_options[{}]
758
+
759
+ rails_env("production") do
760
+ @dummy = Dummy.new
761
+ @dummy.avatar = stringy_file
762
+
763
+ object = double
764
+ allow(@dummy.avatar).to receive(:s3_object).and_return(object)
765
+
766
+ expect(object).to receive(:presigned_url).with(:get, { expires_in: 3600 })
767
+ @dummy.avatar.expiring_url
768
+ end
769
+ end
770
+
771
+ it "allows overriding s3_url_options" do
772
+ @build_model_with_options[s3_url_options: { response_content_disposition: "inline" }]
773
+
774
+ rails_env("production") do
775
+ @dummy = Dummy.new
776
+ @dummy.avatar = stringy_file
777
+
778
+ object = double
779
+ allow(@dummy.avatar).to receive(:s3_object).and_return(object)
780
+ expect(object).to receive(:presigned_url).
781
+ with(
782
+ :get,
783
+ {
784
+ expires_in: 3600,
785
+ response_content_disposition: "inline",
786
+ },
787
+ )
788
+ @dummy.avatar.expiring_url
789
+ end
790
+ end
791
+
792
+ it "allows overriding s3_object options with a proc" do
793
+ @build_model_with_options[s3_url_options: lambda { |attachment| { response_content_type: attachment.avatar_content_type } }]
794
+
795
+ rails_env("production") do
796
+ @dummy = Dummy.new
797
+
798
+ @file = stringy_file
799
+ allow(@file).to receive(:original_filename).and_return("5k.png\n\n")
800
+ allow(Paperclip).to receive(:run).and_return("image/png")
801
+ allow(@file).to receive(:content_type).and_return("image/png\n\n")
802
+ allow(@file).to receive(:to_tempfile).and_return(@file)
803
+
804
+ @dummy.avatar = @file
805
+
806
+ object = double
807
+ allow(@dummy.avatar).to receive(:s3_object).and_return(object)
808
+ expect(object).to receive(:presigned_url).
809
+ with(
810
+ :get,
811
+ {
812
+ expires_in: 3600,
813
+ response_content_type: "image/png",
814
+ },
815
+ )
816
+ @dummy.avatar.expiring_url
817
+ end
818
+ end
819
+ end
820
+
821
+ context "#expiring_url" do
822
+ before { @dummy = Dummy.new }
823
+
824
+ context "with no attachment" do
825
+ before { assert(!@dummy.avatar.exists?) }
826
+
827
+ it "returns the default URL" do
828
+ assert_equal(@dummy.avatar.url, @dummy.avatar.expiring_url)
829
+ end
830
+
831
+ it "generates a url for a style when a file does not exist" do
832
+ assert_equal(@dummy.avatar.url(:thumb), @dummy.avatar.expiring_url(3600, :thumb))
833
+ end
834
+ end
835
+
836
+ it "generates the same url when using Times and Integer offsets" do
837
+ assert_equal @dummy.avatar.expiring_url(1234), @dummy.avatar.expiring_url(Time.now + 1234)
838
+ end
839
+ end
840
+
841
+ context "Generating a url with an expiration for each style" do
842
+ before do
843
+ rebuild_model aws2_add_region.merge storage: :s3,
844
+ s3_credentials: {
845
+ production: { bucket: "prod_bucket" },
846
+ development: { bucket: "dev_bucket" }
847
+ },
848
+ s3_permissions: :private,
849
+ s3_host_alias: "something.something.com",
850
+ path: ":attachment/:style/:basename:dotextension",
851
+ url: ":s3_alias_url"
852
+
853
+ rails_env("production") do
854
+ @dummy = Dummy.new
855
+ @dummy.avatar = stringy_file
856
+ end
857
+ end
858
+
859
+ it "generates a url for the thumb" do
860
+ object = double
861
+ allow(@dummy.avatar).to receive(:s3_object).with(:thumb).and_return(object)
862
+ expect(object).to receive(:presigned_url).with(:get, { expires_in: 1800 })
863
+ @dummy.avatar.expiring_url(1800, :thumb)
864
+ end
865
+
866
+ it "generates a url for the default style" do
867
+ object = double
868
+ allow(@dummy.avatar).to receive(:s3_object).with(:original).and_return(object)
869
+ expect(object).to receive(:presigned_url).with(:get, { expires_in: 1800 })
870
+ @dummy.avatar.expiring_url(1800)
871
+ end
872
+ end
873
+
874
+ context "Parsing S3 credentials with a bucket in them" do
875
+ before do
876
+ rebuild_model aws2_add_region.merge storage: :s3,
877
+ s3_credentials: {
878
+ production: { bucket: "prod_bucket" },
879
+ development: { bucket: "dev_bucket" }
880
+ }
881
+ @dummy = Dummy.new
882
+ end
883
+
884
+ it "gets the right bucket in production" do
885
+ rails_env("production") do
886
+ assert_equal "prod_bucket", @dummy.avatar.bucket_name
887
+ assert_equal "prod_bucket", @dummy.avatar.s3_bucket.name
888
+ end
889
+ end
890
+
891
+ it "gets the right bucket in development" do
892
+ rails_env("development") do
893
+ assert_equal "dev_bucket", @dummy.avatar.bucket_name
894
+ assert_equal "dev_bucket", @dummy.avatar.s3_bucket.name
895
+ end
896
+ end
897
+ end
898
+
899
+ # the bucket.name is determined by the :s3_region
900
+ context "Parsing S3 credentials with a s3_host_name in them" do
901
+ before do
902
+ rebuild_model storage: :s3,
903
+ bucket: "testing",
904
+ s3_credentials: {
905
+ production: {
906
+ s3_region: "world-end",
907
+ s3_host_name: "s3-world-end.amazonaws.com"
908
+ },
909
+ development: {
910
+ s3_region: "ap-northeast-1",
911
+ s3_host_name: "s3-ap-northeast-1.amazonaws.com"
912
+ },
913
+ test: {}
914
+ }
915
+ @dummy = Dummy.new
916
+ end
917
+
918
+ it "gets the right s3_host_name in production" do
919
+ rails_env("production") do
920
+ assert_match %r{^s3-world-end.amazonaws.com}, @dummy.avatar.s3_host_name
921
+ assert_match %r{^s3.world-end.amazonaws.com},
922
+ @dummy.avatar.s3_bucket.client.config.endpoint.host
923
+ end
924
+ end
925
+
926
+ it "gets the right s3_host_name in development" do
927
+ rails_env("development") do
928
+ assert_match %r{^s3.ap-northeast-1.amazonaws.com},
929
+ @dummy.avatar.s3_host_name
930
+ assert_match %r{^s3.ap-northeast-1.amazonaws.com},
931
+ @dummy.avatar.s3_bucket.client.config.endpoint.host
932
+ end
933
+ end
934
+
935
+ it "gets the right s3_host_name if the key does not exist" do
936
+ rails_env("test") do
937
+ assert_match %r{^s3.amazonaws.com}, @dummy.avatar.s3_host_name
938
+ assert_raises(Aws::Errors::MissingRegionError) do
939
+ @dummy.avatar.s3_bucket.client.config.endpoint.host
940
+ end
941
+ end
942
+ end
943
+ end
944
+
945
+ context "An attachment with S3 storage" do
946
+ before do
947
+ rebuild_model aws2_add_region.merge storage: :s3,
948
+ bucket: "testing",
949
+ path: ":attachment/:style/:basename:dotextension",
950
+ s3_credentials: {
951
+ access_key_id: "12345",
952
+ secret_access_key: "54321"
953
+ }
954
+ end
955
+
956
+ it "is extended by the S3 module" do
957
+ assert Dummy.new.avatar.is_a?(Paperclip::Storage::S3)
958
+ end
959
+
960
+ it "won't be extended by the Filesystem module" do
961
+ assert !Dummy.new.avatar.is_a?(Paperclip::Storage::Filesystem)
962
+ end
963
+
964
+ context "when assigned" do
965
+ before do
966
+ @file = File.new(fixture_file("5k.png"), "rb")
967
+ @dummy = Dummy.new
968
+ @dummy.avatar = @file
969
+ allow(@dummy).to receive(:new_record?).and_return(false)
970
+ end
971
+
972
+ after { @file.close }
973
+
974
+ it "does not get a bucket to get a URL" do
975
+ expect(@dummy.avatar).to_not receive(:s3)
976
+ expect(@dummy.avatar).to_not receive(:s3_bucket)
977
+ assert_match %r{^//s3\.amazonaws\.com/testing/avatars/original/5k\.png}, @dummy.avatar.url
978
+ end
979
+
980
+ it "is rewound after flush_writes" do
981
+ @dummy.avatar.instance_eval "def after_flush_writes; end"
982
+ allow(@dummy.avatar).to receive(:s3_object).and_return(double(upload_file: true))
983
+ files = @dummy.avatar.queued_for_write.values.each(&:read)
984
+ @dummy.save
985
+ assert files.none?(&:eof?), "Expect all the files to be rewound."
986
+ end
987
+
988
+ it "is removed after after_flush_writes" do
989
+ allow(@dummy.avatar).to receive(:s3_object).and_return(double(upload_file: true))
990
+ paths = @dummy.avatar.queued_for_write.values.map(&:path)
991
+ @dummy.save
992
+ assert paths.none? { |path| File.exist?(path) },
993
+ "Expect all the files to be deleted."
994
+ end
995
+
996
+ it "will retry to save again but back off on SlowDown" do
997
+ allow(@dummy.avatar).to receive(:sleep)
998
+ err = Aws::S3::Errors::SlowDown.new(spy, spy(status: 503, body: ""))
999
+ if s3_uses_transfer_manager?
1000
+ allow_any_instance_of(Aws::S3::TransferManager).to receive(:upload_file).and_raise(err)
1001
+ else
1002
+ allow_any_instance_of(Aws::S3::Object).to receive(:upload_file).and_raise(err)
1003
+ end
1004
+ expect { @dummy.save }.to raise_error(Aws::S3::Errors::SlowDown)
1005
+ expect(@dummy.avatar).to have_received(:sleep).with(1)
1006
+ expect(@dummy.avatar).to have_received(:sleep).with(2)
1007
+ expect(@dummy.avatar).to have_received(:sleep).with(4)
1008
+ expect(@dummy.avatar).to have_received(:sleep).with(8)
1009
+ expect(@dummy.avatar).to have_received(:sleep).with(16)
1010
+ end
1011
+
1012
+ context "and saved" do
1013
+ before do
1014
+ object = double
1015
+ if s3_uses_transfer_manager?
1016
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(object)
1017
+ else
1018
+ allow(@dummy.avatar).to receive(:s3_object).and_return(object)
1019
+ end
1020
+ expect(object).to receive(:upload_file).
1021
+ with(anything, hash_including(content_type: "image/png", acl: :"public-read"))
1022
+ @dummy.save
1023
+ end
1024
+
1025
+ it "succeeds" do
1026
+ assert true
1027
+ end
1028
+ end
1029
+
1030
+ context "and saved without a bucket" do
1031
+ before do
1032
+ allow_any_instance_of(Aws::S3::Object).to receive(:upload_file).
1033
+ and_raise(Aws::S3::Errors::NoSuchBucket.new(double, double(status: 404, body: "<foo/>", empty?: false)))
1034
+ allow_any_instance_of(Aws::S3::Object).to receive(:upload_file).and_return(nil)
1035
+ @dummy.save
1036
+ end
1037
+
1038
+ it "succeeds" do
1039
+ assert true
1040
+ end
1041
+ end
1042
+
1043
+ context "and remove" do
1044
+ before do
1045
+ allow_any_instance_of(Aws::S3::Object).to receive(:exists?).and_return(true)
1046
+ allow_any_instance_of(Aws::S3::Object).to receive(:delete)
1047
+ @dummy.destroy
1048
+ end
1049
+
1050
+ it "succeeds" do
1051
+ assert true
1052
+ end
1053
+ end
1054
+
1055
+ context "and remove, calling S3 Object destroy once per unique style" do
1056
+ before do
1057
+ # Create fresh dummy without the new_record? stub from parent context
1058
+ @dummy = Dummy.new
1059
+ @dummy.avatar = @file
1060
+
1061
+ # Mock S3 upload and save
1062
+ if s3_uses_transfer_manager?
1063
+ allow_any_instance_of(Aws::S3::TransferManager).to receive(:upload_file).and_return(true)
1064
+ else
1065
+ allow_any_instance_of(Aws::S3::Object).to receive(:upload_file).and_return(true)
1066
+ end
1067
+ @dummy.save!
1068
+
1069
+ # Now set up expectations for destroy
1070
+ allow_any_instance_of(Aws::S3::Object).to receive(:exists?).and_return(true)
1071
+ expect_any_instance_of(Aws::S3::Object).to receive(:delete).once
1072
+ @dummy.avatar.clear(:original)
1073
+ @dummy.destroy
1074
+ end
1075
+
1076
+ it "succeeds" do
1077
+ assert true
1078
+ end
1079
+ end
1080
+
1081
+ context "that the file were missing" do
1082
+ before do
1083
+ allow_any_instance_of(Aws::S3::Object).to receive(:exists?).
1084
+ and_raise(Aws::S3::Errors::ServiceError.new("rspec stub raises",
1085
+ "object exists?"))
1086
+ end
1087
+
1088
+ it "returns false on exists?" do
1089
+ assert !@dummy.avatar.exists?
1090
+ end
1091
+ end
1092
+ end
1093
+ end
1094
+
1095
+ context "An attachment with S3 storage and bucket defined as a Proc" do
1096
+ before do
1097
+ rebuild_model aws2_add_region.merge storage: :s3,
1098
+ bucket: lambda { |attachment| "bucket_#{attachment.instance.other}" },
1099
+ s3_credentials: { not: :important }
1100
+ end
1101
+
1102
+ it "gets the right bucket name" do
1103
+ assert "bucket_a", Dummy.new(other: "a").avatar.bucket_name
1104
+ assert "bucket_a", Dummy.new(other: "a").avatar.s3_bucket.name
1105
+ assert "bucket_b", Dummy.new(other: "b").avatar.bucket_name
1106
+ assert "bucket_b", Dummy.new(other: "b").avatar.s3_bucket.name
1107
+ end
1108
+ end
1109
+
1110
+ context "An attachment with S3 storage and S3 credentials defined as a Proc" do
1111
+ before do
1112
+ rebuild_model aws2_add_region.merge storage: :s3,
1113
+ bucket: { not: :important },
1114
+ s3_credentials: lambda { |attachment|
1115
+ Hash["access_key_id" => "access#{attachment.instance.other}", "secret_access_key" => "secret#{attachment.instance.other}"]
1116
+ }
1117
+ end
1118
+
1119
+ it "gets the right credentials" do
1120
+ assert "access1234", Dummy.new(other: "1234").avatar.s3_credentials[:access_key_id]
1121
+ assert "secret1234", Dummy.new(other: "1234").avatar.s3_credentials[:secret_access_key]
1122
+ end
1123
+ end
1124
+
1125
+ context "An attachment with S3 storage and S3 credentials with a :credential_provider" do
1126
+ before do
1127
+ class DummyCredentialProvider; end
1128
+
1129
+ rebuild_model aws2_add_region.merge storage: :s3,
1130
+ bucket: "testing",
1131
+ s3_credentials: {
1132
+ credentials: DummyCredentialProvider.new
1133
+ }
1134
+ @dummy = Dummy.new
1135
+ end
1136
+
1137
+ it "sets the credential-provider" do
1138
+ expect(@dummy.avatar.s3_bucket.client.config.credentials).to be_a DummyCredentialProvider
1139
+ end
1140
+ end
1141
+
1142
+ context "An attachment with S3 storage and S3 credentials in an unsupported manor" do
1143
+ before do
1144
+ rebuild_model aws2_add_region.merge storage: :s3,
1145
+ bucket: "testing", s3_credentials: ["unsupported"]
1146
+ @dummy = Dummy.new
1147
+ end
1148
+
1149
+ it "does not accept the credentials" do
1150
+ assert_raises(ArgumentError) do
1151
+ @dummy.avatar.s3_credentials
1152
+ end
1153
+ end
1154
+ end
1155
+
1156
+ context "An attachment with S3 storage and S3 credentials not supplied" do
1157
+ before do
1158
+ rebuild_model aws2_add_region.merge storage: :s3, bucket: "testing"
1159
+ @dummy = Dummy.new
1160
+ end
1161
+
1162
+ it "does not parse any credentials" do
1163
+ assert_equal({}, @dummy.avatar.s3_credentials)
1164
+ end
1165
+ end
1166
+
1167
+ context "An attachment with S3 storage and specific s3 headers set" do
1168
+ before do
1169
+ rebuild_model aws2_add_region.merge storage: :s3,
1170
+ bucket: "testing",
1171
+ path: ":attachment/:style/:basename:dotextension",
1172
+ s3_credentials: {
1173
+ "access_key_id" => "12345",
1174
+ "secret_access_key" => "54321"
1175
+ },
1176
+ s3_headers: { "Cache-Control" => "max-age=31557600" }
1177
+ end
1178
+
1179
+ context "when assigned" do
1180
+ before do
1181
+ @file = File.new(fixture_file("5k.png"), "rb")
1182
+ @dummy = Dummy.new
1183
+ @dummy.avatar = @file
1184
+ end
1185
+
1186
+ after { @file.close }
1187
+
1188
+ context "and saved" do
1189
+ before do
1190
+ object = double
1191
+ if s3_uses_transfer_manager?
1192
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(object)
1193
+ else
1194
+ allow(@dummy.avatar).to receive(:s3_object).and_return(object)
1195
+ end
1196
+ expect(object).to receive(:upload_file).
1197
+ with(
1198
+ anything,
1199
+ hash_including(
1200
+ content_type: "image/png",
1201
+ acl: :"public-read",
1202
+ cache_control: "max-age=31557600",
1203
+ ),
1204
+ )
1205
+ @dummy.save
1206
+ end
1207
+
1208
+ it "succeeds" do
1209
+ assert true
1210
+ end
1211
+ end
1212
+ end
1213
+ end
1214
+
1215
+ context "An attachment with S3 storage and metadata set using header names" do
1216
+ before do
1217
+ rebuild_model aws2_add_region.merge storage: :s3,
1218
+ bucket: "testing",
1219
+ path: ":attachment/:style/:basename:dotextension",
1220
+ s3_credentials: {
1221
+ "access_key_id" => "12345",
1222
+ "secret_access_key" => "54321"
1223
+ },
1224
+ s3_headers: { "x-amz-meta-color" => "red" }
1225
+ end
1226
+
1227
+ context "when assigned" do
1228
+ before do
1229
+ @file = File.new(fixture_file("5k.png"), "rb")
1230
+ @dummy = Dummy.new
1231
+ @dummy.avatar = @file
1232
+ end
1233
+
1234
+ after { @file.close }
1235
+
1236
+ context "and saved" do
1237
+ before do
1238
+ object = double
1239
+ if s3_uses_transfer_manager?
1240
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(object)
1241
+ else
1242
+ allow(@dummy.avatar).to receive(:s3_object).and_return(object)
1243
+ end
1244
+
1245
+ expect(object).to receive(:upload_file).
1246
+ with(
1247
+ anything,
1248
+ hash_including(
1249
+ content_type: "image/png",
1250
+ acl: :"public-read",
1251
+ metadata: { "color" => "red" },
1252
+ ),
1253
+ )
1254
+ @dummy.save
1255
+ end
1256
+
1257
+ it "succeeds" do
1258
+ assert true
1259
+ end
1260
+ end
1261
+ end
1262
+ end
1263
+
1264
+ context "An attachment with S3 storage and metadata set using the :s3_metadata option" do
1265
+ before do
1266
+ rebuild_model aws2_add_region.merge storage: :s3,
1267
+ bucket: "testing",
1268
+ path: ":attachment/:style/:basename:dotextension",
1269
+ s3_credentials: {
1270
+ "access_key_id" => "12345",
1271
+ "secret_access_key" => "54321"
1272
+ },
1273
+ s3_metadata: { "color" => "red" }
1274
+ end
1275
+
1276
+ context "when assigned" do
1277
+ before do
1278
+ @file = File.new(fixture_file("5k.png"), "rb")
1279
+ @dummy = Dummy.new
1280
+ @dummy.avatar = @file
1281
+ end
1282
+
1283
+ after { @file.close }
1284
+
1285
+ context "and saved" do
1286
+ before do
1287
+ object = double
1288
+ if s3_uses_transfer_manager?
1289
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(object)
1290
+ else
1291
+ allow(@dummy.avatar).to receive(:s3_object).and_return(object)
1292
+ end
1293
+
1294
+ expect(object).to receive(:upload_file).
1295
+ with(
1296
+ anything,
1297
+ hash_including(
1298
+ content_type: "image/png",
1299
+ acl: :"public-read",
1300
+ metadata: { "color" => "red" },
1301
+ ),
1302
+ )
1303
+ @dummy.save
1304
+ end
1305
+
1306
+ it "succeeds" do
1307
+ assert true
1308
+ end
1309
+ end
1310
+ end
1311
+ end
1312
+
1313
+ context "An attachment with S3 storage and storage class set" do
1314
+ context "using the header name" do
1315
+ before do
1316
+ rebuild_model aws2_add_region.merge storage: :s3,
1317
+ bucket: "testing",
1318
+ path: ":attachment/:style/:basename:dotextension",
1319
+ s3_credentials: {
1320
+ "access_key_id" => "12345",
1321
+ "secret_access_key" => "54321"
1322
+ },
1323
+ s3_headers: { "x-amz-storage-class" => "reduced_redundancy" }
1324
+ end
1325
+
1326
+ context "when assigned" do
1327
+ before do
1328
+ @file = File.new(fixture_file("5k.png"), "rb")
1329
+ @dummy = Dummy.new
1330
+ @dummy.avatar = @file
1331
+ end
1332
+
1333
+ after { @file.close }
1334
+
1335
+ context "and saved" do
1336
+ before do
1337
+ object = double
1338
+ if s3_uses_transfer_manager?
1339
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(object)
1340
+ else
1341
+ allow(@dummy.avatar).to receive(:s3_object).and_return(object)
1342
+ end
1343
+
1344
+ expect(object).to receive(:upload_file).
1345
+ with(
1346
+ anything,
1347
+ hash_including(
1348
+ content_type: "image/png",
1349
+ acl: :"public-read",
1350
+ storage_class: "reduced_redundancy",
1351
+ ),
1352
+ )
1353
+ @dummy.save
1354
+ end
1355
+
1356
+ it "succeeds" do
1357
+ assert true
1358
+ end
1359
+ end
1360
+ end
1361
+ end
1362
+
1363
+ context "using per style hash" do
1364
+ before do
1365
+ rebuild_model aws2_add_region.merge storage: :s3,
1366
+ bucket: "testing",
1367
+ path: ":attachment/:style/:basename.:extension",
1368
+ styles: {
1369
+ thumb: "80x80>"
1370
+ },
1371
+ s3_credentials: {
1372
+ "access_key_id" => "12345",
1373
+ "secret_access_key" => "54321"
1374
+ },
1375
+ s3_storage_class: {
1376
+ thumb: :reduced_redundancy
1377
+ }
1378
+ end
1379
+
1380
+ context "when assigned" do
1381
+ before do
1382
+ @file = File.new(fixture_file("5k.png"), "rb")
1383
+ @dummy = Dummy.new
1384
+ @dummy.avatar = @file
1385
+ end
1386
+
1387
+ after { @file.close }
1388
+
1389
+ context "and saved" do
1390
+ before do
1391
+ object = double
1392
+ [:thumb, :original].each do |style|
1393
+ if s3_uses_transfer_manager?
1394
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(object)
1395
+ else
1396
+ allow(@dummy.avatar).to receive(:s3_object).and_return(object)
1397
+ end
1398
+
1399
+ expected_options = {
1400
+ content_type: "image/png",
1401
+ acl: :"public-read"
1402
+ }
1403
+ expected_options.merge!(storage_class: :reduced_redundancy) if style == :thumb
1404
+
1405
+ expect(object).to receive(:upload_file).
1406
+ with(anything, hash_including(expected_options))
1407
+ end
1408
+ @dummy.save
1409
+ end
1410
+
1411
+ it "succeeds" do
1412
+ assert true
1413
+ end
1414
+ end
1415
+ end
1416
+ end
1417
+
1418
+ context "using global hash option" do
1419
+ before do
1420
+ rebuild_model aws2_add_region.merge storage: :s3,
1421
+ bucket: "testing",
1422
+ path: ":attachment/:style/:basename.:extension",
1423
+ styles: {
1424
+ thumb: "80x80>"
1425
+ },
1426
+ s3_credentials: {
1427
+ "access_key_id" => "12345",
1428
+ "secret_access_key" => "54321"
1429
+ },
1430
+ s3_storage_class: :reduced_redundancy
1431
+ end
1432
+
1433
+ context "when assigned" do
1434
+ before do
1435
+ @file = File.new(fixture_file("5k.png"), "rb")
1436
+ @dummy = Dummy.new
1437
+ @dummy.avatar = @file
1438
+ end
1439
+
1440
+ after { @file.close }
1441
+
1442
+ context "and saved" do
1443
+ before do
1444
+ object = double
1445
+ [:thumb, :original].each do |style|
1446
+ if s3_uses_transfer_manager?
1447
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(object)
1448
+ else
1449
+ allow(@dummy.avatar).to receive(:s3_object).and_return(object)
1450
+ end
1451
+
1452
+ expect(object).to receive(:upload_file).
1453
+ with(
1454
+ anything,
1455
+ hash_including(
1456
+ content_type: "image/png",
1457
+ acl: :"public-read",
1458
+ storage_class: :reduced_redundancy,
1459
+ ),
1460
+ )
1461
+ end
1462
+ @dummy.save
1463
+ end
1464
+
1465
+ it "succeeds" do
1466
+ assert true
1467
+ end
1468
+ end
1469
+ end
1470
+ end
1471
+ end
1472
+
1473
+ context "Can disable AES256 encryption multiple ways" do
1474
+ [nil, false, ""].each do |tech|
1475
+ before do
1476
+ rebuild_model(
1477
+ aws2_add_region.merge(storage: :s3,
1478
+ bucket: "testing",
1479
+ path: ":attachment/:style/:basename:dotextension",
1480
+ s3_credentials: {
1481
+ "access_key_id" => "12345",
1482
+ "secret_access_key" => "54321"
1483
+ },
1484
+ s3_server_side_encryption: tech)
1485
+ )
1486
+ end
1487
+
1488
+ context "when assigned" do
1489
+ before do
1490
+ @file = File.new(fixture_file("5k.png"), "rb")
1491
+ @dummy = Dummy.new
1492
+ @dummy.avatar = @file
1493
+ end
1494
+
1495
+ after { @file.close }
1496
+
1497
+ context "and saved" do
1498
+ before do
1499
+ object = double
1500
+ if s3_uses_transfer_manager?
1501
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(object)
1502
+ else
1503
+ allow(@dummy.avatar).to receive(:s3_object).and_return(object)
1504
+ end
1505
+
1506
+ expect(object).to receive(:upload_file).
1507
+ with(anything, hash_including(content_type: "image/png", acl: :"public-read"))
1508
+ @dummy.save
1509
+ end
1510
+
1511
+ it "succeeds" do
1512
+ assert true
1513
+ end
1514
+ end
1515
+ end
1516
+ end
1517
+ end
1518
+
1519
+ context "An attachment with S3 storage and using AES256 encryption" do
1520
+ before do
1521
+ rebuild_model aws2_add_region.merge storage: :s3,
1522
+ bucket: "testing",
1523
+ path: ":attachment/:style/:basename:dotextension",
1524
+ s3_credentials: {
1525
+ "access_key_id" => "12345",
1526
+ "secret_access_key" => "54321"
1527
+ },
1528
+ s3_server_side_encryption: "AES256"
1529
+ end
1530
+
1531
+ context "when assigned" do
1532
+ before do
1533
+ @file = File.new(fixture_file("5k.png"), "rb")
1534
+ @dummy = Dummy.new
1535
+ @dummy.avatar = @file
1536
+ end
1537
+
1538
+ after { @file.close }
1539
+
1540
+ context "and saved" do
1541
+ before do
1542
+ object = double
1543
+ if s3_uses_transfer_manager?
1544
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(object)
1545
+ else
1546
+ allow(@dummy.avatar).to receive(:s3_object).and_return(object)
1547
+ end
1548
+
1549
+ expect(object).to receive(:upload_file).
1550
+ with(
1551
+ anything,
1552
+ hash_including(
1553
+ content_type: "image/png",
1554
+ acl: :"public-read",
1555
+ server_side_encryption: "AES256",
1556
+ ),
1557
+ )
1558
+ @dummy.save
1559
+ end
1560
+
1561
+ it "succeeds" do
1562
+ assert true
1563
+ end
1564
+ end
1565
+ end
1566
+ end
1567
+
1568
+ context "An attachment with S3 storage and storage class set using the :storage_class option" do
1569
+ before do
1570
+ rebuild_model aws2_add_region.merge storage: :s3,
1571
+ bucket: "testing",
1572
+ path: ":attachment/:style/:basename:dotextension",
1573
+ s3_credentials: {
1574
+ "access_key_id" => "12345",
1575
+ "secret_access_key" => "54321"
1576
+ },
1577
+ s3_storage_class: :reduced_redundancy
1578
+ end
1579
+
1580
+ context "when assigned" do
1581
+ before do
1582
+ @file = File.new(fixture_file("5k.png"), "rb")
1583
+ @dummy = Dummy.new
1584
+ @dummy.avatar = @file
1585
+ end
1586
+
1587
+ after { @file.close }
1588
+
1589
+ context "and saved" do
1590
+ before do
1591
+ object = double
1592
+ if s3_uses_transfer_manager?
1593
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(object)
1594
+ else
1595
+ allow(@dummy.avatar).to receive(:s3_object).and_return(object)
1596
+ end
1597
+
1598
+ expect(object).to receive(:upload_file).
1599
+ with(
1600
+ anything,
1601
+ hash_including(
1602
+ content_type: "image/png",
1603
+ acl: :"public-read",
1604
+ storage_class: :reduced_redundancy,
1605
+ ),
1606
+ )
1607
+ @dummy.save
1608
+ end
1609
+
1610
+ it "succeeds" do
1611
+ assert true
1612
+ end
1613
+ end
1614
+ end
1615
+ end
1616
+
1617
+ context "with S3 credentials supplied as Pathname" do
1618
+ before do
1619
+ ENV["S3_KEY"] = "pathname_key"
1620
+ ENV["S3_BUCKET"] = "pathname_bucket"
1621
+ ENV["S3_SECRET"] = "pathname_secret"
1622
+
1623
+ rails_env("test") do
1624
+ rebuild_model aws2_add_region.merge storage: :s3,
1625
+ s3_credentials: Pathname.new(fixture_file("s3.yml"))
1626
+
1627
+ Dummy.delete_all
1628
+ @dummy = Dummy.new
1629
+ end
1630
+ end
1631
+
1632
+ it "parses the credentials" do
1633
+ assert_equal "pathname_bucket", @dummy.avatar.bucket_name
1634
+
1635
+ assert_equal "pathname_key",
1636
+ @dummy.avatar.s3_bucket.client.config.access_key_id
1637
+
1638
+ assert_equal "pathname_secret",
1639
+ @dummy.avatar.s3_bucket.client.config.secret_access_key
1640
+ end
1641
+ end
1642
+
1643
+ context "with S3 credentials supplied as Pathname and aliases being set" do
1644
+ before do
1645
+ ENV["S3_KEY"] = "pathname_key"
1646
+ ENV["S3_BUCKET"] = "pathname_bucket"
1647
+ ENV["S3_SECRET"] = "pathname_secret"
1648
+
1649
+ rails_env("test") do
1650
+ rebuild_model aws2_add_region.merge storage: :s3,
1651
+ s3_credentials: Pathname.new(fixture_file("aws_s3.yml"))
1652
+
1653
+ Dummy.delete_all
1654
+ @dummy = Dummy.new
1655
+ end
1656
+ end
1657
+
1658
+ it "parses the credentials" do
1659
+ assert_equal "pathname_bucket", @dummy.avatar.bucket_name
1660
+
1661
+ assert_equal "pathname_key",
1662
+ @dummy.avatar.s3_bucket.client.config.access_key_id
1663
+
1664
+ assert_equal "pathname_secret",
1665
+ @dummy.avatar.s3_bucket.client.config.secret_access_key
1666
+ end
1667
+ end
1668
+
1669
+ context "with S3 credentials in a YAML file" do
1670
+ before do
1671
+ ENV["S3_KEY"] = "env_key"
1672
+ ENV["S3_BUCKET"] = "env_bucket"
1673
+ ENV["S3_SECRET"] = "env_secret"
1674
+
1675
+ rails_env("test") do
1676
+ rebuild_model aws2_add_region.merge storage: :s3,
1677
+ s3_credentials: File.new(fixture_file("s3.yml"))
1678
+
1679
+ Dummy.delete_all
1680
+
1681
+ @dummy = Dummy.new
1682
+ end
1683
+ end
1684
+
1685
+ it "runs the file through ERB" do
1686
+ assert_equal "env_bucket", @dummy.avatar.bucket_name
1687
+
1688
+ assert_equal "env_key",
1689
+ @dummy.avatar.s3_bucket.client.config.access_key_id
1690
+
1691
+ assert_equal "env_secret",
1692
+ @dummy.avatar.s3_bucket.client.config.secret_access_key
1693
+ end
1694
+ end
1695
+
1696
+ context "with S3 credentials in a YAML file and aliases being set" do
1697
+ before do
1698
+ ENV["S3_KEY"] = "env_key"
1699
+ ENV["S3_BUCKET"] = "env_bucket"
1700
+ ENV["S3_SECRET"] = "env_secret"
1701
+
1702
+ rails_env("test") do
1703
+ rebuild_model aws2_add_region.merge storage: :s3,
1704
+ s3_credentials: File.new(fixture_file("aws_s3.yml"))
1705
+
1706
+ Dummy.delete_all
1707
+
1708
+ @dummy = Dummy.new
1709
+ end
1710
+ end
1711
+
1712
+ it "runs the file through ERB" do
1713
+ assert_equal "env_bucket", @dummy.avatar.bucket_name
1714
+
1715
+ assert_equal "env_key",
1716
+ @dummy.avatar.s3_bucket.client.config.access_key_id
1717
+
1718
+ assert_equal "env_secret",
1719
+ @dummy.avatar.s3_bucket.client.config.secret_access_key
1720
+ end
1721
+ end
1722
+
1723
+ context "S3 Permissions" do
1724
+ context "defaults to :public_read" do
1725
+ before do
1726
+ rebuild_model aws2_add_region.merge storage: :s3,
1727
+ bucket: "testing",
1728
+ path: ":attachment/:style/:basename:dotextension",
1729
+ s3_credentials: {
1730
+ "access_key_id" => "12345",
1731
+ "secret_access_key" => "54321"
1732
+ }
1733
+ end
1734
+
1735
+ context "when assigned" do
1736
+ before do
1737
+ @file = File.new(fixture_file("5k.png"), "rb")
1738
+ @dummy = Dummy.new
1739
+ @dummy.avatar = @file
1740
+ end
1741
+
1742
+ after { @file.close }
1743
+
1744
+ context "and saved" do
1745
+ before do
1746
+ object = double
1747
+ if s3_uses_transfer_manager?
1748
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(object)
1749
+ else
1750
+ allow(@dummy.avatar).to receive(:s3_object).and_return(object)
1751
+ end
1752
+
1753
+ expect(object).to receive(:upload_file).
1754
+ with(anything, hash_including(content_type: "image/png", acl: :"public-read"))
1755
+ @dummy.save
1756
+ end
1757
+
1758
+ it "succeeds" do
1759
+ assert true
1760
+ end
1761
+ end
1762
+ end
1763
+ end
1764
+
1765
+ context "string permissions set" do
1766
+ before do
1767
+ rebuild_model aws2_add_region.merge storage: :s3,
1768
+ bucket: "testing",
1769
+ path: ":attachment/:style/:basename:dotextension",
1770
+ s3_credentials: {
1771
+ "access_key_id" => "12345",
1772
+ "secret_access_key" => "54321"
1773
+ },
1774
+ s3_permissions: :private
1775
+ end
1776
+
1777
+ context "when assigned" do
1778
+ before do
1779
+ @file = File.new(fixture_file("5k.png"), "rb")
1780
+ @dummy = Dummy.new
1781
+ @dummy.avatar = @file
1782
+ end
1783
+
1784
+ after { @file.close }
1785
+
1786
+ context "and saved" do
1787
+ before do
1788
+ object = double
1789
+ if s3_uses_transfer_manager?
1790
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(object)
1791
+ else
1792
+ allow(@dummy.avatar).to receive(:s3_object).and_return(object)
1793
+ end
1794
+
1795
+ expect(object).to receive(:upload_file).
1796
+ with(anything, hash_including(content_type: "image/png", acl: :private))
1797
+ @dummy.save
1798
+ end
1799
+
1800
+ it "succeeds" do
1801
+ assert true
1802
+ end
1803
+ end
1804
+ end
1805
+ end
1806
+
1807
+ context "hash permissions set" do
1808
+ before do
1809
+ rebuild_model aws2_add_region.merge storage: :s3,
1810
+ bucket: "testing",
1811
+ path: ":attachment/:style/:basename:dotextension",
1812
+ styles: {
1813
+ thumb: "80x80>"
1814
+ },
1815
+ s3_credentials: {
1816
+ "access_key_id" => "12345",
1817
+ "secret_access_key" => "54321"
1818
+ },
1819
+ s3_permissions: {
1820
+ original: :private,
1821
+ thumb: :public_read
1822
+ }
1823
+ end
1824
+
1825
+ context "when assigned" do
1826
+ before do
1827
+ @file = File.new(fixture_file("5k.png"), "rb")
1828
+ @dummy = Dummy.new
1829
+ @dummy.avatar = @file
1830
+ end
1831
+
1832
+ after { @file.close }
1833
+
1834
+ context "and saved" do
1835
+ before do
1836
+ object = double
1837
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(object) if s3_uses_transfer_manager?
1838
+
1839
+ [:thumb, :original].each do |style|
1840
+ expected_args = { content_type: "image/png", acl: style == :thumb ? :public_read : :private }
1841
+ if s3_uses_transfer_manager?
1842
+ expected_args = expected_args.merge({bucket: "testing", key: "avatars/#{style}/5k.png"})
1843
+ else
1844
+ allow(@dummy.avatar).to receive(:s3_object).with(style).and_return(object)
1845
+ end
1846
+ expect(object).to receive(:upload_file).
1847
+ with(
1848
+ anything,
1849
+ hash_including(expected_args)
1850
+ )
1851
+ end
1852
+ @dummy.save
1853
+ end
1854
+
1855
+ it "succeeds" do
1856
+ assert true
1857
+ end
1858
+ end
1859
+ end
1860
+ end
1861
+
1862
+ context "proc permission set" do
1863
+ before do
1864
+ rebuild_model(
1865
+ aws2_add_region.merge(storage: :s3,
1866
+ bucket: "testing",
1867
+ path: ":attachment/:style/:basename:dotextension",
1868
+ styles: {
1869
+ thumb: "80x80>"
1870
+ },
1871
+ s3_credentials: {
1872
+ "access_key_id" => "12345",
1873
+ "secret_access_key" => "54321"
1874
+ },
1875
+ s3_permissions: lambda { |attachment, style|
1876
+ attachment.instance.private_attachment? && style.to_sym != :thumb ? :private : :"public-read"
1877
+ })
1878
+ )
1879
+ end
1880
+ end
1881
+ end
1882
+
1883
+ context "An attachment with S3 storage and metadata set using a proc as headers" do
1884
+ before do
1885
+ rebuild_model(
1886
+ aws2_add_region.merge(storage: :s3,
1887
+ bucket: "testing",
1888
+ path: ":attachment/:style/:basename:dotextension",
1889
+ styles: {
1890
+ thumb: "80x80>"
1891
+ },
1892
+ s3_credentials: {
1893
+ "access_key_id" => "12345",
1894
+ "secret_access_key" => "54321"
1895
+ },
1896
+ s3_headers: lambda { |attachment|
1897
+ { "Content-Disposition" => "attachment; filename=\"#{attachment.name}\"" }
1898
+ })
1899
+ )
1900
+ end
1901
+
1902
+ context "when assigned" do
1903
+ before do
1904
+ @file = File.new(fixture_file("5k.png"), "rb")
1905
+ @dummy = Dummy.new
1906
+ allow(@dummy).to receive(:name).and_return("Custom Avatar Name.png")
1907
+ @dummy.avatar = @file
1908
+ end
1909
+
1910
+ after { @file.close }
1911
+
1912
+ context "and saved" do
1913
+ before do
1914
+ object = double
1915
+ allow(@dummy.avatar).to receive(:s3_transfer_manager).and_return(object) if s3_uses_transfer_manager?
1916
+
1917
+ [:thumb, :original].each do |style|
1918
+ expected_args = {
1919
+ content_type: "image/png",
1920
+ acl: :"public-read",
1921
+ content_disposition: 'attachment; filename="Custom Avatar Name.png"'
1922
+ }
1923
+ if s3_uses_transfer_manager?
1924
+ expected_args = expected_args.merge({bucket: "testing", key: "avatars/#{style}/5k.png"})
1925
+ else
1926
+ allow(@dummy.avatar).to receive(:s3_object).with(style).and_return(object)
1927
+ end
1928
+ expect(object).to receive(:upload_file).
1929
+ with(
1930
+ anything,
1931
+ hash_including(expected_args)
1932
+ )
1933
+ end
1934
+ @dummy.save
1935
+ end
1936
+
1937
+ it "succeeds" do
1938
+ assert true
1939
+ end
1940
+ end
1941
+ end
1942
+ end
1943
+
1944
+ context "path is a proc" do
1945
+ before do
1946
+ rebuild_model aws2_add_region.merge storage: :s3,
1947
+ path: ->(attachment) { attachment.instance.attachment_path }
1948
+
1949
+ @dummy = Dummy.new
1950
+ @dummy.class_eval do
1951
+ def attachment_path
1952
+ "/some/dynamic/path"
1953
+ end
1954
+ end
1955
+ @dummy.avatar = stringy_file
1956
+ end
1957
+
1958
+ it "returns a correct path" do
1959
+ assert_match "/some/dynamic/path", @dummy.avatar.path
1960
+ end
1961
+ end
1962
+
1963
+ private
1964
+
1965
+ def rails_env(env)
1966
+ stored_env = Rails.env
1967
+ Rails.env = env
1968
+ begin
1969
+ yield
1970
+ ensure
1971
+ Rails.env = stored_env
1972
+ end
1973
+ end
1974
+ end