kt-paperclip 6.2.0

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