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,252 @@
1
+ require "spec_helper"
2
+ require "paperclip/schema"
3
+ require "active_support/testing/deprecation"
4
+
5
+ describe Paperclip::Schema do
6
+ include ActiveSupport::Testing::Deprecation
7
+
8
+ before do
9
+ rebuild_class
10
+ end
11
+
12
+ after do
13
+ begin
14
+ Dummy.connection.drop_table :dummies
15
+ rescue StandardError
16
+ nil
17
+ end
18
+ end
19
+
20
+ context "within table definition" do
21
+ context "using #has_attached_file" do
22
+ before do
23
+ ActiveSupport::Deprecation.silenced = false
24
+ end
25
+ it "creates attachment columns" do
26
+ Dummy.connection.create_table :dummies, force: true do |t|
27
+ ActiveSupport::Deprecation.silence do
28
+ t.has_attached_file :avatar
29
+ end
30
+ end
31
+
32
+ columns = Dummy.columns.map { |column| [column.name, column.sql_type] }
33
+
34
+ expect(columns).to include(["avatar_file_name", "varchar"])
35
+ expect(columns).to include(["avatar_content_type", "varchar"])
36
+ expect(columns).to include(["avatar_file_size", "bigint"])
37
+ expect(columns).to include(["avatar_updated_at", "datetime"])
38
+ end
39
+
40
+ it "displays deprecation warning" do
41
+ Dummy.connection.create_table :dummies, force: true do |t|
42
+ assert_deprecated do
43
+ t.has_attached_file :avatar
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ context "using #attachment" do
50
+ before do
51
+ Dummy.connection.create_table :dummies, force: true do |t|
52
+ t.attachment :avatar
53
+ end
54
+ end
55
+
56
+ it "creates attachment columns" do
57
+ columns = Dummy.columns.map { |column| [column.name, column.sql_type] }
58
+
59
+ expect(columns).to include(["avatar_file_name", "varchar"])
60
+ expect(columns).to include(["avatar_content_type", "varchar"])
61
+ expect(columns).to include(["avatar_file_size", "bigint"])
62
+ expect(columns).to include(["avatar_updated_at", "datetime"])
63
+ end
64
+ end
65
+
66
+ context "using #attachment with options" do
67
+ before do
68
+ Dummy.connection.create_table :dummies, force: true do |t|
69
+ t.attachment :avatar, default: 1, file_name: { default: "default" }
70
+ end
71
+ end
72
+
73
+ it "sets defaults on columns" do
74
+ defaults_columns = ["avatar_file_name", "avatar_content_type", "avatar_file_size"]
75
+ columns = Dummy.columns.select { |e| defaults_columns.include? e.name }
76
+
77
+ expect(columns).to have_column("avatar_file_name").with_default("default")
78
+ expect(columns).to have_column("avatar_content_type").with_default("1")
79
+ expect(columns).to have_column("avatar_file_size").with_default(1)
80
+ end
81
+ end
82
+ end
83
+
84
+ context "within schema statement" do
85
+ before do
86
+ Dummy.connection.create_table :dummies, force: true
87
+ end
88
+
89
+ context "migrating up" do
90
+ context "with single attachment" do
91
+ before do
92
+ Dummy.connection.add_attachment :dummies, :avatar
93
+ end
94
+
95
+ it "creates attachment columns" do
96
+ columns = Dummy.columns.map { |column| [column.name, column.sql_type] }
97
+
98
+ expect(columns).to include(["avatar_file_name", "varchar"])
99
+ expect(columns).to include(["avatar_content_type", "varchar"])
100
+ expect(columns).to include(["avatar_file_size", "bigint"])
101
+ expect(columns).to include(["avatar_updated_at", "datetime"])
102
+ end
103
+ end
104
+
105
+ context "with single attachment and options" do
106
+ before do
107
+ Dummy.connection.add_attachment :dummies, :avatar, default: "1", file_name: { default: "default" }
108
+ end
109
+
110
+ it "sets defaults on columns" do
111
+ defaults_columns = ["avatar_file_name", "avatar_content_type", "avatar_file_size"]
112
+ columns = Dummy.columns.select { |e| defaults_columns.include? e.name }
113
+
114
+ expect(columns).to have_column("avatar_file_name").with_default("default")
115
+ expect(columns).to have_column("avatar_content_type").with_default("1")
116
+ expect(columns).to have_column("avatar_file_size").with_default(1)
117
+ end
118
+ end
119
+
120
+ context "with multiple attachments" do
121
+ before do
122
+ Dummy.connection.add_attachment :dummies, :avatar, :photo
123
+ end
124
+
125
+ it "creates attachment columns" do
126
+ columns = Dummy.columns.map { |column| [column.name, column.sql_type] }
127
+
128
+ expect(columns).to include(["avatar_file_name", "varchar"])
129
+ expect(columns).to include(["avatar_content_type", "varchar"])
130
+ expect(columns).to include(["avatar_file_size", "bigint"])
131
+ expect(columns).to include(["avatar_updated_at", "datetime"])
132
+ expect(columns).to include(["photo_file_name", "varchar"])
133
+ expect(columns).to include(["photo_content_type", "varchar"])
134
+ expect(columns).to include(["photo_file_size", "bigint"])
135
+ expect(columns).to include(["photo_updated_at", "datetime"])
136
+ end
137
+ end
138
+
139
+ context "with multiple attachments and options" do
140
+ before do
141
+ Dummy.connection.add_attachment :dummies, :avatar, :photo, default: "1", file_name: { default: "default" }
142
+ end
143
+
144
+ it "sets defaults on columns" do
145
+ defaults_columns = ["avatar_file_name", "avatar_content_type", "avatar_file_size", "photo_file_name", "photo_content_type", "photo_file_size"]
146
+ columns = Dummy.columns.select { |e| defaults_columns.include? e.name }
147
+
148
+ expect(columns).to have_column("avatar_file_name").with_default("default")
149
+ expect(columns).to have_column("avatar_content_type").with_default("1")
150
+ expect(columns).to have_column("avatar_file_size").with_default(1)
151
+ expect(columns).to have_column("photo_file_name").with_default("default")
152
+ expect(columns).to have_column("photo_content_type").with_default("1")
153
+ expect(columns).to have_column("photo_file_size").with_default(1)
154
+ end
155
+ end
156
+
157
+ context "with no attachment" do
158
+ it "raises an error" do
159
+ assert_raises ArgumentError do
160
+ Dummy.connection.add_attachment :dummies
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ context "migrating down" do
167
+ before do
168
+ Dummy.connection.change_table :dummies do |t|
169
+ t.column :avatar_file_name, :string
170
+ t.column :avatar_content_type, :string
171
+ t.column :avatar_file_size, :bigint
172
+ t.column :avatar_updated_at, :datetime
173
+ end
174
+ end
175
+
176
+ context "using #drop_attached_file" do
177
+ before do
178
+ ActiveSupport::Deprecation.silenced = false
179
+ end
180
+ it "removes the attachment columns" do
181
+ ActiveSupport::Deprecation.silence do
182
+ Dummy.connection.drop_attached_file :dummies, :avatar
183
+ end
184
+
185
+ columns = Dummy.columns.map { |column| [column.name, column.sql_type] }
186
+
187
+ expect(columns).to_not include(["avatar_file_name", "varchar"])
188
+ expect(columns).to_not include(["avatar_content_type", "varchar"])
189
+ expect(columns).to_not include(["avatar_file_size", "bigint"])
190
+ expect(columns).to_not include(["avatar_updated_at", "datetime"])
191
+ end
192
+
193
+ it "displays a deprecation warning" do
194
+ assert_deprecated do
195
+ Dummy.connection.drop_attached_file :dummies, :avatar
196
+ end
197
+ end
198
+ end
199
+
200
+ context "using #remove_attachment" do
201
+ context "with single attachment" do
202
+ before do
203
+ Dummy.connection.remove_attachment :dummies, :avatar
204
+ end
205
+
206
+ it "removes the attachment columns" do
207
+ columns = Dummy.columns.map { |column| [column.name, column.sql_type] }
208
+
209
+ expect(columns).to_not include(["avatar_file_name", "varchar"])
210
+ expect(columns).to_not include(["avatar_content_type", "varchar"])
211
+ expect(columns).to_not include(["avatar_file_size", "bigint"])
212
+ expect(columns).to_not include(["avatar_updated_at", "datetime"])
213
+ end
214
+ end
215
+
216
+ context "with multiple attachments" do
217
+ before do
218
+ Dummy.connection.change_table :dummies do |t|
219
+ t.column :photo_file_name, :string
220
+ t.column :photo_content_type, :string
221
+ t.column :photo_file_size, :bigint
222
+ t.column :photo_updated_at, :datetime
223
+ end
224
+
225
+ Dummy.connection.remove_attachment :dummies, :avatar, :photo
226
+ end
227
+
228
+ it "removes the attachment columns" do
229
+ columns = Dummy.columns.map { |column| [column.name, column.sql_type] }
230
+
231
+ expect(columns).to_not include(["avatar_file_name", "varchar"])
232
+ expect(columns).to_not include(["avatar_content_type", "varchar"])
233
+ expect(columns).to_not include(["avatar_file_size", "bigint"])
234
+ expect(columns).to_not include(["avatar_updated_at", "datetime"])
235
+ expect(columns).to_not include(["photo_file_name", "varchar"])
236
+ expect(columns).to_not include(["photo_content_type", "varchar"])
237
+ expect(columns).to_not include(["photo_file_size", "bigint"])
238
+ expect(columns).to_not include(["photo_updated_at", "datetime"])
239
+ end
240
+ end
241
+
242
+ context "with no attachment" do
243
+ it "raises an error" do
244
+ assert_raises ArgumentError do
245
+ Dummy.connection.remove_attachment :dummies
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,79 @@
1
+ require "spec_helper"
2
+
3
+ describe Paperclip::Storage::Filesystem do
4
+ context "Filesystem" do
5
+ context "normal file" do
6
+ before do
7
+ rebuild_model styles: { thumbnail: "25x25#" }
8
+ @dummy = Dummy.create!
9
+
10
+ @file = File.open(fixture_file("5k.png"))
11
+ @dummy.avatar = @file
12
+ end
13
+
14
+ after { @file.close }
15
+
16
+ it "allows file assignment" do
17
+ assert @dummy.save
18
+ end
19
+
20
+ it "stores the original" do
21
+ @dummy.save
22
+ assert_file_exists(@dummy.avatar.path)
23
+ end
24
+
25
+ it "stores the thumbnail" do
26
+ @dummy.save
27
+ assert_file_exists(@dummy.avatar.path(:thumbnail))
28
+ end
29
+
30
+ it "is rewinded after flush_writes" do
31
+ @dummy.avatar.instance_eval "def after_flush_writes; end"
32
+
33
+ files = @dummy.avatar.queued_for_write.values
34
+ @dummy.save
35
+ assert files.none?(&:eof?), "Expect all the files to be rewinded."
36
+ end
37
+
38
+ it "is removed after after_flush_writes" do
39
+ paths = @dummy.avatar.queued_for_write.values.map(&:path)
40
+ @dummy.save
41
+ assert paths.none? { |path| File.exist?(path) },
42
+ "Expect all the files to be deleted."
43
+ end
44
+
45
+ it "copies the file to a known location with copy_to_local_file" do
46
+ tempfile = Tempfile.new("known_location")
47
+ @dummy.avatar.copy_to_local_file(:original, tempfile.path)
48
+ tempfile.rewind
49
+ assert_equal @file.read, tempfile.read
50
+ tempfile.close
51
+ end
52
+ end
53
+
54
+ context "with file that has space in file name" do
55
+ before do
56
+ rebuild_model styles: { thumbnail: "25x25#" }
57
+ @dummy = Dummy.create!
58
+
59
+ @file = File.open(fixture_file("spaced file.png"))
60
+ @dummy.avatar = @file
61
+ @dummy.save
62
+ end
63
+
64
+ after { @file.close }
65
+
66
+ it "stores the file" do
67
+ assert_file_exists(@dummy.avatar.path)
68
+ end
69
+
70
+ it "returns a replaced version for path" do
71
+ assert_match /.+\/spaced_file\.png/, @dummy.avatar.path
72
+ end
73
+
74
+ it "returns a replaced version for url" do
75
+ assert_match /.+\/spaced_file\.png/, @dummy.avatar.url
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,560 @@
1
+ require "spec_helper"
2
+ require "fog/aws"
3
+ require "fog/local"
4
+ require "timecop"
5
+
6
+ describe Paperclip::Storage::Fog do
7
+ context "" do
8
+ before { Fog.mock! }
9
+
10
+ context "with credentials provided in a path string" do
11
+ before do
12
+ rebuild_model styles: { medium: "300x300>", thumb: "100x100>" },
13
+ storage: :fog,
14
+ url: "/:attachment/:filename",
15
+ fog_directory: "paperclip",
16
+ fog_credentials: fixture_file("fog.yml")
17
+ @file = File.new(fixture_file("5k.png"), "rb")
18
+ @dummy = Dummy.new
19
+ @dummy.avatar = @file
20
+ end
21
+
22
+ after { @file.close }
23
+
24
+ it "has the proper information loading credentials from a file" do
25
+ assert_equal @dummy.avatar.fog_credentials[:provider], "AWS"
26
+ end
27
+ end
28
+
29
+ context "with credentials provided in a File object" do
30
+ before do
31
+ rebuild_model styles: { medium: "300x300>", thumb: "100x100>" },
32
+ storage: :fog,
33
+ url: "/:attachment/:filename",
34
+ fog_directory: "paperclip",
35
+ fog_credentials: File.open(fixture_file("fog.yml"))
36
+ @file = File.new(fixture_file("5k.png"), "rb")
37
+ @dummy = Dummy.new
38
+ @dummy.avatar = @file
39
+ end
40
+
41
+ after { @file.close }
42
+
43
+ it "has the proper information loading credentials from a file" do
44
+ assert_equal @dummy.avatar.fog_credentials[:provider], "AWS"
45
+ end
46
+ end
47
+
48
+ context "with default values for path and url" do
49
+ before do
50
+ rebuild_model styles: { medium: "300x300>", thumb: "100x100>" },
51
+ storage: :fog,
52
+ url: "/:attachment/:filename",
53
+ fog_directory: "paperclip",
54
+ fog_credentials: {
55
+ provider: "AWS",
56
+ aws_access_key_id: "AWS_ID",
57
+ aws_secret_access_key: "AWS_SECRET"
58
+ }
59
+ @file = File.new(fixture_file("5k.png"), "rb")
60
+ @dummy = Dummy.new
61
+ @dummy.avatar = @file
62
+ end
63
+
64
+ after { @file.close }
65
+
66
+ it "is able to interpolate the path without blowing up" do
67
+ assert_equal File.expand_path(File.join(File.dirname(__FILE__), "../../../tmp/public/avatars/5k.png")),
68
+ @dummy.avatar.path
69
+ end
70
+ end
71
+
72
+ context "with no path or url given and using defaults" do
73
+ before do
74
+ rebuild_model styles: { medium: "300x300>", thumb: "100x100>" },
75
+ storage: :fog,
76
+ fog_directory: "paperclip",
77
+ fog_credentials: {
78
+ provider: "AWS",
79
+ aws_access_key_id: "AWS_ID",
80
+ aws_secret_access_key: "AWS_SECRET"
81
+ }
82
+ @file = File.new(fixture_file("5k.png"), "rb")
83
+ @dummy = Dummy.new
84
+ @dummy.id = 1
85
+ @dummy.avatar = @file
86
+ end
87
+
88
+ after { @file.close }
89
+
90
+ it "has correct path and url from interpolated defaults" do
91
+ assert_equal "dummies/avatars/000/000/001/original/5k.png", @dummy.avatar.path
92
+ end
93
+ end
94
+
95
+ context "with file params provided as lambda" do
96
+ before do
97
+ fog_file = lambda { |a| { custom_header: a.instance.custom_method } }
98
+ klass = rebuild_model storage: :fog,
99
+ fog_file: fog_file
100
+
101
+ klass.class_eval do
102
+ def custom_method
103
+ "foobar"
104
+ end
105
+ end
106
+
107
+ @dummy = Dummy.new
108
+ end
109
+
110
+ it "is able to evaluate correct values for file headers" do
111
+ assert_equal @dummy.avatar.send(:fog_file), custom_header: "foobar"
112
+ end
113
+ end
114
+
115
+ before do
116
+ @fog_directory = "papercliptests"
117
+
118
+ @credentials = {
119
+ provider: "AWS",
120
+ aws_access_key_id: "ID",
121
+ aws_secret_access_key: "SECRET"
122
+ }
123
+
124
+ @connection = Fog::Storage.new(@credentials)
125
+ @connection.directories.create(
126
+ key: @fog_directory
127
+ )
128
+
129
+ @options = {
130
+ fog_directory: @fog_directory,
131
+ fog_credentials: @credentials,
132
+ fog_host: nil,
133
+ fog_file: { cache_control: 1234 },
134
+ path: ":attachment/:basename:dotextension",
135
+ storage: :fog
136
+ }
137
+
138
+ rebuild_model(@options)
139
+ end
140
+
141
+ it "is extended by the Fog module" do
142
+ assert Dummy.new.avatar.is_a?(Paperclip::Storage::Fog)
143
+ end
144
+
145
+ context "when assigned" do
146
+ before do
147
+ @file = File.new(fixture_file("5k.png"), "rb")
148
+ @dummy = Dummy.new
149
+ @dummy.avatar = @file
150
+ end
151
+
152
+ after do
153
+ @file.close
154
+ directory = @connection.directories.new(key: @fog_directory)
155
+ directory.files.each(&:destroy)
156
+ directory.destroy
157
+ end
158
+
159
+ it "is rewound after flush_writes" do
160
+ @dummy.avatar.instance_eval "def after_flush_writes; end"
161
+
162
+ files = @dummy.avatar.queued_for_write.values
163
+ @dummy.save
164
+ assert files.none?(&:eof?), "Expect all the files to be rewinded."
165
+ end
166
+
167
+ it "is removed after after_flush_writes" do
168
+ paths = @dummy.avatar.queued_for_write.values.map(&:path)
169
+ @dummy.save
170
+ assert paths.none? { |path| File.exist?(path) },
171
+ "Expect all the files to be deleted."
172
+ end
173
+
174
+ it "is able to be copied to a local file" do
175
+ @dummy.save
176
+ tempfile = Tempfile.new("known_location")
177
+ tempfile.binmode
178
+ @dummy.avatar.copy_to_local_file(:original, tempfile.path)
179
+ tempfile.rewind
180
+ assert_equal @connection.directories.get(@fog_directory).files.get(@dummy.avatar.path).body,
181
+ tempfile.read
182
+ tempfile.close
183
+ end
184
+
185
+ it "is able to be handled when missing while copying to a local file" do
186
+ tempfile = Tempfile.new("known_location")
187
+ tempfile.binmode
188
+ assert_equal false, @dummy.avatar.copy_to_local_file(:original, tempfile.path)
189
+ tempfile.close
190
+ end
191
+
192
+ it "passes the content type to the Fog::Storage::AWS::Files instance" do
193
+ expect_any_instance_of(Fog::Storage::AWS::Files).to receive(:create).with(hash_including(:content_type))
194
+ @dummy.save
195
+ end
196
+
197
+ context "without a bucket" do
198
+ before do
199
+ @connection.directories.get(@fog_directory).destroy
200
+ end
201
+
202
+ it "creates the bucket" do
203
+ assert @dummy.save
204
+ assert @connection.directories.get(@fog_directory)
205
+ end
206
+
207
+ it "sucessfully rewinds the file during bucket creation" do
208
+ assert @dummy.save
209
+ expect(Paperclip.io_adapters.for(@dummy.avatar).read.length).to be > 0
210
+ end
211
+ end
212
+
213
+ context "with a bucket" do
214
+ it "succeeds" do
215
+ assert @dummy.save
216
+ end
217
+ end
218
+
219
+ context "without a fog_host" do
220
+ before do
221
+ rebuild_model(@options.merge(fog_host: nil))
222
+ @dummy = Dummy.new
223
+ @dummy.avatar = StringIO.new(".")
224
+ @dummy.save
225
+ end
226
+
227
+ it "provides a public url" do
228
+ assert !@dummy.avatar.url.nil?
229
+ end
230
+ end
231
+
232
+ context "with a fog_host" do
233
+ before do
234
+ rebuild_model(@options.merge(fog_host: "http://example.com"))
235
+ @dummy = Dummy.new
236
+ @dummy.avatar = StringIO.new(".\n")
237
+ @dummy.save
238
+ end
239
+
240
+ it "provides a public url" do
241
+ expect(@dummy.avatar.url).to match(/^http:\/\/example\.com\/avatars\/data\?\d*$/)
242
+ end
243
+ end
244
+
245
+ context "with a fog_host that includes a wildcard placeholder" do
246
+ before do
247
+ rebuild_model(
248
+ fog_directory: @fog_directory,
249
+ fog_credentials: @credentials,
250
+ fog_host: "http://img%d.example.com",
251
+ path: ":attachment/:basename:dotextension",
252
+ storage: :fog
253
+ )
254
+ @dummy = Dummy.new
255
+ @dummy.avatar = StringIO.new(".\n")
256
+ @dummy.save
257
+ end
258
+
259
+ it "provides a public url" do
260
+ expect(@dummy.avatar.url).to match(/^http:\/\/img[0123]\.example\.com\/avatars\/data\?\d*$/)
261
+ end
262
+ end
263
+
264
+ context "with fog_public set to false" do
265
+ before do
266
+ rebuild_model(@options.merge(fog_public: false))
267
+ @dummy = Dummy.new
268
+ @dummy.avatar = StringIO.new(".")
269
+ @dummy.save
270
+ end
271
+
272
+ it "sets the @fog_public instance variable to false" do
273
+ assert_equal false, @dummy.avatar.instance_variable_get("@options")[:fog_public]
274
+ assert_equal false, @dummy.avatar.fog_public
275
+ end
276
+ end
277
+
278
+ context "with fog_public as a proc" do
279
+ let(:proc) { ->(attachment) { !attachment } }
280
+
281
+ before do
282
+ rebuild_model(@options.merge(fog_public: proc))
283
+ @dummy = Dummy.new
284
+ @dummy.avatar = StringIO.new(".")
285
+ @dummy.save
286
+ end
287
+
288
+ it "sets the @fog_public instance variable to false" do
289
+ assert_equal proc, @dummy.avatar.instance_variable_get("@options")[:fog_public]
290
+ assert_equal false, @dummy.avatar.fog_public
291
+ end
292
+ end
293
+
294
+ context "with styles set and fog_public set to false" do
295
+ before do
296
+ rebuild_model(@options.merge(fog_public: false, styles: { medium: "300x300>", thumb: "100x100>" }))
297
+ @file = File.new(fixture_file("5k.png"), "rb")
298
+ @dummy = Dummy.new
299
+ @dummy.avatar = @file
300
+ @dummy.save
301
+ end
302
+
303
+ it "sets the @fog_public for a particular style to false" do
304
+ assert_equal false, @dummy.avatar.instance_variable_get("@options")[:fog_public]
305
+ assert_equal false, @dummy.avatar.fog_public(:thumb)
306
+ end
307
+ end
308
+
309
+ context "with styles set and fog_public set per-style" do
310
+ before do
311
+ rebuild_model(@options.merge(fog_public: { medium: false, thumb: true },
312
+ styles: { medium: "300x300>", thumb: "100x100>" }))
313
+ @file = File.new(fixture_file("5k.png"), "rb")
314
+ @dummy = Dummy.new
315
+ @dummy.avatar = @file
316
+ @dummy.save
317
+ end
318
+
319
+ it "sets the fog_public for a particular style to correct value" do
320
+ assert_equal false, @dummy.avatar.fog_public(:medium)
321
+ assert_equal true, @dummy.avatar.fog_public(:thumb)
322
+ end
323
+ end
324
+
325
+ context "with fog_public not set" do
326
+ before do
327
+ rebuild_model(@options)
328
+ @dummy = Dummy.new
329
+ @dummy.avatar = StringIO.new(".")
330
+ @dummy.save
331
+ end
332
+
333
+ it "defaults fog_public to true" do
334
+ assert_equal true, @dummy.avatar.fog_public
335
+ end
336
+ end
337
+
338
+ context "with scheme set" do
339
+ before do
340
+ rebuild_model(@options.merge(fog_credentials: @credentials.merge(scheme: "http")))
341
+ @file = File.new(fixture_file("5k.png"), "rb")
342
+ @dummy = Dummy.new
343
+ @dummy.avatar = @file
344
+ @dummy.save
345
+ end
346
+
347
+ it "honors the scheme in public url" do
348
+ assert_match(/^http:\/\//, @dummy.avatar.url)
349
+ end
350
+ it "honors the scheme in expiring url" do
351
+ assert_match(/^http:\/\//, @dummy.avatar.expiring_url)
352
+ end
353
+ end
354
+
355
+ context "with scheme not set" do
356
+ before do
357
+ rebuild_model(@options)
358
+ @file = File.new(fixture_file("5k.png"), "rb")
359
+ @dummy = Dummy.new
360
+ @dummy.avatar = @file
361
+ @dummy.save
362
+ end
363
+
364
+ it "provides HTTPS public url" do
365
+ assert_match(/^https:\/\//, @dummy.avatar.url)
366
+ end
367
+ it "provides HTTPS expiring url" do
368
+ assert_match(/^https:\/\//, @dummy.avatar.expiring_url)
369
+ end
370
+ end
371
+
372
+ context "with a valid bucket name for a subdomain" do
373
+ before { allow(@dummy).to receive(:new_record?).and_return(false) }
374
+
375
+ it "provides an url in subdomain style" do
376
+ assert_match(/^https:\/\/papercliptests.s3.amazonaws.com\/avatars\/5k.png/, @dummy.avatar.url)
377
+ end
378
+
379
+ it "provides an url that expires in subdomain style" do
380
+ assert_match(/^https:\/\/papercliptests.s3.amazonaws.com\/avatars\/5k.png.+Expires=.+$/, @dummy.avatar.expiring_url)
381
+ end
382
+ end
383
+
384
+ context "generating an expiring url" do
385
+ it "generates the same url when using Times and Integer offsets" do
386
+ Timecop.freeze do
387
+ offset = 1234
388
+ rebuild_model(@options)
389
+ dummy = Dummy.new
390
+ dummy.avatar = StringIO.new(".")
391
+
392
+ assert_equal dummy.avatar.expiring_url(offset),
393
+ dummy.avatar.expiring_url(Time.now + offset)
394
+ end
395
+ end
396
+
397
+ it "matches the default url if there is no assignment" do
398
+ dummy = Dummy.new
399
+ assert_equal dummy.avatar.url, dummy.avatar.expiring_url
400
+ end
401
+
402
+ it "matches the default url when given a style if there is no assignment" do
403
+ dummy = Dummy.new
404
+ assert_equal dummy.avatar.url(:thumb), dummy.avatar.expiring_url(3600, :thumb)
405
+ end
406
+ end
407
+
408
+ context "with an invalid bucket name for a subdomain" do
409
+ before do
410
+ rebuild_model(@options.merge(fog_directory: "this_is_invalid"))
411
+ @dummy = Dummy.new
412
+ @dummy.avatar = @file
413
+ @dummy.save
414
+ end
415
+
416
+ it "does not match the bucket-subdomain restrictions" do
417
+ invalid_subdomains = %w(this_is_invalid in iamareallylongbucketnameiamareallylongbucketnameiamareallylongbu invalid- inval..id inval-.id inval.-id -invalid 192.168.10.2)
418
+ invalid_subdomains.each do |name|
419
+ assert_no_match Paperclip::Storage::Fog::AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX, name
420
+ end
421
+ end
422
+
423
+ it "provides an url in folder style" do
424
+ assert_match(/^https:\/\/s3.amazonaws.com\/this_is_invalid\/avatars\/5k.png\?\d*$/, @dummy.avatar.url)
425
+ end
426
+
427
+ it "provides a url that expires in folder style" do
428
+ assert_match(/^https:\/\/s3.amazonaws.com\/this_is_invalid\/avatars\/5k.png.+Expires=.+$/, @dummy.avatar.expiring_url)
429
+ end
430
+ end
431
+
432
+ context "with a proc for a bucket name evaluating a model method" do
433
+ before do
434
+ @dynamic_fog_directory = "dynamicpaperclip"
435
+ rebuild_model(@options.merge(fog_directory: lambda { |attachment| attachment.instance.bucket_name }))
436
+ @dummy = Dummy.new
437
+ allow(@dummy).to receive(:bucket_name).and_return(@dynamic_fog_directory)
438
+ @dummy.avatar = @file
439
+ @dummy.save
440
+ end
441
+
442
+ it "has created the bucket" do
443
+ assert @connection.directories.get(@dynamic_fog_directory).inspect
444
+ end
445
+
446
+ it "provides an url using dynamic bucket name" do
447
+ assert_match(/^https:\/\/dynamicpaperclip.s3.amazonaws.com\/avatars\/5k.png\?\d*$/, @dummy.avatar.url)
448
+ end
449
+ end
450
+
451
+ context "with a proc for the fog_host evaluating a model method" do
452
+ before do
453
+ rebuild_model(@options.merge(fog_host: lambda { |attachment| attachment.instance.fog_host }))
454
+ @dummy = Dummy.new
455
+ allow(@dummy).to receive(:fog_host).and_return("http://dynamicfoghost.com")
456
+ @dummy.avatar = @file
457
+ @dummy.save
458
+ end
459
+
460
+ it "provides a public url" do
461
+ assert_match(/http:\/\/dynamicfoghost\.com/, @dummy.avatar.url)
462
+ end
463
+ end
464
+
465
+ context "with a custom fog_host" do
466
+ before do
467
+ rebuild_model(@options.merge(fog_host: "http://dynamicfoghost.com"))
468
+ @dummy = Dummy.new
469
+ @dummy.avatar = @file
470
+ @dummy.save
471
+ end
472
+
473
+ it "provides a public url" do
474
+ assert_match(/http:\/\/dynamicfoghost\.com/, @dummy.avatar.url)
475
+ end
476
+
477
+ it "provides an expiring url" do
478
+ assert_match(/http:\/\/dynamicfoghost\.com/, @dummy.avatar.expiring_url)
479
+ end
480
+
481
+ context "with an invalid bucket name for a subdomain" do
482
+ before do
483
+ rebuild_model(@options.merge(fog_directory: "this_is_invalid", fog_host: "http://dynamicfoghost.com"))
484
+ @dummy = Dummy.new
485
+ @dummy.avatar = @file
486
+ @dummy.save
487
+ end
488
+
489
+ it "provides an expiring url" do
490
+ assert_match(/http:\/\/dynamicfoghost\.com/, @dummy.avatar.expiring_url)
491
+ end
492
+ end
493
+ end
494
+
495
+ context "with a proc for the fog_credentials evaluating a model method" do
496
+ before do
497
+ @dynamic_fog_credentials = {
498
+ provider: "AWS",
499
+ aws_access_key_id: "DYNAMIC_ID",
500
+ aws_secret_access_key: "DYNAMIC_SECRET"
501
+ }
502
+ rebuild_model(@options.merge(fog_credentials: lambda { |attachment| attachment.instance.fog_credentials }))
503
+ @dummy = Dummy.new
504
+ allow(@dummy).to receive(:fog_credentials).and_return(@dynamic_fog_credentials)
505
+ @dummy.avatar = @file
506
+ @dummy.save
507
+ end
508
+
509
+ it "provides a public url" do
510
+ assert_equal @dummy.avatar.fog_credentials, @dynamic_fog_credentials
511
+ end
512
+ end
513
+
514
+ context "with custom fog_options" do
515
+ before do
516
+ rebuild_model(
517
+ @options.merge(fog_options: { multipart_chunk_size: 104857600 })
518
+ )
519
+ @dummy = Dummy.new
520
+ @dummy.avatar = @file
521
+ end
522
+
523
+ it "applies the options to the fog #create call" do
524
+ files = double
525
+ allow(@dummy.avatar).to receive(:directory).and_return double(files: files)
526
+ expect(files).to receive(:create).with(
527
+ hash_including(multipart_chunk_size: 104857600)
528
+ )
529
+ @dummy.save
530
+ end
531
+ end
532
+ end
533
+ end
534
+
535
+ context "when using local storage" do
536
+ before do
537
+ Fog.unmock!
538
+ rebuild_model styles: { medium: "300x300>", thumb: "100x100>" },
539
+ storage: :fog,
540
+ url: "/:attachment/:filename",
541
+ fog_directory: "paperclip",
542
+ fog_credentials: { provider: :local, local_root: "." },
543
+ fog_host: "localhost"
544
+
545
+ @file = File.new(fixture_file("5k.png"), "rb")
546
+ @dummy = Dummy.new
547
+ @dummy.avatar = @file
548
+ allow(@dummy).to receive(:new_record?).and_return(false)
549
+ end
550
+
551
+ after do
552
+ @file.close
553
+ Fog.mock!
554
+ end
555
+
556
+ it "returns the public url in place of the expiring url" do
557
+ assert_match @dummy.avatar.public_url, @dummy.avatar.expiring_url
558
+ end
559
+ end
560
+ end