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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +17 -0
- data/.github/issue_template.md +3 -0
- data/.gitignore +19 -0
- data/.hound.yml +1050 -0
- data/.rubocop.yml +1 -0
- data/.travis.yml +47 -0
- data/Appraisals +24 -0
- data/CONTRIBUTING.md +86 -0
- data/Gemfile +18 -0
- data/LICENSE +24 -0
- data/NEWS +515 -0
- data/README.md +1053 -0
- data/RELEASING.md +17 -0
- data/Rakefile +52 -0
- data/UPGRADING +17 -0
- data/features/basic_integration.feature +85 -0
- data/features/migration.feature +29 -0
- data/features/rake_tasks.feature +62 -0
- data/features/step_definitions/attachment_steps.rb +110 -0
- data/features/step_definitions/html_steps.rb +15 -0
- data/features/step_definitions/rails_steps.rb +257 -0
- data/features/step_definitions/s3_steps.rb +14 -0
- data/features/step_definitions/web_steps.rb +106 -0
- data/features/support/env.rb +12 -0
- data/features/support/fakeweb.rb +11 -0
- data/features/support/file_helpers.rb +34 -0
- data/features/support/fixtures/boot_config.txt +15 -0
- data/features/support/fixtures/gemfile.txt +5 -0
- data/features/support/fixtures/preinitializer.txt +20 -0
- data/features/support/paths.rb +28 -0
- data/features/support/rails.rb +39 -0
- data/features/support/selectors.rb +19 -0
- data/gemfiles/4.2.gemfile +20 -0
- data/gemfiles/5.0.gemfile +20 -0
- data/gemfiles/5.1.gemfile +20 -0
- data/gemfiles/5.2.gemfile +20 -0
- data/gemfiles/6.0.gemfile +20 -0
- data/lib/generators/paperclip/USAGE +8 -0
- data/lib/generators/paperclip/paperclip_generator.rb +36 -0
- data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
- data/lib/paperclip.rb +215 -0
- data/lib/paperclip/attachment.rb +617 -0
- data/lib/paperclip/attachment_registry.rb +60 -0
- data/lib/paperclip/callbacks.rb +42 -0
- data/lib/paperclip/content_type_detector.rb +80 -0
- data/lib/paperclip/errors.rb +34 -0
- data/lib/paperclip/file_command_content_type_detector.rb +28 -0
- data/lib/paperclip/filename_cleaner.rb +15 -0
- data/lib/paperclip/geometry.rb +157 -0
- data/lib/paperclip/geometry_detector_factory.rb +45 -0
- data/lib/paperclip/geometry_parser_factory.rb +31 -0
- data/lib/paperclip/glue.rb +17 -0
- data/lib/paperclip/has_attached_file.rb +116 -0
- data/lib/paperclip/helpers.rb +60 -0
- data/lib/paperclip/interpolations.rb +201 -0
- data/lib/paperclip/interpolations/plural_cache.rb +18 -0
- data/lib/paperclip/io_adapters/abstract_adapter.rb +75 -0
- data/lib/paperclip/io_adapters/attachment_adapter.rb +47 -0
- data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
- data/lib/paperclip/io_adapters/empty_string_adapter.rb +19 -0
- data/lib/paperclip/io_adapters/file_adapter.rb +26 -0
- data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +16 -0
- data/lib/paperclip/io_adapters/identity_adapter.rb +17 -0
- data/lib/paperclip/io_adapters/nil_adapter.rb +37 -0
- data/lib/paperclip/io_adapters/registry.rb +36 -0
- data/lib/paperclip/io_adapters/stringio_adapter.rb +36 -0
- data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +44 -0
- data/lib/paperclip/io_adapters/uri_adapter.rb +68 -0
- data/lib/paperclip/locales/en.yml +18 -0
- data/lib/paperclip/logger.rb +21 -0
- data/lib/paperclip/matchers.rb +64 -0
- data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
- data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +101 -0
- data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
- data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +97 -0
- data/lib/paperclip/media_type_spoof_detector.rb +90 -0
- data/lib/paperclip/missing_attachment_styles.rb +84 -0
- data/lib/paperclip/processor.rb +56 -0
- data/lib/paperclip/processor_helpers.rb +52 -0
- data/lib/paperclip/rails_environment.rb +21 -0
- data/lib/paperclip/railtie.rb +31 -0
- data/lib/paperclip/schema.rb +81 -0
- data/lib/paperclip/storage.rb +3 -0
- data/lib/paperclip/storage/filesystem.rb +99 -0
- data/lib/paperclip/storage/fog.rb +252 -0
- data/lib/paperclip/storage/s3.rb +461 -0
- data/lib/paperclip/style.rb +106 -0
- data/lib/paperclip/tempfile.rb +42 -0
- data/lib/paperclip/tempfile_factory.rb +22 -0
- data/lib/paperclip/thumbnail.rb +131 -0
- data/lib/paperclip/url_generator.rb +76 -0
- data/lib/paperclip/validators.rb +73 -0
- data/lib/paperclip/validators/attachment_content_type_validator.rb +88 -0
- data/lib/paperclip/validators/attachment_file_name_validator.rb +75 -0
- data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +28 -0
- data/lib/paperclip/validators/attachment_presence_validator.rb +28 -0
- data/lib/paperclip/validators/attachment_size_validator.rb +109 -0
- data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +29 -0
- data/lib/paperclip/version.rb +3 -0
- data/lib/tasks/paperclip.rake +140 -0
- data/paperclip.gemspec +50 -0
- data/shoulda_macros/paperclip.rb +134 -0
- data/spec/database.yml +4 -0
- data/spec/paperclip/attachment_definitions_spec.rb +13 -0
- data/spec/paperclip/attachment_processing_spec.rb +79 -0
- data/spec/paperclip/attachment_registry_spec.rb +158 -0
- data/spec/paperclip/attachment_spec.rb +1590 -0
- data/spec/paperclip/content_type_detector_spec.rb +47 -0
- data/spec/paperclip/file_command_content_type_detector_spec.rb +40 -0
- data/spec/paperclip/filename_cleaner_spec.rb +13 -0
- data/spec/paperclip/geometry_detector_spec.rb +38 -0
- data/spec/paperclip/geometry_parser_spec.rb +73 -0
- data/spec/paperclip/geometry_spec.rb +255 -0
- data/spec/paperclip/glue_spec.rb +42 -0
- data/spec/paperclip/has_attached_file_spec.rb +78 -0
- data/spec/paperclip/integration_spec.rb +702 -0
- data/spec/paperclip/interpolations_spec.rb +270 -0
- data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +160 -0
- data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +140 -0
- data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +88 -0
- data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
- data/spec/paperclip/io_adapters/file_adapter_spec.rb +131 -0
- data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +137 -0
- data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
- data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
- data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
- data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
- data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
- data/spec/paperclip/io_adapters/uri_adapter_spec.rb +221 -0
- data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
- data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +108 -0
- data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
- data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
- data/spec/paperclip/media_type_spoof_detector_spec.rb +120 -0
- data/spec/paperclip/meta_class_spec.rb +30 -0
- data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +88 -0
- data/spec/paperclip/paperclip_spec.rb +196 -0
- data/spec/paperclip/plural_cache_spec.rb +37 -0
- data/spec/paperclip/processor_helpers_spec.rb +57 -0
- data/spec/paperclip/processor_spec.rb +26 -0
- data/spec/paperclip/rails_environment_spec.rb +30 -0
- data/spec/paperclip/rake_spec.rb +103 -0
- data/spec/paperclip/schema_spec.rb +252 -0
- data/spec/paperclip/storage/filesystem_spec.rb +79 -0
- data/spec/paperclip/storage/fog_spec.rb +560 -0
- data/spec/paperclip/storage/s3_live_spec.rb +188 -0
- data/spec/paperclip/storage/s3_spec.rb +1695 -0
- data/spec/paperclip/style_spec.rb +251 -0
- data/spec/paperclip/tempfile_factory_spec.rb +33 -0
- data/spec/paperclip/tempfile_spec.rb +35 -0
- data/spec/paperclip/thumbnail_spec.rb +504 -0
- data/spec/paperclip/url_generator_spec.rb +221 -0
- data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
- data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +159 -0
- data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
- data/spec/paperclip/validators/attachment_size_validator_spec.rb +235 -0
- data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +48 -0
- data/spec/paperclip/validators_spec.rb +164 -0
- data/spec/spec_helper.rb +45 -0
- data/spec/support/assertions.rb +84 -0
- data/spec/support/fake_model.rb +24 -0
- data/spec/support/fake_rails.rb +12 -0
- data/spec/support/fixtures/12k.png +0 -0
- data/spec/support/fixtures/50x50.png +0 -0
- data/spec/support/fixtures/5k.png +0 -0
- data/spec/support/fixtures/animated +0 -0
- data/spec/support/fixtures/animated.gif +0 -0
- data/spec/support/fixtures/animated.unknown +0 -0
- data/spec/support/fixtures/bad.png +1 -0
- data/spec/support/fixtures/empty.html +1 -0
- data/spec/support/fixtures/empty.xlsx +0 -0
- data/spec/support/fixtures/fog.yml +8 -0
- data/spec/support/fixtures/rotated.jpg +0 -0
- data/spec/support/fixtures/s3.yml +8 -0
- data/spec/support/fixtures/spaced file.jpg +0 -0
- data/spec/support/fixtures/spaced file.png +0 -0
- data/spec/support/fixtures/text.txt +1 -0
- data/spec/support/fixtures/twopage.pdf +0 -0
- data/spec/support/fixtures/uppercase.PNG +0 -0
- data/spec/support/matchers/accept.rb +5 -0
- data/spec/support/matchers/exist.rb +5 -0
- data/spec/support/matchers/have_column.rb +23 -0
- data/spec/support/mock_attachment.rb +24 -0
- data/spec/support/mock_interpolator.rb +24 -0
- data/spec/support/mock_url_generator_builder.rb +26 -0
- data/spec/support/model_reconstruction.rb +72 -0
- data/spec/support/reporting.rb +11 -0
- data/spec/support/test_data.rb +13 -0
- data/spec/support/version_helper.rb +9 -0
- 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
|