paperclip 4.1.1 → 4.2.0

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

Potentially problematic release.


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

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