kt-paperclip 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +17 -0
  3. data/.github/issue_template.md +3 -0
  4. data/.gitignore +19 -0
  5. data/.hound.yml +1050 -0
  6. data/.rubocop.yml +1 -0
  7. data/.travis.yml +47 -0
  8. data/Appraisals +24 -0
  9. data/CONTRIBUTING.md +86 -0
  10. data/Gemfile +18 -0
  11. data/LICENSE +24 -0
  12. data/NEWS +515 -0
  13. data/README.md +1053 -0
  14. data/RELEASING.md +17 -0
  15. data/Rakefile +52 -0
  16. data/UPGRADING +17 -0
  17. data/features/basic_integration.feature +85 -0
  18. data/features/migration.feature +29 -0
  19. data/features/rake_tasks.feature +62 -0
  20. data/features/step_definitions/attachment_steps.rb +110 -0
  21. data/features/step_definitions/html_steps.rb +15 -0
  22. data/features/step_definitions/rails_steps.rb +257 -0
  23. data/features/step_definitions/s3_steps.rb +14 -0
  24. data/features/step_definitions/web_steps.rb +106 -0
  25. data/features/support/env.rb +12 -0
  26. data/features/support/fakeweb.rb +11 -0
  27. data/features/support/file_helpers.rb +34 -0
  28. data/features/support/fixtures/boot_config.txt +15 -0
  29. data/features/support/fixtures/gemfile.txt +5 -0
  30. data/features/support/fixtures/preinitializer.txt +20 -0
  31. data/features/support/paths.rb +28 -0
  32. data/features/support/rails.rb +39 -0
  33. data/features/support/selectors.rb +19 -0
  34. data/gemfiles/4.2.gemfile +20 -0
  35. data/gemfiles/5.0.gemfile +20 -0
  36. data/gemfiles/5.1.gemfile +20 -0
  37. data/gemfiles/5.2.gemfile +20 -0
  38. data/gemfiles/6.0.gemfile +20 -0
  39. data/lib/generators/paperclip/USAGE +8 -0
  40. data/lib/generators/paperclip/paperclip_generator.rb +36 -0
  41. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +15 -0
  42. data/lib/paperclip.rb +215 -0
  43. data/lib/paperclip/attachment.rb +617 -0
  44. data/lib/paperclip/attachment_registry.rb +60 -0
  45. data/lib/paperclip/callbacks.rb +42 -0
  46. data/lib/paperclip/content_type_detector.rb +80 -0
  47. data/lib/paperclip/errors.rb +34 -0
  48. data/lib/paperclip/file_command_content_type_detector.rb +28 -0
  49. data/lib/paperclip/filename_cleaner.rb +15 -0
  50. data/lib/paperclip/geometry.rb +157 -0
  51. data/lib/paperclip/geometry_detector_factory.rb +45 -0
  52. data/lib/paperclip/geometry_parser_factory.rb +31 -0
  53. data/lib/paperclip/glue.rb +17 -0
  54. data/lib/paperclip/has_attached_file.rb +116 -0
  55. data/lib/paperclip/helpers.rb +60 -0
  56. data/lib/paperclip/interpolations.rb +201 -0
  57. data/lib/paperclip/interpolations/plural_cache.rb +18 -0
  58. data/lib/paperclip/io_adapters/abstract_adapter.rb +75 -0
  59. data/lib/paperclip/io_adapters/attachment_adapter.rb +47 -0
  60. data/lib/paperclip/io_adapters/data_uri_adapter.rb +22 -0
  61. data/lib/paperclip/io_adapters/empty_string_adapter.rb +19 -0
  62. data/lib/paperclip/io_adapters/file_adapter.rb +26 -0
  63. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +16 -0
  64. data/lib/paperclip/io_adapters/identity_adapter.rb +17 -0
  65. data/lib/paperclip/io_adapters/nil_adapter.rb +37 -0
  66. data/lib/paperclip/io_adapters/registry.rb +36 -0
  67. data/lib/paperclip/io_adapters/stringio_adapter.rb +36 -0
  68. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +44 -0
  69. data/lib/paperclip/io_adapters/uri_adapter.rb +68 -0
  70. data/lib/paperclip/locales/en.yml +18 -0
  71. data/lib/paperclip/logger.rb +21 -0
  72. data/lib/paperclip/matchers.rb +64 -0
  73. data/lib/paperclip/matchers/have_attached_file_matcher.rb +54 -0
  74. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +101 -0
  75. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +59 -0
  76. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +97 -0
  77. data/lib/paperclip/media_type_spoof_detector.rb +90 -0
  78. data/lib/paperclip/missing_attachment_styles.rb +84 -0
  79. data/lib/paperclip/processor.rb +56 -0
  80. data/lib/paperclip/processor_helpers.rb +52 -0
  81. data/lib/paperclip/rails_environment.rb +21 -0
  82. data/lib/paperclip/railtie.rb +31 -0
  83. data/lib/paperclip/schema.rb +81 -0
  84. data/lib/paperclip/storage.rb +3 -0
  85. data/lib/paperclip/storage/filesystem.rb +99 -0
  86. data/lib/paperclip/storage/fog.rb +252 -0
  87. data/lib/paperclip/storage/s3.rb +461 -0
  88. data/lib/paperclip/style.rb +106 -0
  89. data/lib/paperclip/tempfile.rb +42 -0
  90. data/lib/paperclip/tempfile_factory.rb +22 -0
  91. data/lib/paperclip/thumbnail.rb +131 -0
  92. data/lib/paperclip/url_generator.rb +76 -0
  93. data/lib/paperclip/validators.rb +73 -0
  94. data/lib/paperclip/validators/attachment_content_type_validator.rb +88 -0
  95. data/lib/paperclip/validators/attachment_file_name_validator.rb +75 -0
  96. data/lib/paperclip/validators/attachment_file_type_ignorance_validator.rb +28 -0
  97. data/lib/paperclip/validators/attachment_presence_validator.rb +28 -0
  98. data/lib/paperclip/validators/attachment_size_validator.rb +109 -0
  99. data/lib/paperclip/validators/media_type_spoof_detection_validator.rb +29 -0
  100. data/lib/paperclip/version.rb +3 -0
  101. data/lib/tasks/paperclip.rake +140 -0
  102. data/paperclip.gemspec +50 -0
  103. data/shoulda_macros/paperclip.rb +134 -0
  104. data/spec/database.yml +4 -0
  105. data/spec/paperclip/attachment_definitions_spec.rb +13 -0
  106. data/spec/paperclip/attachment_processing_spec.rb +79 -0
  107. data/spec/paperclip/attachment_registry_spec.rb +158 -0
  108. data/spec/paperclip/attachment_spec.rb +1590 -0
  109. data/spec/paperclip/content_type_detector_spec.rb +47 -0
  110. data/spec/paperclip/file_command_content_type_detector_spec.rb +40 -0
  111. data/spec/paperclip/filename_cleaner_spec.rb +13 -0
  112. data/spec/paperclip/geometry_detector_spec.rb +38 -0
  113. data/spec/paperclip/geometry_parser_spec.rb +73 -0
  114. data/spec/paperclip/geometry_spec.rb +255 -0
  115. data/spec/paperclip/glue_spec.rb +42 -0
  116. data/spec/paperclip/has_attached_file_spec.rb +78 -0
  117. data/spec/paperclip/integration_spec.rb +702 -0
  118. data/spec/paperclip/interpolations_spec.rb +270 -0
  119. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +160 -0
  120. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +140 -0
  121. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +88 -0
  122. data/spec/paperclip/io_adapters/empty_string_adapter_spec.rb +17 -0
  123. data/spec/paperclip/io_adapters/file_adapter_spec.rb +131 -0
  124. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +137 -0
  125. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +8 -0
  126. data/spec/paperclip/io_adapters/nil_adapter_spec.rb +25 -0
  127. data/spec/paperclip/io_adapters/registry_spec.rb +35 -0
  128. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +64 -0
  129. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +146 -0
  130. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +221 -0
  131. data/spec/paperclip/matchers/have_attached_file_matcher_spec.rb +19 -0
  132. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +108 -0
  133. data/spec/paperclip/matchers/validate_attachment_presence_matcher_spec.rb +69 -0
  134. data/spec/paperclip/matchers/validate_attachment_size_matcher_spec.rb +88 -0
  135. data/spec/paperclip/media_type_spoof_detector_spec.rb +120 -0
  136. data/spec/paperclip/meta_class_spec.rb +30 -0
  137. data/spec/paperclip/paperclip_missing_attachment_styles_spec.rb +88 -0
  138. data/spec/paperclip/paperclip_spec.rb +196 -0
  139. data/spec/paperclip/plural_cache_spec.rb +37 -0
  140. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  141. data/spec/paperclip/processor_spec.rb +26 -0
  142. data/spec/paperclip/rails_environment_spec.rb +30 -0
  143. data/spec/paperclip/rake_spec.rb +103 -0
  144. data/spec/paperclip/schema_spec.rb +252 -0
  145. data/spec/paperclip/storage/filesystem_spec.rb +79 -0
  146. data/spec/paperclip/storage/fog_spec.rb +560 -0
  147. data/spec/paperclip/storage/s3_live_spec.rb +188 -0
  148. data/spec/paperclip/storage/s3_spec.rb +1695 -0
  149. data/spec/paperclip/style_spec.rb +251 -0
  150. data/spec/paperclip/tempfile_factory_spec.rb +33 -0
  151. data/spec/paperclip/tempfile_spec.rb +35 -0
  152. data/spec/paperclip/thumbnail_spec.rb +504 -0
  153. data/spec/paperclip/url_generator_spec.rb +221 -0
  154. data/spec/paperclip/validators/attachment_content_type_validator_spec.rb +322 -0
  155. data/spec/paperclip/validators/attachment_file_name_validator_spec.rb +159 -0
  156. data/spec/paperclip/validators/attachment_presence_validator_spec.rb +85 -0
  157. data/spec/paperclip/validators/attachment_size_validator_spec.rb +235 -0
  158. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +48 -0
  159. data/spec/paperclip/validators_spec.rb +164 -0
  160. data/spec/spec_helper.rb +45 -0
  161. data/spec/support/assertions.rb +84 -0
  162. data/spec/support/fake_model.rb +24 -0
  163. data/spec/support/fake_rails.rb +12 -0
  164. data/spec/support/fixtures/12k.png +0 -0
  165. data/spec/support/fixtures/50x50.png +0 -0
  166. data/spec/support/fixtures/5k.png +0 -0
  167. data/spec/support/fixtures/animated +0 -0
  168. data/spec/support/fixtures/animated.gif +0 -0
  169. data/spec/support/fixtures/animated.unknown +0 -0
  170. data/spec/support/fixtures/bad.png +1 -0
  171. data/spec/support/fixtures/empty.html +1 -0
  172. data/spec/support/fixtures/empty.xlsx +0 -0
  173. data/spec/support/fixtures/fog.yml +8 -0
  174. data/spec/support/fixtures/rotated.jpg +0 -0
  175. data/spec/support/fixtures/s3.yml +8 -0
  176. data/spec/support/fixtures/spaced file.jpg +0 -0
  177. data/spec/support/fixtures/spaced file.png +0 -0
  178. data/spec/support/fixtures/text.txt +1 -0
  179. data/spec/support/fixtures/twopage.pdf +0 -0
  180. data/spec/support/fixtures/uppercase.PNG +0 -0
  181. data/spec/support/matchers/accept.rb +5 -0
  182. data/spec/support/matchers/exist.rb +5 -0
  183. data/spec/support/matchers/have_column.rb +23 -0
  184. data/spec/support/mock_attachment.rb +24 -0
  185. data/spec/support/mock_interpolator.rb +24 -0
  186. data/spec/support/mock_url_generator_builder.rb +26 -0
  187. data/spec/support/model_reconstruction.rb +72 -0
  188. data/spec/support/reporting.rb +11 -0
  189. data/spec/support/test_data.rb +13 -0
  190. data/spec/support/version_helper.rb +9 -0
  191. metadata +586 -0
@@ -0,0 +1,42 @@
1
+ # require "spec_helper"
2
+
3
+ describe Paperclip::Glue do
4
+ describe "when ActiveRecord does not exist" do
5
+ before do
6
+ ActiveRecordSaved = ActiveRecord
7
+ Object.send :remove_const, "ActiveRecord"
8
+ end
9
+
10
+ after do
11
+ ActiveRecord = ActiveRecordSaved
12
+ Object.send :remove_const, "ActiveRecordSaved"
13
+ end
14
+
15
+ it "does not fail" do
16
+ NonActiveRecordModel = Class.new
17
+ NonActiveRecordModel.include Paperclip::Glue
18
+ Object.send :remove_const, "NonActiveRecordModel"
19
+ end
20
+ end
21
+
22
+ describe "when ActiveRecord does exist" do
23
+ before do
24
+ if Object.const_defined?("ActiveRecord")
25
+ @defined_active_record = false
26
+ else
27
+ ActiveRecord = :defined
28
+ @defined_active_record = true
29
+ end
30
+ end
31
+
32
+ after do
33
+ Object.send :remove_const, "ActiveRecord" if @defined_active_record
34
+ end
35
+
36
+ it "does not fail" do
37
+ NonActiveRecordModel = Class.new
38
+ NonActiveRecordModel.include Paperclip::Glue
39
+ Object.send :remove_const, "NonActiveRecordModel"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,78 @@
1
+ require "spec_helper"
2
+
3
+ describe Paperclip::HasAttachedFile do
4
+ let(:a_class) { spy("Class") }
5
+
6
+ context "#define_on" do
7
+ it "defines a setter on the class object" do
8
+ Paperclip::HasAttachedFile.define_on(a_class, "avatar", {})
9
+ expect(a_class).to have_received(:define_method).with("avatar=")
10
+ end
11
+
12
+ it "defines a getter on the class object" do
13
+ Paperclip::HasAttachedFile.define_on(a_class, "avatar", {})
14
+ expect(a_class).to have_received(:define_method).with("avatar")
15
+ end
16
+
17
+ it "defines a query on the class object" do
18
+ Paperclip::HasAttachedFile.define_on(a_class, "avatar", {})
19
+ expect(a_class).to have_received(:define_method).with("avatar?")
20
+ end
21
+
22
+ it "defines a method on the class to get all of its attachments" do
23
+ allow(a_class).to receive(:extend)
24
+ Paperclip::HasAttachedFile.define_on(a_class, "avatar", {})
25
+ expect(a_class).to have_received(:extend).with(Paperclip::HasAttachedFile::ClassMethods)
26
+ end
27
+
28
+ it "flushes errors as part of validations" do
29
+ Paperclip::HasAttachedFile.define_on(a_class, "avatar", {})
30
+ expect(a_class).to have_received(:validates_each).with("avatar")
31
+ end
32
+
33
+ it "registers the attachment with Paperclip::AttachmentRegistry" do
34
+ allow(Paperclip::AttachmentRegistry).to receive(:register)
35
+ Paperclip::HasAttachedFile.define_on(a_class, "avatar", size: 1)
36
+ expect(Paperclip::AttachmentRegistry).to have_received(:register).with(a_class, "avatar", size: 1)
37
+ end
38
+
39
+ it "defines an after_save callback" do
40
+ Paperclip::HasAttachedFile.define_on(a_class, "avatar", {})
41
+ expect(a_class).to have_received("after_save")
42
+ end
43
+
44
+ it "defines a before_destroy callback" do
45
+ Paperclip::HasAttachedFile.define_on(a_class, "avatar", {})
46
+ expect(a_class).to have_received("before_destroy")
47
+ end
48
+
49
+ it "defines an after_commit callback" do
50
+ Paperclip::HasAttachedFile.define_on(a_class, "avatar", {})
51
+ expect(a_class).to have_received("after_commit")
52
+ end
53
+
54
+ context "when the class does not allow after_commit callbacks" do
55
+ it "defines an after_destroy callback" do
56
+ a_class = double("class", after_destroy: nil, validates_each: nil, define_method: nil, after_save: nil, before_destroy: nil, define_paperclip_callbacks: nil, validates_media_type_spoof_detection: nil)
57
+ Paperclip::HasAttachedFile.define_on(a_class, "avatar", {})
58
+ expect(a_class).to have_received("after_destroy")
59
+ end
60
+ end
61
+
62
+ it "defines the Paperclip-specific callbacks" do
63
+ Paperclip::HasAttachedFile.define_on(a_class, "avatar", validate_media_type: false)
64
+ expect(a_class).to_not have_received(:validates_media_type_spoof_detection)
65
+ expect(a_class).to have_received("define_paperclip_callbacks")
66
+ end
67
+
68
+ it "does not define a media_type check if told not to" do
69
+ Paperclip::HasAttachedFile.define_on(a_class, "avatar", validate_media_type: false)
70
+ expect(a_class).to_not have_received(:validates_media_type_spoof_detection)
71
+ end
72
+
73
+ it "does define a media_type check if told to" do
74
+ Paperclip::HasAttachedFile.define_on(a_class, "avatar", validate_media_type: true)
75
+ expect(a_class).to have_received(:validates_media_type_spoof_detection)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,702 @@
1
+ require "spec_helper"
2
+ require "open-uri"
3
+
4
+ describe "Paperclip" do
5
+ around do |example|
6
+ files_before = ObjectSpace.each_object(Tempfile).select do |file|
7
+ file.path && File.file?(file.path)
8
+ end
9
+
10
+ example.run
11
+
12
+ files_after = ObjectSpace.each_object(Tempfile).select do |file|
13
+ file.path && File.file?(file.path)
14
+ end
15
+
16
+ diff = files_after - files_before
17
+ expect(diff).to eq([]), "Leaked tempfiles: #{diff.inspect}"
18
+ end
19
+
20
+ context "Many models at once" do
21
+ before do
22
+ rebuild_model
23
+ @file = File.new(fixture_file("5k.png"), "rb")
24
+ # Deals with `Too many open files` error
25
+ dummies = Array.new(300) { Dummy.new avatar: @file }
26
+ Dummy.import dummies
27
+ # save attachment instances to run after hooks including tempfile cleanup
28
+ # since activerecord-import does not use our usually hooked-in hooks
29
+ # (such as after_save)
30
+ dummies.each { |dummy| dummy.avatar.save }
31
+ end
32
+
33
+ after { @file.close }
34
+
35
+ it "does not exceed the open file limit" do
36
+ assert_nothing_raised do
37
+ Dummy.all.each(&:avatar)
38
+ end
39
+ end
40
+ end
41
+
42
+ context "An attachment" do
43
+ before do
44
+ rebuild_model styles: { thumb: "50x50#" }
45
+ @dummy = Dummy.new
46
+ @file = File.new(fixture_file("5k.png"), "rb")
47
+ @dummy.avatar = @file
48
+ assert @dummy.save
49
+ end
50
+
51
+ after { @file.close }
52
+
53
+ it "creates its thumbnails properly" do
54
+ assert_match(/\b50x50\b/, `identify "#{@dummy.avatar.path(:thumb)}"`)
55
+ end
56
+
57
+ context "reprocessing with unreadable original" do
58
+ before { File.chmod(0o000, @dummy.avatar.path) }
59
+
60
+ it "does not raise an error" do
61
+ assert_nothing_raised do
62
+ silence_stream(STDERR) do
63
+ @dummy.avatar.reprocess!
64
+ end
65
+ end
66
+ end
67
+
68
+ it "returns false" do
69
+ silence_stream(STDERR) do
70
+ assert !@dummy.avatar.reprocess!
71
+ end
72
+ end
73
+
74
+ after { File.chmod(0o644, @dummy.avatar.path) }
75
+ end
76
+
77
+ context "redefining its attachment styles" do
78
+ before do
79
+ Dummy.class_eval do
80
+ has_attached_file :avatar, styles: { thumb: "150x25#", dynamic: lambda { |_a| "50x50#" } }
81
+ end
82
+ @d2 = Dummy.find(@dummy.id)
83
+ @original_timestamp = @d2.avatar_updated_at
84
+ @d2.avatar.reprocess!
85
+ @d2.save
86
+ end
87
+
88
+ it "creates its thumbnails properly" do
89
+ assert_match(/\b150x25\b/, `identify "#{@dummy.avatar.path(:thumb)}"`)
90
+ assert_match(/\b50x50\b/, `identify "#{@dummy.avatar.path(:dynamic)}"`)
91
+ end
92
+
93
+ it "changes the timestamp" do
94
+ assert_not_equal @original_timestamp, @d2.avatar_updated_at
95
+ end
96
+ end
97
+ end
98
+
99
+ context "Attachment" do
100
+ before do
101
+ @thumb_path = "tmp/public/system/dummies/avatars/000/000/001/thumb/5k.png"
102
+ File.delete(@thumb_path) if File.exist?(@thumb_path)
103
+ rebuild_model styles: { thumb: "50x50#" }
104
+ @dummy = Dummy.new
105
+ @file = File.new(fixture_file("5k.png"), "rb")
106
+ end
107
+
108
+ after { @file.close }
109
+
110
+ it "does not create the thumbnails upon saving when post-processing is disabled" do
111
+ @dummy.avatar.post_processing = false
112
+ @dummy.avatar = @file
113
+ assert @dummy.save
114
+ assert_file_not_exists @thumb_path
115
+ end
116
+
117
+ it "creates the thumbnails upon saving when post_processing is enabled" do
118
+ @dummy.avatar.post_processing = true
119
+ @dummy.avatar = @file
120
+ assert @dummy.save
121
+ assert_file_exists @thumb_path
122
+ end
123
+ end
124
+
125
+ context "Attachment with no generated thumbnails" do
126
+ before do
127
+ @thumb_small_path = "tmp/public/system/dummies/avatars/000/000/001/thumb_small/5k.png"
128
+ @thumb_large_path = "tmp/public/system/dummies/avatars/000/000/001/thumb_large/5k.png"
129
+ File.delete(@thumb_small_path) if File.exist?(@thumb_small_path)
130
+ File.delete(@thumb_large_path) if File.exist?(@thumb_large_path)
131
+ rebuild_model styles: { thumb_small: "50x50#", thumb_large: "60x60#" }
132
+ @dummy = Dummy.new
133
+ @file = File.new(fixture_file("5k.png"), "rb")
134
+
135
+ @dummy.avatar.post_processing = false
136
+ @dummy.avatar = @file
137
+ assert @dummy.save
138
+ @dummy.avatar.post_processing = true
139
+ end
140
+
141
+ after { @file.close }
142
+
143
+ it "allows us to create all thumbnails in one go" do
144
+ assert_file_not_exists(@thumb_small_path)
145
+ assert_file_not_exists(@thumb_large_path)
146
+
147
+ @dummy.avatar.reprocess!
148
+
149
+ assert_file_exists(@thumb_small_path)
150
+ assert_file_exists(@thumb_large_path)
151
+ end
152
+
153
+ it "allows us to selectively create each thumbnail" do
154
+ skip <<-EXPLANATION
155
+ #reprocess! calls #assign which calls Paperclip.io_adapters.for
156
+ which creates the tempfile. #assign then calls #post_process_file which
157
+ calls MediaTypeSpoofDetectionValidator#validate_each which calls
158
+ Paperclip.io_adapters.for, which creates another tempfile. That first
159
+ tempfile is the one that leaks.
160
+ EXPLANATION
161
+
162
+ assert_file_not_exists(@thumb_small_path)
163
+ assert_file_not_exists(@thumb_large_path)
164
+
165
+ @dummy.avatar.reprocess! :thumb_small
166
+ assert_file_exists(@thumb_small_path)
167
+ assert_file_not_exists(@thumb_large_path)
168
+
169
+ @dummy.avatar.reprocess! :thumb_large
170
+ assert_file_exists(@thumb_large_path)
171
+ end
172
+ end
173
+
174
+ context "A model that modifies its original" do
175
+ before do
176
+ rebuild_model styles: { original: "2x2#" }
177
+ @dummy = Dummy.new
178
+ @file = File.new(fixture_file("5k.png"), "rb")
179
+ @dummy.avatar = @file
180
+ end
181
+
182
+ it "reports the file size of the processed file and not the original" do
183
+ assert_not_equal File.size(@file.path), @dummy.avatar.size
184
+ end
185
+
186
+ after do
187
+ @file.close
188
+ # save attachment instance to run after hooks (including tempfile cleanup)
189
+ @dummy.avatar.save
190
+ end
191
+ end
192
+
193
+ context "A model with attachments scoped under an id" do
194
+ before do
195
+ rebuild_model styles: { large: "100x100",
196
+ medium: "50x50" },
197
+ path: ":rails_root/tmp/:id/:attachments/:style.:extension"
198
+ @dummy = Dummy.new
199
+ @file = File.new(fixture_file("5k.png"), "rb")
200
+ @dummy.avatar = @file
201
+ end
202
+
203
+ after { @file.close }
204
+
205
+ context "when saved" do
206
+ before do
207
+ @dummy.save
208
+ @saved_path = @dummy.avatar.path(:large)
209
+ end
210
+
211
+ it "has a large file in the right place" do
212
+ assert_file_exists(@dummy.avatar.path(:large))
213
+ end
214
+
215
+ context "and deleted" do
216
+ before do
217
+ @dummy.avatar.clear
218
+ @dummy.save
219
+ end
220
+
221
+ it "does not have a large file in the right place anymore" do
222
+ assert_file_not_exists(@saved_path)
223
+ end
224
+
225
+ it "does not have its next two parent directories" do
226
+ assert_file_not_exists(File.dirname(@saved_path))
227
+ assert_file_not_exists(File.dirname(File.dirname(@saved_path)))
228
+ end
229
+ end
230
+
231
+ context "and deleted where the delete fails" do
232
+ it "does not die if an unexpected SystemCallError happens" do
233
+ allow(FileUtils).to receive(:rmdir).and_raise(Errno::EPIPE)
234
+ assert_nothing_raised do
235
+ @dummy.avatar.clear
236
+ @dummy.save
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end
242
+
243
+ [0o00, 0o02, 0o22].each do |umask|
244
+ context "when the umask is #{umask}" do
245
+ before do
246
+ rebuild_model
247
+ @dummy = Dummy.new
248
+ @file = File.new(fixture_file("5k.png"), "rb")
249
+ @umask = File.umask(umask)
250
+ end
251
+
252
+ after do
253
+ File.umask @umask
254
+ @file.close
255
+ end
256
+
257
+ it "respects the current umask" do
258
+ @dummy.avatar = @file
259
+ @dummy.save
260
+ assert_equal 0o666 & ~umask, 0o666 & File.stat(@dummy.avatar.path).mode
261
+ end
262
+ end
263
+ end
264
+
265
+ [0o666, 0o664, 0o640].each do |perms|
266
+ context "when the perms are #{perms}" do
267
+ before do
268
+ rebuild_model override_file_permissions: perms
269
+ @dummy = Dummy.new
270
+ @file = File.new(fixture_file("5k.png"), "rb")
271
+ end
272
+
273
+ after do
274
+ @file.close
275
+ end
276
+
277
+ it "respects the current perms" do
278
+ @dummy.avatar = @file
279
+ @dummy.save
280
+ assert_equal perms, File.stat(@dummy.avatar.path).mode & 0o777
281
+ end
282
+ end
283
+ end
284
+
285
+ it "skips chmod operation, when override_file_permissions is set to false (e.g. useful when using CIFS mounts)" do
286
+ expect(FileUtils).to_not receive(:chmod)
287
+
288
+ rebuild_model override_file_permissions: false
289
+ dummy = Dummy.create!
290
+ dummy.avatar = @file
291
+ dummy.save
292
+ end
293
+
294
+ context "A model with a filesystem attachment" do
295
+ before do
296
+ rebuild_model styles: { large: "300x300>",
297
+ medium: "100x100",
298
+ thumb: ["32x32#", :gif] },
299
+ default_style: :medium,
300
+ url: "/:attachment/:class/:style/:id/:basename.:extension",
301
+ path: ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
302
+ @dummy = Dummy.new
303
+ @file = File.new(fixture_file("5k.png"), "rb")
304
+ @bad_file = File.new(fixture_file("bad.png"), "rb")
305
+
306
+ assert @dummy.avatar = @file
307
+ assert @dummy.valid?, @dummy.errors.full_messages.join(", ")
308
+ assert @dummy.save
309
+ end
310
+
311
+ after { [@file, @bad_file].each(&:close) }
312
+
313
+ it "writes and delete its files" do
314
+ [["434x66", :original],
315
+ ["300x46", :large],
316
+ ["100x15", :medium],
317
+ ["32x32", :thumb]].each do |geo, style|
318
+ cmd = %[identify -format "%wx%h" "#{@dummy.avatar.path(style)}"]
319
+ assert_equal geo, `#{cmd}`.chomp, cmd
320
+ end
321
+
322
+ saved_paths = [:thumb, :medium, :large, :original].map { |s| @dummy.avatar.path(s) }
323
+
324
+ @d2 = Dummy.find(@dummy.id)
325
+ assert_equal "100x15", `identify -format "%wx%h" "#{@d2.avatar.path}"`.chomp
326
+ assert_equal "434x66", `identify -format "%wx%h" "#{@d2.avatar.path(:original)}"`.chomp
327
+ assert_equal "300x46", `identify -format "%wx%h" "#{@d2.avatar.path(:large)}"`.chomp
328
+ assert_equal "100x15", `identify -format "%wx%h" "#{@d2.avatar.path(:medium)}"`.chomp
329
+ assert_equal "32x32", `identify -format "%wx%h" "#{@d2.avatar.path(:thumb)}"`.chomp
330
+
331
+ assert @dummy.valid?
332
+ assert @dummy.save
333
+
334
+ saved_paths.each do |p|
335
+ assert_file_exists(p)
336
+ end
337
+
338
+ @dummy.avatar.clear
339
+ assert_nil @dummy.avatar_file_name
340
+ assert @dummy.valid?
341
+ assert @dummy.save
342
+
343
+ saved_paths.each do |p|
344
+ assert_file_not_exists(p)
345
+ end
346
+
347
+ @d2 = Dummy.find(@dummy.id)
348
+ assert_nil @d2.avatar_file_name
349
+ end
350
+
351
+ it "works exactly the same when new as when reloaded" do
352
+ @d2 = Dummy.find(@dummy.id)
353
+
354
+ assert_equal @dummy.avatar_file_name, @d2.avatar_file_name
355
+ [:thumb, :medium, :large, :original].each do |style|
356
+ assert_equal @dummy.avatar.path(style), @d2.avatar.path(style)
357
+ end
358
+
359
+ saved_paths = [:thumb, :medium, :large, :original].map { |s| @dummy.avatar.path(s) }
360
+
361
+ @d2.avatar.clear
362
+ assert @d2.save
363
+
364
+ saved_paths.each do |p|
365
+ assert_file_not_exists(p)
366
+ end
367
+ end
368
+
369
+ it "does not abide things that don't have adapters" do
370
+ assert_raises(Paperclip::AdapterRegistry::NoHandlerError) do
371
+ @dummy.avatar = "not a file"
372
+ end
373
+ end
374
+
375
+ it "is not ok with bad files" do
376
+ @dummy.avatar = @bad_file
377
+ assert !@dummy.valid?
378
+ # save attachment instance to run after hooks (including tempfile cleanup)
379
+ @dummy.avatar.save
380
+ end
381
+
382
+ it "knows the difference between good files, bad files, and not files when validating" do
383
+ Dummy.validates_attachment_presence :avatar
384
+ @d2 = Dummy.find(@dummy.id)
385
+ @d2.avatar = @file
386
+ assert @d2.valid?, @d2.errors.full_messages.inspect
387
+ # save attachment instance to run after hooks (including tempfile cleanup)
388
+ @d2.avatar.save
389
+
390
+ @d2.avatar = @bad_file
391
+ assert !@d2.valid?
392
+ # save attachment instance to run after hooks (including tempfile cleanup)
393
+ @d2.avatar.save
394
+ end
395
+
396
+ it "is able to reload without saving and not have the file disappear" do
397
+ @dummy.avatar = @file
398
+ assert @dummy.save, @dummy.errors.full_messages.inspect
399
+ @dummy.avatar.clear
400
+ assert_nil @dummy.avatar_file_name
401
+ @dummy.reload
402
+ assert_equal "5k.png", @dummy.avatar_file_name
403
+ end
404
+
405
+ context "that is assigned its file from another Paperclip attachment" do
406
+ before do
407
+ @dummy2 = Dummy.new
408
+ @file2 = File.new(fixture_file("12k.png"), "rb")
409
+ assert @dummy2.avatar = @file2
410
+ @dummy2.save
411
+ end
412
+
413
+ after { @file2.close }
414
+
415
+ it "works when assigned a file" do
416
+ assert_not_equal `identify -format "%wx%h" "#{@dummy.avatar.path(:original)}"`,
417
+ `identify -format "%wx%h" "#{@dummy2.avatar.path(:original)}"`
418
+
419
+ assert @dummy.avatar = @dummy2.avatar
420
+ @dummy.save
421
+ assert_equal @dummy.avatar_file_name, @dummy2.avatar_file_name
422
+ assert_equal `identify -format "%wx%h" "#{@dummy.avatar.path(:original)}"`,
423
+ `identify -format "%wx%h" "#{@dummy2.avatar.path(:original)}"`
424
+ end
425
+ end
426
+ end
427
+
428
+ context "A model with an attachments association and a Paperclip attachment" do
429
+ before do
430
+ Dummy.class_eval do
431
+ has_many :attachments, class_name: "Dummy"
432
+ end
433
+
434
+ @file = File.new(fixture_file("5k.png"), "rb")
435
+ @dummy = Dummy.new
436
+ @dummy.avatar = @file
437
+ end
438
+
439
+ after { @file.close }
440
+
441
+ it "does not error when saving" do
442
+ @dummy.save!
443
+ end
444
+ end
445
+
446
+ context "A model with an attachment with hash in file name" do
447
+ before do
448
+ @settings = { styles: { thumb: "50x50#" },
449
+ path: ":rails_root/public/system/:attachment/:id_partition/:style/:hash.:extension",
450
+ url: "/system/:attachment/:id_partition/:style/:hash.:extension",
451
+ hash_secret: "somesecret" }
452
+
453
+ rebuild_model @settings
454
+
455
+ @file = File.new(fixture_file("5k.png"), "rb")
456
+ @dummy = Dummy.create! avatar: @file
457
+ end
458
+
459
+ after do
460
+ @file.close
461
+ end
462
+
463
+ it "is accessible" do
464
+ assert_file_exists(@dummy.avatar.path(:original))
465
+ assert_file_exists(@dummy.avatar.path(:thumb))
466
+ end
467
+
468
+ context "when new style is added" do
469
+ before do
470
+ @dummy.avatar.options[:styles][:mini] = "25x25#"
471
+ @dummy.avatar.instance_variable_set :@normalized_styles, nil
472
+ allow(Time).to receive(:now).and_return(Time.now + 10)
473
+ @dummy.avatar.reprocess!
474
+ @dummy.reload
475
+ end
476
+
477
+ it "makes all the styles accessible" do
478
+ assert_file_exists(@dummy.avatar.path(:original))
479
+ assert_file_exists(@dummy.avatar.path(:thumb))
480
+ assert_file_exists(@dummy.avatar.path(:mini))
481
+ end
482
+ end
483
+ end
484
+
485
+ if ENV["S3_BUCKET"]
486
+ def s3_files_for(attachment)
487
+ [:thumb, :medium, :large, :original].inject({}) do |files, style|
488
+ data = `curl "#{attachment.url(style)}" 2>/dev/null`.chomp
489
+ t = Tempfile.new("paperclip-test")
490
+ t.binmode
491
+ t.write(data)
492
+ t.rewind
493
+ files[style] = t
494
+ files
495
+ end
496
+ end
497
+
498
+ def s3_headers_for(attachment, style)
499
+ `curl --head "#{attachment.url(style)}" 2>/dev/null`.split("\n").inject({}) do |h, head|
500
+ split_head = head.chomp.split(/\s*:\s*/, 2)
501
+ h[split_head.first.downcase] = split_head.last unless split_head.empty?
502
+ h
503
+ end
504
+ end
505
+
506
+ context "A model with an S3 attachment" do
507
+ before do
508
+ rebuild_model(
509
+ styles: {
510
+ large: "300x300>",
511
+ medium: "100x100",
512
+ thumb: ["32x32#", :gif],
513
+ custom: {
514
+ geometry: "32x32#",
515
+ s3_headers: { "Cache-Control" => "max-age=31557600" },
516
+ s3_metadata: { "foo" => "bar" }
517
+ }
518
+ },
519
+ storage: :s3,
520
+ s3_credentials: File.new(fixture_file("s3.yml")),
521
+ s3_options: { logger: Paperclip.logger },
522
+ default_style: :medium,
523
+ bucket: ENV["S3_BUCKET"],
524
+ path: ":class/:attachment/:id/:style/:basename.:extension"
525
+ )
526
+
527
+ @dummy = Dummy.new
528
+ @file = File.new(fixture_file("5k.png"), "rb")
529
+ @bad_file = File.new(fixture_file("bad.png"), "rb")
530
+
531
+ @dummy.avatar = @file
532
+ @dummy.valid?
533
+ @dummy.save!
534
+
535
+ @files_on_s3 = s3_files_for(@dummy.avatar)
536
+ end
537
+
538
+ after do
539
+ @file.close
540
+ @bad_file.close
541
+ @files_on_s3.values.each(&:close) if @files_on_s3
542
+ end
543
+
544
+ context "assigning itself to a new model" do
545
+ before do
546
+ @d2 = Dummy.new
547
+ @d2.avatar = @dummy.avatar
548
+ @d2.save
549
+ end
550
+
551
+ it "has the same name as the old file" do
552
+ assert_equal @d2.avatar.original_filename, @dummy.avatar.original_filename
553
+ end
554
+ end
555
+
556
+ it "has the same contents as the original" do
557
+ assert_equal @file.read, @files_on_s3[:original].read
558
+ end
559
+
560
+ it "writes and delete its files" do
561
+ [["434x66", :original],
562
+ ["300x46", :large],
563
+ ["100x15", :medium],
564
+ ["32x32", :thumb]].each do |geo, style|
565
+ cmd = %[identify -format "%wx%h" "#{@files_on_s3[style].path}"]
566
+ assert_equal geo, `#{cmd}`.chomp, cmd
567
+ end
568
+
569
+ @d2 = Dummy.find(@dummy.id)
570
+ @d2_files = s3_files_for @d2.avatar
571
+ [["434x66", :original],
572
+ ["300x46", :large],
573
+ ["100x15", :medium],
574
+ ["32x32", :thumb]].each do |geo, style|
575
+ cmd = %[identify -format "%wx%h" "#{@d2_files[style].path}"]
576
+ assert_equal geo, `#{cmd}`.chomp, cmd
577
+ end
578
+
579
+ @dummy.avatar.clear
580
+ assert_nil @dummy.avatar_file_name
581
+ assert @dummy.valid?
582
+ assert @dummy.save
583
+
584
+ [:thumb, :medium, :large, :original].each do |style|
585
+ assert !@dummy.avatar.exists?(style)
586
+ end
587
+
588
+ @d2 = Dummy.find(@dummy.id)
589
+ assert_nil @d2.avatar_file_name
590
+ end
591
+
592
+ it "works exactly the same when new as when reloaded" do
593
+ @d2 = Dummy.find(@dummy.id)
594
+
595
+ assert_equal @dummy.avatar_file_name, @d2.avatar_file_name
596
+
597
+ [:thumb, :medium, :large, :original].each do |style|
598
+ begin
599
+ first_file = open(@dummy.avatar.url(style))
600
+ second_file = open(@dummy.avatar.url(style))
601
+ assert_equal first_file.read, second_file.read
602
+ ensure
603
+ first_file.close if first_file
604
+ second_file.close if second_file
605
+ end
606
+ end
607
+
608
+ @d2.avatar.clear
609
+ assert @d2.save
610
+
611
+ [:thumb, :medium, :large, :original].each do |style|
612
+ assert !@dummy.avatar.exists?(style)
613
+ end
614
+ end
615
+
616
+ it "knows the difference between good files, bad files, and nil" do
617
+ @dummy.avatar = @bad_file
618
+ assert !@dummy.valid?
619
+ @dummy.avatar = nil
620
+ assert @dummy.valid?
621
+
622
+ Dummy.validates_attachment_presence :avatar
623
+ @d2 = Dummy.find(@dummy.id)
624
+ @d2.avatar = @file
625
+ assert @d2.valid?
626
+ @d2.avatar = @bad_file
627
+ assert !@d2.valid?
628
+ @d2.avatar = nil
629
+ assert !@d2.valid?
630
+ end
631
+
632
+ it "is able to reload without saving and not have the file disappear" do
633
+ @dummy.avatar = @file
634
+ assert @dummy.save
635
+ @dummy.avatar = nil
636
+ assert_nil @dummy.avatar_file_name
637
+ @dummy.reload
638
+ assert_equal "5k.png", @dummy.avatar_file_name
639
+ end
640
+
641
+ it "has the right content type" do
642
+ headers = s3_headers_for(@dummy.avatar, :original)
643
+ assert_equal "image/png", headers["content-type"]
644
+ end
645
+
646
+ it "has the right style-specific headers" do
647
+ headers = s3_headers_for(@dummy.avatar, :custom)
648
+ assert_equal "max-age=31557600", headers["cache-control"]
649
+ end
650
+
651
+ it "has the right style-specific metadata" do
652
+ headers = s3_headers_for(@dummy.avatar, :custom)
653
+ assert_equal "bar", headers["x-amz-meta-foo"]
654
+ end
655
+
656
+ context "with non-english character in the file name" do
657
+ before do
658
+ allow(@file).to receive(:original_filename).and_return("クリップ.png")
659
+ @dummy.avatar = @file
660
+ end
661
+
662
+ it "does not raise any error" do
663
+ @dummy.save!
664
+ end
665
+ end
666
+ end
667
+ end
668
+
669
+ context "Copying attachments between models" do
670
+ before do
671
+ rebuild_model
672
+ @file = File.new(fixture_file("5k.png"), "rb")
673
+ end
674
+
675
+ after { @file.close }
676
+
677
+ it "succeeds when original attachment is a file" do
678
+ original = Dummy.new
679
+ original.avatar = @file
680
+ assert original.save
681
+
682
+ copy = Dummy.new
683
+ copy.avatar = original.avatar
684
+ assert copy.save
685
+
686
+ assert copy.avatar.present?
687
+ end
688
+
689
+ it "succeeds when original attachment is empty" do
690
+ original = Dummy.create!
691
+
692
+ copy = Dummy.new
693
+ copy.avatar = @file
694
+ assert copy.save
695
+ assert copy.avatar.present?
696
+
697
+ copy.avatar = original.avatar
698
+ assert copy.save
699
+ assert !copy.avatar.present?
700
+ end
701
+ end
702
+ end