kt-paperclip 6.2.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 (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