jr-paperclip 7.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/FUNDING.yml +3 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- data/.github/ISSUE_TEMPLATE/custom.md +10 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/reviewdog.yml +23 -0
- data/.github/workflows/test.yml +46 -0
- data/.gitignore +19 -0
- data/.qlty/.gitignore +7 -0
- data/.qlty/qlty.toml +89 -0
- data/.rubocop.yml +1060 -0
- data/Appraisals +29 -0
- data/CONTRIBUTING.md +85 -0
- data/Gemfile +17 -0
- data/LICENSE +25 -0
- data/NEWS +567 -0
- data/README.md +1083 -0
- data/RELEASING.md +17 -0
- data/Rakefile +52 -0
- data/bin/console +11 -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 +121 -0
- data/features/step_definitions/html_steps.rb +15 -0
- data/features/step_definitions/rails_steps.rb +271 -0
- data/features/step_definitions/s3_steps.rb +16 -0
- data/features/step_definitions/web_steps.rb +106 -0
- data/features/support/env.rb +12 -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/features/support/webmock_setup.rb +8 -0
- data/gemfiles/7.0.gemfile +20 -0
- data/gemfiles/7.1.gemfile +20 -0
- data/gemfiles/7.2.gemfile +20 -0
- data/gemfiles/8.0.gemfile +20 -0
- data/gemfiles/8.1.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/jr-paperclip.rb +1 -0
- data/lib/paperclip/attachment.rb +634 -0
- data/lib/paperclip/attachment_registry.rb +60 -0
- data/lib/paperclip/callbacks.rb +42 -0
- data/lib/paperclip/content_type_detector.rb +85 -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 +18 -0
- data/lib/paperclip/has_attached_file.rb +116 -0
- data/lib/paperclip/helpers.rb +60 -0
- data/lib/paperclip/interpolations/plural_cache.rb +18 -0
- data/lib/paperclip/interpolations.rb +205 -0
- data/lib/paperclip/io_adapters/abstract_adapter.rb +75 -0
- data/lib/paperclip/io_adapters/attachment_adapter.rb +56 -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 +78 -0
- data/lib/paperclip/locales/en.yml +18 -0
- data/lib/paperclip/locales/fr.yml +18 -0
- data/lib/paperclip/locales/gd.yml +20 -0
- data/lib/paperclip/logger.rb +21 -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/matchers.rb +64 -0
- data/lib/paperclip/media_type_spoof_detector.rb +93 -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 +104 -0
- data/lib/paperclip/storage/filesystem.rb +99 -0
- data/lib/paperclip/storage/fog.rb +262 -0
- data/lib/paperclip/storage/s3.rb +497 -0
- data/lib/paperclip/storage.rb +3 -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 +83 -0
- data/lib/paperclip/validators/attachment_content_type_validator.rb +95 -0
- data/lib/paperclip/validators/attachment_file_name_validator.rb +82 -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 +126 -0
- data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +29 -0
- data/lib/paperclip/validators.rb +73 -0
- data/lib/paperclip/version.rb +3 -0
- data/lib/paperclip.rb +215 -0
- data/lib/tasks/paperclip.rake +140 -0
- data/paperclip.gemspec +51 -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 +1617 -0
- data/spec/paperclip/content_type_detector_spec.rb +58 -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 +47 -0
- data/spec/paperclip/geometry_parser_spec.rb +73 -0
- data/spec/paperclip/geometry_spec.rb +267 -0
- data/spec/paperclip/glue_spec.rb +63 -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 +167 -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 +134 -0
- data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +142 -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 +231 -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 +126 -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 +298 -0
- data/spec/paperclip/storage/filesystem_spec.rb +102 -0
- data/spec/paperclip/storage/fog_spec.rb +606 -0
- data/spec/paperclip/storage/s3_live_spec.rb +188 -0
- data/spec/paperclip/storage/s3_spec.rb +1974 -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 +231 -0
- data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +410 -0
- data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +249 -0
- data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
- data/spec/paperclip/validators/attachment_size_validator_spec.rb +325 -0
- data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +48 -0
- data/spec/paperclip/validators_spec.rb +179 -0
- data/spec/spec_helper.rb +52 -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/aws_s3.yml +13 -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/sample.xlsm +0 -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 +702 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Paperclip::UriAdapter do
|
|
4
|
+
let(:content_type) { "image/png" }
|
|
5
|
+
let(:meta) { {} }
|
|
6
|
+
|
|
7
|
+
before do
|
|
8
|
+
@open_return = StringIO.new("xxx")
|
|
9
|
+
allow(@open_return).to receive(:content_type).and_return(content_type)
|
|
10
|
+
allow(@open_return).to receive(:meta).and_return(meta)
|
|
11
|
+
Paperclip::UriAdapter.register
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
after do
|
|
15
|
+
Paperclip.io_adapters.unregister(described_class)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
context "a new instance" do
|
|
19
|
+
let(:meta) { { "content-type" => "image/png" } }
|
|
20
|
+
|
|
21
|
+
before do
|
|
22
|
+
allow_any_instance_of(Paperclip::UriAdapter).
|
|
23
|
+
to receive(:download_content).and_return(@open_return)
|
|
24
|
+
|
|
25
|
+
@uri = URI.parse("http://thoughtbot.com/images/thoughtbot-logo.png")
|
|
26
|
+
@subject = Paperclip.io_adapters.for(@uri, hash_digest: Digest::MD5)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "returns a file name" do
|
|
30
|
+
assert_equal "thoughtbot-logo.png", @subject.original_filename
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "closes open handle after reading" do
|
|
34
|
+
assert_equal true, @open_return.closed?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "returns a content type" do
|
|
38
|
+
assert_equal "image/png", @subject.content_type
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "returns the size of the data" do
|
|
42
|
+
assert_equal @open_return.size, @subject.size
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "generates an MD5 hash of the contents" do
|
|
46
|
+
assert_equal Digest::MD5.hexdigest("xxx"), @subject.fingerprint
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "generates correct fingerprint after read" do
|
|
50
|
+
fingerprint = Digest::MD5.hexdigest(@subject.read)
|
|
51
|
+
assert_equal fingerprint, @subject.fingerprint
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "generates same fingerprint" do
|
|
55
|
+
assert_equal @subject.fingerprint, @subject.fingerprint
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "returns the data contained in the StringIO" do
|
|
59
|
+
assert_equal "xxx", @subject.read
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "accepts a content_type" do
|
|
63
|
+
@subject.content_type = "image/png"
|
|
64
|
+
assert_equal "image/png", @subject.content_type
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "accepts an original_filename" do
|
|
68
|
+
@subject.original_filename = "image.png"
|
|
69
|
+
assert_equal "image.png", @subject.original_filename
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
context "a directory index url" do
|
|
74
|
+
let(:content_type) { "text/html" }
|
|
75
|
+
let(:meta) { { "content-type" => "text/html" } }
|
|
76
|
+
|
|
77
|
+
before do
|
|
78
|
+
allow_any_instance_of(Paperclip::UriAdapter).
|
|
79
|
+
to receive(:download_content).and_return(@open_return)
|
|
80
|
+
|
|
81
|
+
@uri = URI.parse("http://thoughtbot.com")
|
|
82
|
+
@subject = Paperclip.io_adapters.for(@uri)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "returns a file name" do
|
|
86
|
+
assert_equal "index.html", @subject.original_filename
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "returns a content type" do
|
|
90
|
+
assert_equal "text/html", @subject.content_type
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context "a url with query params" do
|
|
95
|
+
before do
|
|
96
|
+
allow_any_instance_of(Paperclip::UriAdapter).
|
|
97
|
+
to receive(:download_content).and_return(@open_return)
|
|
98
|
+
|
|
99
|
+
@uri = URI.parse("https://github.com/thoughtbot/paperclip?file=test")
|
|
100
|
+
@subject = Paperclip.io_adapters.for(@uri)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "returns a file name" do
|
|
104
|
+
assert_equal "paperclip", @subject.original_filename
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
context "a url with content disposition headers" do
|
|
109
|
+
let(:file_name) { "test_document.pdf" }
|
|
110
|
+
let(:filename_from_path) { "paperclip" }
|
|
111
|
+
|
|
112
|
+
before do
|
|
113
|
+
allow_any_instance_of(Paperclip::UriAdapter).
|
|
114
|
+
to receive(:download_content).and_return(@open_return)
|
|
115
|
+
|
|
116
|
+
@uri = URI.parse(
|
|
117
|
+
"https://github.com/thoughtbot/#{filename_from_path}?file=test"
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "returns file name from path" do
|
|
122
|
+
meta["content-disposition"] = "inline;"
|
|
123
|
+
|
|
124
|
+
@subject = Paperclip.io_adapters.for(@uri)
|
|
125
|
+
|
|
126
|
+
assert_equal filename_from_path, @subject.original_filename
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it "returns a file name enclosed in double quotes" do
|
|
130
|
+
file_name = "john's test document.pdf"
|
|
131
|
+
meta["content-disposition"] = "attachment; filename=\"#{file_name}\";"
|
|
132
|
+
|
|
133
|
+
@subject = Paperclip.io_adapters.for(@uri)
|
|
134
|
+
|
|
135
|
+
assert_equal file_name, @subject.original_filename
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it "returns a file name not enclosed in double quotes" do
|
|
139
|
+
meta["content-disposition"] = "ATTACHMENT; FILENAME=#{file_name};"
|
|
140
|
+
|
|
141
|
+
@subject = Paperclip.io_adapters.for(@uri)
|
|
142
|
+
|
|
143
|
+
assert_equal file_name, @subject.original_filename
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it "does not crash when an empty filename is given" do
|
|
147
|
+
meta["content-disposition"] = "ATTACHMENT; FILENAME=\"\";"
|
|
148
|
+
|
|
149
|
+
@subject = Paperclip.io_adapters.for(@uri)
|
|
150
|
+
|
|
151
|
+
assert_equal "", @subject.original_filename
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "returns a file name ignoring RFC 5987 encoding" do
|
|
155
|
+
meta["content-disposition"] =
|
|
156
|
+
"attachment; filename=#{file_name}; filename* = utf-8''%e2%82%ac%20rates"
|
|
157
|
+
|
|
158
|
+
@subject = Paperclip.io_adapters.for(@uri)
|
|
159
|
+
|
|
160
|
+
assert_equal file_name, @subject.original_filename
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
context "when file name has consecutive periods" do
|
|
164
|
+
let(:file_name) { "test_document..pdf" }
|
|
165
|
+
|
|
166
|
+
it "returns a file name" do
|
|
167
|
+
@uri = URI.parse(
|
|
168
|
+
"https://github.com/thoughtbot/#{file_name}?file=test"
|
|
169
|
+
)
|
|
170
|
+
@subject = Paperclip.io_adapters.for(@uri)
|
|
171
|
+
assert_equal file_name, @subject.original_filename
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
context "a url with restricted characters in the filename" do
|
|
177
|
+
before do
|
|
178
|
+
allow_any_instance_of(Paperclip::UriAdapter).
|
|
179
|
+
to receive(:download_content).and_return(@open_return)
|
|
180
|
+
|
|
181
|
+
@uri = URI.parse("https://github.com/thoughtbot/paper:clip.jpg")
|
|
182
|
+
@subject = Paperclip.io_adapters.for(@uri)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it "does not generate filenames that include restricted characters" do
|
|
186
|
+
assert_equal "paper_clip.jpg", @subject.original_filename
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it "does not generate paths that include restricted characters" do
|
|
190
|
+
expect(@subject.path).to_not match(/:/)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
describe "#download_content" do
|
|
195
|
+
before do
|
|
196
|
+
allowed_mock =
|
|
197
|
+
if RUBY_VERSION < '2.5'
|
|
198
|
+
allow_any_instance_of(Paperclip::UriAdapter)
|
|
199
|
+
else
|
|
200
|
+
allow(URI)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
allowed_mock.to receive(:open).and_return(@open_return)
|
|
204
|
+
|
|
205
|
+
@uri = URI.parse("https://github.com/thoughtbot/paper:clip.jpg")
|
|
206
|
+
@subject = Paperclip.io_adapters.for(@uri)
|
|
207
|
+
@uri_opener = RUBY_VERSION < '2.5' ? @subject : URI
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
after do
|
|
211
|
+
@subject.send(:download_content)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
context "with default read_timeout" do
|
|
215
|
+
it "calls open without options" do
|
|
216
|
+
expect(@uri_opener).to receive(:open).with(@uri, {}).at_least(1).times
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
context "with custom read_timeout" do
|
|
221
|
+
before do
|
|
222
|
+
Paperclip.options[:read_timeout] = 120
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it "calls open with read_timeout option" do
|
|
226
|
+
expect(@uri_opener)
|
|
227
|
+
.to receive(:open).with(@uri, { read_timeout: 120 }).at_least(1).times
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "paperclip/matchers"
|
|
3
|
+
|
|
4
|
+
describe Paperclip::Shoulda::Matchers::HaveAttachedFileMatcher do
|
|
5
|
+
extend Paperclip::Shoulda::Matchers
|
|
6
|
+
|
|
7
|
+
it "rejects the dummy class if it has no attachment" do
|
|
8
|
+
reset_table "dummies"
|
|
9
|
+
reset_class "Dummy"
|
|
10
|
+
matcher = self.class.have_attached_file(:avatar)
|
|
11
|
+
expect(matcher).to_not accept(Dummy)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "accepts the dummy class if it has an attachment" do
|
|
15
|
+
rebuild_model
|
|
16
|
+
matcher = self.class.have_attached_file(:avatar)
|
|
17
|
+
expect(matcher).to accept(Dummy)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "paperclip/matchers"
|
|
3
|
+
|
|
4
|
+
describe Paperclip::Shoulda::Matchers::ValidateAttachmentContentTypeMatcher do
|
|
5
|
+
extend Paperclip::Shoulda::Matchers
|
|
6
|
+
|
|
7
|
+
before do
|
|
8
|
+
reset_table("dummies") do |d|
|
|
9
|
+
d.string :title
|
|
10
|
+
d.string :avatar_file_name
|
|
11
|
+
d.string :avatar_content_type
|
|
12
|
+
end
|
|
13
|
+
reset_class "Dummy"
|
|
14
|
+
Dummy.do_not_validate_attachment_file_type :avatar
|
|
15
|
+
Dummy.has_attached_file :avatar
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "rejects a class with no validation" do
|
|
19
|
+
expect(matcher).to_not accept(Dummy)
|
|
20
|
+
expect { matcher.failure_message }.to_not raise_error
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "rejects a class when the validation fails" do
|
|
24
|
+
Dummy.validates_attachment_content_type :avatar, content_type: %r{audio/.*}
|
|
25
|
+
expect(matcher).to_not accept(Dummy)
|
|
26
|
+
expect { matcher.failure_message }.to_not raise_error
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "accepts a class with a matching validation" do
|
|
30
|
+
Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}
|
|
31
|
+
expect(matcher).to accept(Dummy)
|
|
32
|
+
expect { matcher.failure_message }.to_not raise_error
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "accepts a class with other validations but matching types" do
|
|
36
|
+
Dummy.validates_presence_of :title
|
|
37
|
+
Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}
|
|
38
|
+
expect(matcher).to accept(Dummy)
|
|
39
|
+
expect { matcher.failure_message }.to_not raise_error
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "accepts a class that matches and a matcher that only specifies 'allowing'" do
|
|
43
|
+
Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}
|
|
44
|
+
matcher = plain_matcher.allowing(%w(image/png image/jpeg))
|
|
45
|
+
|
|
46
|
+
expect(matcher).to accept(Dummy)
|
|
47
|
+
expect { matcher.failure_message }.to_not raise_error
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "rejects a class that does not match and a matcher that only specifies 'allowing'" do
|
|
51
|
+
Dummy.validates_attachment_content_type :avatar, content_type: %r{audio/.*}
|
|
52
|
+
matcher = plain_matcher.allowing(%w(image/png image/jpeg))
|
|
53
|
+
|
|
54
|
+
expect(matcher).to_not accept(Dummy)
|
|
55
|
+
expect { matcher.failure_message }.to_not raise_error
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "accepts a class that matches and a matcher that only specifies 'rejecting'" do
|
|
59
|
+
Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}
|
|
60
|
+
matcher = plain_matcher.rejecting(%w(audio/mp3 application/octet-stream))
|
|
61
|
+
|
|
62
|
+
expect(matcher).to accept(Dummy)
|
|
63
|
+
expect { matcher.failure_message }.to_not raise_error
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "rejects a class that does not match and a matcher that only specifies 'rejecting'" do
|
|
67
|
+
Dummy.validates_attachment_content_type :avatar, content_type: %r{audio/.*}
|
|
68
|
+
matcher = plain_matcher.rejecting(%w(audio/mp3 application/octet-stream))
|
|
69
|
+
|
|
70
|
+
expect(matcher).to_not accept(Dummy)
|
|
71
|
+
expect { matcher.failure_message }.to_not raise_error
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
context "using an :if to control the validation" do
|
|
75
|
+
before do
|
|
76
|
+
Dummy.class_eval do
|
|
77
|
+
validates_attachment_content_type :avatar, content_type: %r{image/*}, if: :go
|
|
78
|
+
attr_accessor :go
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "runs the validation if the control is true" do
|
|
83
|
+
dummy = Dummy.new
|
|
84
|
+
dummy.go = true
|
|
85
|
+
expect(matcher).to accept(dummy)
|
|
86
|
+
expect { matcher.failure_message }.to_not raise_error
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "does not run the validation if the control is false" do
|
|
90
|
+
dummy = Dummy.new
|
|
91
|
+
dummy.go = false
|
|
92
|
+
expect(matcher).to_not accept(dummy)
|
|
93
|
+
expect { matcher.failure_message }.to_not raise_error
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def plain_matcher
|
|
100
|
+
self.class.validate_attachment_content_type(:avatar)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def matcher
|
|
104
|
+
plain_matcher.
|
|
105
|
+
allowing(%w(image/png image/jpeg)).
|
|
106
|
+
rejecting(%w(audio/mp3 application/octet-stream))
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "paperclip/matchers"
|
|
3
|
+
|
|
4
|
+
describe Paperclip::Shoulda::Matchers::ValidateAttachmentPresenceMatcher do
|
|
5
|
+
extend Paperclip::Shoulda::Matchers
|
|
6
|
+
|
|
7
|
+
before do
|
|
8
|
+
reset_table("dummies") do |d|
|
|
9
|
+
d.string :avatar_file_name
|
|
10
|
+
end
|
|
11
|
+
reset_class "Dummy"
|
|
12
|
+
Dummy.has_attached_file :avatar
|
|
13
|
+
Dummy.do_not_validate_attachment_file_type :avatar
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "rejects a class with no validation" do
|
|
17
|
+
expect(matcher).to_not accept(Dummy)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "accepts a class with a matching validation" do
|
|
21
|
+
Dummy.validates_attachment_presence :avatar
|
|
22
|
+
expect(matcher).to accept(Dummy)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "accepts an instance with other attachment validations" do
|
|
26
|
+
reset_table("dummies") do |d|
|
|
27
|
+
d.string :avatar_file_name
|
|
28
|
+
d.string :avatar_content_type
|
|
29
|
+
end
|
|
30
|
+
Dummy.class_eval do
|
|
31
|
+
validates_attachment_presence :avatar
|
|
32
|
+
validates_attachment_content_type :avatar, content_type: "image/gif"
|
|
33
|
+
end
|
|
34
|
+
dummy = Dummy.new
|
|
35
|
+
|
|
36
|
+
dummy.avatar = File.new fixture_file("5k.png")
|
|
37
|
+
|
|
38
|
+
expect(matcher).to accept(dummy)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context "using an :if to control the validation" do
|
|
42
|
+
before do
|
|
43
|
+
Dummy.class_eval do
|
|
44
|
+
validates_attachment_presence :avatar, if: :go
|
|
45
|
+
attr_accessor :go
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "runs the validation if the control is true" do
|
|
50
|
+
dummy = Dummy.new
|
|
51
|
+
dummy.avatar = nil
|
|
52
|
+
dummy.go = true
|
|
53
|
+
expect(matcher).to accept(dummy)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "does not run the validation if the control is false" do
|
|
57
|
+
dummy = Dummy.new
|
|
58
|
+
dummy.avatar = nil
|
|
59
|
+
dummy.go = false
|
|
60
|
+
expect(matcher).to_not accept(dummy)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def matcher
|
|
67
|
+
self.class.validate_attachment_presence(:avatar)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "paperclip/matchers"
|
|
3
|
+
|
|
4
|
+
describe Paperclip::Shoulda::Matchers::ValidateAttachmentSizeMatcher do
|
|
5
|
+
extend Paperclip::Shoulda::Matchers
|
|
6
|
+
|
|
7
|
+
before do
|
|
8
|
+
reset_table("dummies") do |d|
|
|
9
|
+
d.string :avatar_file_name
|
|
10
|
+
d.bigint :avatar_file_size
|
|
11
|
+
end
|
|
12
|
+
reset_class "Dummy"
|
|
13
|
+
Dummy.do_not_validate_attachment_file_type :avatar
|
|
14
|
+
Dummy.has_attached_file :avatar
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
context "Limiting size" do
|
|
18
|
+
it "rejects a class with no validation" do
|
|
19
|
+
expect(matcher.in(256..1024)).to_not accept(Dummy)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "rejects a class with a validation that's too high" do
|
|
23
|
+
Dummy.validates_attachment_size :avatar, in: 256..2048
|
|
24
|
+
expect(matcher.in(256..1024)).to_not accept(Dummy)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "accepts a class with a validation that's too low" do
|
|
28
|
+
Dummy.validates_attachment_size :avatar, in: 0..1024
|
|
29
|
+
expect(matcher.in(256..1024)).to_not accept(Dummy)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "accepts a class with a validation that matches" do
|
|
33
|
+
Dummy.validates_attachment_size :avatar, in: 256..1024
|
|
34
|
+
expect(matcher.in(256..1024)).to accept(Dummy)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context "allowing anything" do
|
|
39
|
+
it "given a class with an upper limit" do
|
|
40
|
+
Dummy.validates_attachment_size :avatar, less_than: 1
|
|
41
|
+
expect(matcher).to accept(Dummy)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "given a class with a lower limit" do
|
|
45
|
+
Dummy.validates_attachment_size :avatar, greater_than: 1
|
|
46
|
+
expect(matcher).to accept(Dummy)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
context "using an :if to control the validation" do
|
|
51
|
+
before do
|
|
52
|
+
Dummy.class_eval do
|
|
53
|
+
validates_attachment_size :avatar, greater_than: 1024, if: :go
|
|
54
|
+
attr_accessor :go
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "run the validation if the control is true" do
|
|
59
|
+
dummy = Dummy.new
|
|
60
|
+
dummy.go = true
|
|
61
|
+
expect(matcher.greater_than(1024)).to accept(dummy)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "not run the validation if the control is false" do
|
|
65
|
+
dummy = Dummy.new
|
|
66
|
+
dummy.go = false
|
|
67
|
+
expect(matcher.greater_than(1024)).to_not accept(dummy)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
context "post processing" do
|
|
72
|
+
before do
|
|
73
|
+
Dummy.validates_attachment_size :avatar, greater_than: 1024
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "be skipped" do
|
|
77
|
+
dummy = Dummy.new
|
|
78
|
+
expect(dummy.avatar).to_not receive(:post_process)
|
|
79
|
+
expect(matcher.greater_than(1024)).to accept(dummy)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def matcher
|
|
86
|
+
self.class.validate_attachment_size(:avatar)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Paperclip::MediaTypeSpoofDetector do
|
|
4
|
+
it "rejects a file that is named .html and identifies as PNG" do
|
|
5
|
+
file = File.open(fixture_file("5k.png"))
|
|
6
|
+
assert Paperclip::MediaTypeSpoofDetector.using(file, "5k.html", "image/png").spoofed?
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "does not reject a file that is named .jpg and identifies as PNG" do
|
|
10
|
+
file = File.open(fixture_file("5k.png"))
|
|
11
|
+
assert !Paperclip::MediaTypeSpoofDetector.using(file, "5k.jpg", "image/png").spoofed?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "does not reject a file that is named .html and identifies as HTML" do
|
|
15
|
+
file = File.open(fixture_file("empty.html"))
|
|
16
|
+
assert !Paperclip::MediaTypeSpoofDetector.using(file, "empty.html", "text/html").spoofed?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "does not reject a file that does not have a name" do
|
|
20
|
+
file = File.open(fixture_file("empty.html"))
|
|
21
|
+
assert !Paperclip::MediaTypeSpoofDetector.using(file, "", "text/html").spoofed?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "does not reject a file that does have an extension" do
|
|
25
|
+
file = File.open(fixture_file("empty.html"))
|
|
26
|
+
assert !Paperclip::MediaTypeSpoofDetector.using(file, "data", "text/html").spoofed?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "does not reject when the supplied file is an IOAdapter" do
|
|
30
|
+
adapter = Paperclip.io_adapters.for(File.new(fixture_file("5k.png")))
|
|
31
|
+
assert !Paperclip::MediaTypeSpoofDetector.using(adapter, adapter.original_filename, adapter.content_type).spoofed?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "does not reject when the extension => content_type is in :content_type_mappings" do
|
|
35
|
+
file = Tempfile.open(["test", ".PEM"])
|
|
36
|
+
file.puts "Certificate!"
|
|
37
|
+
file.close
|
|
38
|
+
|
|
39
|
+
adapter = Paperclip.io_adapters.for(File.new(file.path))
|
|
40
|
+
|
|
41
|
+
begin
|
|
42
|
+
Paperclip.options[:content_type_mappings] = { pem: "text/plain" }
|
|
43
|
+
assert !Paperclip::MediaTypeSpoofDetector.using(adapter, adapter.original_filename, adapter.content_type).spoofed?
|
|
44
|
+
|
|
45
|
+
# As a string.
|
|
46
|
+
Paperclip.options[:content_type_mappings] = { "pem" => "text/plain" }
|
|
47
|
+
assert !Paperclip::MediaTypeSpoofDetector.using(adapter, adapter.original_filename, adapter.content_type).spoofed?
|
|
48
|
+
ensure
|
|
49
|
+
Paperclip.options[:content_type_mappings] = {}
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
context "file named .html and is as HTML, but we're told JPG" do
|
|
54
|
+
let(:file) { File.open(fixture_file("empty.html")) }
|
|
55
|
+
let(:spoofed?) { Paperclip::MediaTypeSpoofDetector.using(file, "empty.html", "image/jpg").spoofed? }
|
|
56
|
+
|
|
57
|
+
it "rejects the file" do
|
|
58
|
+
assert spoofed?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "logs info about the detected spoof" do
|
|
62
|
+
expect(Paperclip).to receive(:log).with('Content Type Spoof: Filename empty.html (image/jpg from Headers, ["text/html"] from Extension), content type discovered from file command: text/html. See documentation to allow this combination.')
|
|
63
|
+
spoofed?
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context "GIF file named without extension, but we're told GIF" do
|
|
68
|
+
let(:file) { File.open(fixture_file("animated")) }
|
|
69
|
+
let(:spoofed?) do
|
|
70
|
+
Paperclip::MediaTypeSpoofDetector.
|
|
71
|
+
using(file, "animated", "image/gif").
|
|
72
|
+
spoofed?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "accepts the file" do
|
|
76
|
+
assert !spoofed?
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
context "GIF file named without extension, but we're told HTML" do
|
|
81
|
+
let(:file) { File.open(fixture_file("animated")) }
|
|
82
|
+
let(:spoofed?) do
|
|
83
|
+
Paperclip::MediaTypeSpoofDetector.
|
|
84
|
+
using(file, "animated", "text/html").
|
|
85
|
+
spoofed?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "rejects the file" do
|
|
89
|
+
assert spoofed?
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "does not reject if content_type is empty but otherwise checks out" do
|
|
94
|
+
file = File.open(fixture_file("empty.html"))
|
|
95
|
+
assert !Paperclip::MediaTypeSpoofDetector.using(file, "empty.html", "").spoofed?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "does allow array as :content_type_mappings" do
|
|
99
|
+
begin
|
|
100
|
+
Paperclip.options[:content_type_mappings] = {
|
|
101
|
+
html: ["binary", "text/html"]
|
|
102
|
+
}
|
|
103
|
+
file = File.open(fixture_file("empty.html"))
|
|
104
|
+
spoofed = Paperclip::MediaTypeSpoofDetector.
|
|
105
|
+
using(file, "empty.html", "text/html").spoofed?
|
|
106
|
+
assert !spoofed
|
|
107
|
+
ensure
|
|
108
|
+
Paperclip.options[:content_type_mappings] = {}
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
context "#type_from_file_command" do
|
|
113
|
+
let(:file) { File.new(fixture_file("empty.html")) }
|
|
114
|
+
let(:detector) { Paperclip::MediaTypeSpoofDetector.new(file, "html", "") }
|
|
115
|
+
|
|
116
|
+
it "does work with the output of old versions of file" do
|
|
117
|
+
allow(Paperclip).to receive(:run).and_return("text/html charset=us-ascii")
|
|
118
|
+
expect(detector.send(:type_from_file_command)).to eq("text/html")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "does work with the output of new versions of file" do
|
|
122
|
+
allow(Paperclip).to receive(:run).and_return("text/html; charset=us-ascii")
|
|
123
|
+
expect(detector.send(:type_from_file_command)).to eq("text/html")
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe "Metaclasses" do
|
|
4
|
+
context "A meta-class of dummy" do
|
|
5
|
+
if active_support_version >= "4.1" || ruby_version < "2.1"
|
|
6
|
+
before do
|
|
7
|
+
rebuild_model
|
|
8
|
+
reset_class("Dummy")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "is able to use Paperclip like a normal class" do
|
|
12
|
+
@dummy = Dummy.new
|
|
13
|
+
|
|
14
|
+
assert_nothing_raised do
|
|
15
|
+
rebuild_meta_class_of(@dummy)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "works like any other instance" do
|
|
20
|
+
@dummy = Dummy.new
|
|
21
|
+
rebuild_meta_class_of(@dummy)
|
|
22
|
+
|
|
23
|
+
assert_nothing_raised do
|
|
24
|
+
@dummy.avatar = File.new(fixture_file("5k.png"), "rb")
|
|
25
|
+
end
|
|
26
|
+
assert @dummy.save
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|