resizing 1.2.1 → 1.3.1

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer/Dockerfile +73 -0
  3. data/.devcontainer/compose.yaml +58 -0
  4. data/.devcontainer/devcontainer-lock.json +9 -0
  5. data/.devcontainer/devcontainer.json +32 -0
  6. data/.devcontainer/post-create.sh +41 -0
  7. data/.github/copilot-instructions.md +1 -0
  8. data/.github/labeler.yml +28 -0
  9. data/.github/release.yml +35 -0
  10. data/.github/workflows/labeler.yml +20 -0
  11. data/.github/workflows/lint.yml +37 -0
  12. data/.github/workflows/test.yml +16 -9
  13. data/.gitignore +6 -0
  14. data/AGENTS.md +1 -0
  15. data/CHANGELOG.md +3 -0
  16. data/CLAUDE.md +68 -0
  17. data/Gemfile +9 -3
  18. data/README.md +114 -2
  19. data/Rakefile +5 -7
  20. data/bin/setup +3 -0
  21. data/git-hooks/pre-push +6 -0
  22. data/git-hooks/pre-push-ruby.sh +48 -0
  23. data/lib/resizing/carrier_wave/callback_file.rb +88 -0
  24. data/lib/resizing/carrier_wave/storage/file.rb +120 -47
  25. data/lib/resizing/carrier_wave/storage/remote.rb +7 -3
  26. data/lib/resizing/carrier_wave.rb +51 -7
  27. data/lib/resizing/client.rb +0 -1
  28. data/lib/resizing/configurable.rb +1 -1
  29. data/lib/resizing/configuration.rb +40 -10
  30. data/lib/resizing/mock_client.rb +23 -7
  31. data/lib/resizing/public_id.rb +3 -2
  32. data/lib/resizing/version.rb +1 -1
  33. data/lib/resizing.rb +0 -1
  34. data/resizing.gemspec +3 -3
  35. data/test/resizing/carrier_wave/storage/file_test.rb +455 -9
  36. data/test/resizing/carrier_wave/storage/remote_test.rb +91 -9
  37. data/test/resizing/carrier_wave_cache_callbacks_test.rb +224 -0
  38. data/test/resizing/carrier_wave_callback_file_test.rb +77 -0
  39. data/test/resizing/carrier_wave_test.rb +477 -23
  40. data/test/resizing/carrier_wave_uploader_test.rb +211 -0
  41. data/test/resizing/client_test.rb +324 -25
  42. data/test/resizing/configuration_test.rb +235 -10
  43. data/test/resizing/http_clientable_test.rb +27 -0
  44. data/test/resizing/mock_client_test.rb +111 -1
  45. data/test/resizing/public_id_test.rb +96 -0
  46. data/test/resizing_module_test.rb +110 -0
  47. data/test/resizing_test.rb +8 -0
  48. data/test/test_helper.rb +358 -11
  49. data/test/vcr/carrier_wave_test/update_image.yml +63 -0
  50. metadata +35 -10
@@ -6,16 +6,11 @@ module Resizing
6
6
  module CarrierWave
7
7
  module Storage
8
8
  class RemoteTest < Minitest::Test
9
+ include FakeApiClientAssertions
10
+ include ResizingTestConfiguration
11
+
9
12
  def setup
10
- @configuration_template = {
11
- image_host: 'http://192.168.56.101:5000',
12
- video_host: 'http://192.168.56.101:5000',
13
- project_id: 'e06e710d-f026-4dcf-b2c0-eab0de8bb83f',
14
- secret_token: 'ewbym2r1pk49x1d2lxdbiiavnqp25j2kh00hsg3koy0ppm620x5mhlmgl3rq5ci8',
15
- open_timeout: 10,
16
- response_timeout: 20
17
- }
18
- Resizing.configure = @configuration_template
13
+ configure_resizing
19
14
 
20
15
  model = TestModel.new
21
16
  @uploader = model.resizing_picture
@@ -69,6 +64,93 @@ module Resizing
69
64
  assert_instance_of Resizing::CarrierWave::Storage::File, result
70
65
  assert_equal identifier, result.public_id.to_s
71
66
  end
67
+
68
+ IDENTIFIER = '/projects/e06e710d-f026-4dcf-b2c0-eab0de8bb83f/upload/images/14ea7aac-a194-4330-931f-6b562aec413d/v_8c5lEhDB5RT3PZp1Fn5PYGm9YVx_x0e'
69
+
70
+ def test_store_uploads_and_returns_file_with_public_id
71
+ VCR.use_cassette 'carrier_wave_test/save', record: :none do
72
+ file = @storage.store!(::File.open('test/data/images/sample1.jpg', 'r'))
73
+
74
+ assert_instance_of Resizing::CarrierWave::Storage::File, file
75
+ assert_equal IDENTIFIER, file.public_id.to_s
76
+ assert_equal 'image/jpeg', file.content_type
77
+ end
78
+
79
+ assert_equal IDENTIFIER, @uploader.model.read_attribute(:resizing_picture)
80
+ end
81
+
82
+ def test_cache_uploads_immediately_like_store
83
+ # Resizing はキャッシュをサポートしないため cache! でも直接アップロードする
84
+ VCR.use_cassette 'carrier_wave_test/save', record: :none do
85
+ file = @storage.cache!(::File.open('test/data/images/sample1.jpg', 'r'))
86
+
87
+ assert_instance_of Resizing::CarrierWave::Storage::File, file
88
+ assert_equal IDENTIFIER, file.public_id.to_s
89
+ end
90
+
91
+ assert_equal IDENTIFIER, @uploader.model.read_attribute(:resizing_picture)
92
+ end
93
+
94
+ def test_retrieve_returns_new_file_instance_each_time
95
+ first = @storage.retrieve!(IDENTIFIER)
96
+ second = @storage.retrieve!(IDENTIFIER)
97
+
98
+ refute_same first, second
99
+ assert_equal first.public_id.to_s, second.public_id.to_s
100
+ end
101
+
102
+ IMAGE_ID = '14ea7aac-a194-4330-931f-6b562aec413d'
103
+ OTHER_IDENTIFIER = '/projects/e06e710d-f026-4dcf-b2c0-eab0de8bb83f/upload/images/new-image-id-2222-2222-222222222222/v2'
104
+
105
+ def test_remove_deletes_the_image_stored_in_the_model_column
106
+ model = @uploader.model
107
+ model.send(:write_attribute, :resizing_picture, IDENTIFIER)
108
+
109
+ file = with_fake_api_client({ 'id' => IMAGE_ID }) do |fake|
110
+ result = @storage.remove!(nil)
111
+ assert_equal [IMAGE_ID], fake.deleted_ids
112
+ result
113
+ end
114
+
115
+ assert_instance_of Resizing::CarrierWave::Storage::File, file
116
+ assert_nil model.read_attribute(:resizing_picture)
117
+ end
118
+
119
+ def test_remove_accepts_a_carrier_wave_file_argument
120
+ # CarrierWave が storage.remove!(file) 経由で呼ぶ場合も ArgumentError にならないこと
121
+ model = @uploader.model
122
+ model.send(:write_attribute, :resizing_picture, IDENTIFIER)
123
+
124
+ with_fake_api_client({ 'id' => IMAGE_ID }) do |fake|
125
+ file = @uploader.file
126
+ result = @storage.remove!(file)
127
+
128
+ assert_equal [IMAGE_ID], fake.deleted_ids
129
+ assert_same file, result
130
+ end
131
+
132
+ assert_nil model.read_attribute(:resizing_picture)
133
+ end
134
+
135
+ def test_remove_deletes_the_given_file_not_the_current_column_value
136
+ # 画像更新後に古い画像を消す場合: 削除対象は渡された file、カラムは新しい画像のまま
137
+ model = @uploader.model
138
+ model.send(:write_attribute, :resizing_picture, OTHER_IDENTIFIER)
139
+ file = Resizing::CarrierWave::Storage::File.new(@uploader, IDENTIFIER)
140
+
141
+ with_fake_api_client({ 'id' => IMAGE_ID }) do |fake|
142
+ @storage.remove!(file)
143
+
144
+ assert_equal [IMAGE_ID], fake.deleted_ids
145
+ end
146
+
147
+ assert_equal OTHER_IDENTIFIER, model.read_attribute(:resizing_picture)
148
+ end
149
+
150
+ def test_storage_is_subclass_of_carrierwave_abstract_storage
151
+ assert Remote < ::CarrierWave::Storage::Abstract
152
+ assert_same @uploader, @storage.uploader
153
+ end
72
154
  end
73
155
  end
74
156
  end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ module Resizing
6
+ # Resizing::CarrierWave#cache! が CarrierWave の :cache コールバックを通すことをテストする
7
+ #
8
+ # - before :cache のチェック (拡張子 / content_type / サイズ) はアップロード前に働く
9
+ # - process! と cache_versions! は Resizing の設計に合わないので発火しない
10
+ class CarrierWaveCacheCallbacksTest < Minitest::Test
11
+ include VCRRequestAssertions
12
+ include ResizingTestConfiguration
13
+
14
+ def setup
15
+ TestModel.delete_all
16
+ configure_resizing
17
+ end
18
+
19
+ def teardown; end
20
+
21
+ # ============================================================
22
+ # before :cache のチェックが働くこと (アップロードされない)
23
+ # ============================================================
24
+
25
+ def test_extension_allowlist_rejects_file_without_uploading
26
+ assert_rejected_before_upload TestModelWithExtensionAllowlist
27
+ end
28
+
29
+ def test_extension_denylist_rejects_file_without_uploading
30
+ assert_rejected_before_upload TestModelWithExtensionDenylist
31
+ end
32
+
33
+ def test_content_type_allowlist_rejects_file_without_uploading
34
+ assert_rejected_before_upload TestModelWithContentTypeAllowlist
35
+ end
36
+
37
+ def test_size_range_rejects_file_without_uploading
38
+ assert_rejected_before_upload TestModelWithSizeRange
39
+ end
40
+
41
+ def test_cache_raises_integrity_error_when_called_directly
42
+ model = TestModelWithExtensionAllowlist.new
43
+ uploader = ResizingUploaderWithExtensionAllowlist.new(model, :resizing_picture)
44
+
45
+ assert_vcr_no_requests 'carrier_wave_test/save' do
46
+ assert_raises(::CarrierWave::IntegrityError) { uploader.cache!(sample_uploaded_file) }
47
+ end
48
+
49
+ assert_nil model.read_attribute(:resizing_picture)
50
+ end
51
+
52
+ def test_cache_ignores_empty_file
53
+ # 中身のないファイルは CarrierWave 本体の cache! と同じくアップロードしない
54
+ model = TestModel.new
55
+ uploader = ResizingUploader.new(model, :resizing_picture)
56
+
57
+ assert_vcr_no_requests 'carrier_wave_test/save' do
58
+ assert_nil uploader.cache!(StringIO.new(''))
59
+ end
60
+
61
+ assert_nil model.read_attribute(:resizing_picture)
62
+ end
63
+
64
+ # ============================================================
65
+ # 拡張子のチェックが content_type から判定されること
66
+ # ============================================================
67
+
68
+ # Resizing の公開 URL は末尾がバージョン文字列 (`/v<version>`) で、ドットを含むため
69
+ # ファイル名から取れる「拡張子」はバージョンの一部 (`Fv_Q`) になる
70
+ # (ダウンロードは差し替えるので、URL の中身は実在しなくてよい)
71
+ RESIZING_IMAGE_URL = "#{ResizingTestConfiguration::CONFIGURATION_TEMPLATE[:image_host]}" \
72
+ "/projects/#{ResizingTestConfiguration::CONFIGURATION_TEMPLATE[:project_id]}" \
73
+ '/upload/images/3f2c9d1e-5b7a-4e8c-9a0d-6c1b2e3f4a5d/v0cRq7Yb2mZt9LxA3wN8eKd5pHs1.Fv_Q'.freeze
74
+
75
+ def test_remote_resizing_url_is_accepted_by_content_type
76
+ # `remote_<column>_url=` に Resizing の URL を渡した場合、ファイル名の拡張子 (Fv_Q) ではなく
77
+ # レスポンスの Content-Type (image/jpeg) で判定され、アップロードされる
78
+ model = TestModelWithJpegAllowlist.new
79
+ remote_file = sample_remote_file(RESIZING_IMAGE_URL, content_type: 'image/jpeg')
80
+
81
+ assert_vcr_requests_count 'carrier_wave_test/save', 1 do
82
+ with_stubbed_download(remote_file) do
83
+ model.remote_resizing_picture_url = RESIZING_IMAGE_URL
84
+ end
85
+ end
86
+
87
+ assert model.valid?, model.errors.full_messages.join(', ')
88
+ assert_equal expect_identifier, model.read_attribute(:resizing_picture)
89
+ end
90
+
91
+ def test_remote_file_is_rejected_when_content_type_is_not_allowed
92
+ # Content-Type が image/png なら、拡張子の許可リスト (jpg / jpeg) に合わないので拒否される
93
+ model = TestModelWithJpegAllowlist.new
94
+ remote_file = sample_remote_file(RESIZING_IMAGE_URL, content_type: 'image/png')
95
+
96
+ assert_vcr_no_requests 'carrier_wave_test/save' do
97
+ with_stubbed_download(remote_file) do
98
+ model.remote_resizing_picture_url = RESIZING_IMAGE_URL
99
+ end
100
+ end
101
+
102
+ refute model.valid?
103
+ refute_empty model.errors[:resizing_picture]
104
+ assert_nil model.read_attribute(:resizing_picture)
105
+ end
106
+
107
+ def test_extension_of_file_name_is_used_when_content_type_is_unknown
108
+ # application/octet-stream は型の情報を持たないので、ファイル名の拡張子 (jpg) で判定される
109
+ model = TestModelWithJpegAllowlist.new
110
+ url = 'https://example.com/images/photo.jpg'
111
+ remote_file = sample_remote_file(url, content_type: 'application/octet-stream')
112
+
113
+ assert_vcr_requests_count 'carrier_wave_test/save', 1 do
114
+ with_stubbed_download(remote_file) do
115
+ model.remote_resizing_picture_url = url
116
+ end
117
+ end
118
+
119
+ assert model.valid?, model.errors.full_messages.join(', ')
120
+ assert_equal expect_identifier, model.read_attribute(:resizing_picture)
121
+ end
122
+
123
+ def test_extension_of_file_name_is_used_when_content_type_is_not_registered
124
+ # MIME::Types / MiniMime に登録されていない content_type でもエラーにならず、
125
+ # ファイル名の拡張子 (jpg) で判定される
126
+ model = TestModelWithJpegAllowlist.new
127
+ url = 'https://example.com/images/photo.jpg'
128
+ remote_file = sample_remote_file(url, content_type: 'image/x-unregistered-type')
129
+
130
+ assert_vcr_requests_count 'carrier_wave_test/save', 1 do
131
+ with_stubbed_download(remote_file) do
132
+ model.remote_resizing_picture_url = url
133
+ end
134
+ end
135
+
136
+ assert model.valid?, model.errors.full_messages.join(', ')
137
+ assert_equal expect_identifier, model.read_attribute(:resizing_picture)
138
+ end
139
+
140
+ def test_content_type_parameters_are_ignored
141
+ # `image/jpeg; charset=binary` のようなパラメータ付きの値でも image/jpeg として判定される
142
+ model = TestModelWithJpegAllowlist.new
143
+ remote_file = sample_remote_file(RESIZING_IMAGE_URL, content_type: 'image/jpeg; charset=binary')
144
+
145
+ assert_vcr_requests_count 'carrier_wave_test/save', 1 do
146
+ with_stubbed_download(remote_file) do
147
+ model.remote_resizing_picture_url = RESIZING_IMAGE_URL
148
+ end
149
+ end
150
+
151
+ assert model.valid?, model.errors.full_messages.join(', ')
152
+ assert_equal expect_identifier, model.read_attribute(:resizing_picture)
153
+ end
154
+
155
+ def test_content_type_takes_precedence_over_extension_of_uploaded_file
156
+ # ローカルからのアップロードでも、ファイル名の拡張子 (jpg) より content_type (image/png) が優先される
157
+ model = TestModelWithJpegAllowlist.new
158
+ file = File.open('test/data/images/sample1.jpg', 'r')
159
+ uploaded_file = ActionDispatch::Http::UploadedFile.new(filename: 'sample1.jpg', type: 'image/png', tempfile: file)
160
+
161
+ assert_vcr_no_requests 'carrier_wave_test/save' do
162
+ model.resizing_picture = uploaded_file
163
+ end
164
+
165
+ refute model.valid?
166
+ assert_nil model.read_attribute(:resizing_picture)
167
+ end
168
+
169
+ def test_uploaded_file_matching_its_content_type_is_accepted
170
+ # 拡張子と content_type が一致する通常のアップロードは、これまで通り受け付けられる
171
+ model = TestModelWithJpegAllowlist.new
172
+
173
+ assert_vcr_requests_count 'carrier_wave_test/save', 1 do
174
+ model.resizing_picture = sample_uploaded_file
175
+ end
176
+
177
+ assert model.valid?, model.errors.full_messages.join(', ')
178
+ assert_equal expect_identifier, model.read_attribute(:resizing_picture)
179
+ end
180
+
181
+ # ============================================================
182
+ # Resizing の設計に合わないコールバックが発火しないこと
183
+ # ============================================================
184
+
185
+ def test_versions_are_not_uploaded_again
186
+ # ResizingUploader は version :small を持つが、version は配信時に変換されるので
187
+ # cache_versions! による追加アップロードは発生しない
188
+ model = TestModel.new
189
+
190
+ assert_vcr_requests_count 'carrier_wave_test/save', 1 do
191
+ model.resizing_picture = sample_uploaded_file
192
+ end
193
+
194
+ assert_equal expect_identifier, model.read_attribute(:resizing_picture)
195
+ end
196
+
197
+ def test_processors_are_not_executed_on_upload
198
+ # process は配信 URL の transform 定義なので、uploader のメソッドとしては存在しない。
199
+ # process! が発火すると NoMethodError になる
200
+ model = TestModelWithUndefinedProcessor.new
201
+
202
+ assert_vcr_requests_count 'carrier_wave_test/save', 1 do
203
+ model.resizing_picture = sample_uploaded_file
204
+ end
205
+
206
+ assert_equal expect_identifier, model.read_attribute(:resizing_picture)
207
+ end
208
+
209
+ private
210
+
211
+ # 画像の代入時に POST が発行されず、モデルが integrity error で invalid になることを確認する
212
+ def assert_rejected_before_upload(model_class)
213
+ model = model_class.new
214
+
215
+ assert_vcr_no_requests 'carrier_wave_test/save' do
216
+ model.resizing_picture = sample_uploaded_file
217
+ end
218
+
219
+ refute model.valid?, "#{model_class} should be invalid after the file was rejected"
220
+ refute_empty model.errors[:resizing_picture]
221
+ assert_nil model.read_attribute(:resizing_picture)
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ module Resizing
6
+ # :cache コールバックに渡されるファイル (CallbackFile) の拡張子が content_type から決まることをテストする
7
+ class CarrierWaveCallbackFileTest < Minitest::Test
8
+ # Resizing の公開 URL の最後のセグメント (バージョン文字列)。ドットを含むので
9
+ # ファイル名としては `Fv_Q` が拡張子に見える
10
+ RESIZING_FILENAME = 'v0cRq7Yb2mZt9LxA3wN8eKd5pHs1.Fv_Q'
11
+
12
+ def test_original_filename_is_nil_when_the_file_has_no_name
13
+ file = Resizing::CarrierWave::CallbackFile.new(StringIO.new('data'))
14
+
15
+ assert_nil file.original_filename
16
+ assert_nil file.extension
17
+ end
18
+
19
+ def test_extension_follows_the_content_type
20
+ file = build_file(RESIZING_FILENAME, 'image/jpeg')
21
+
22
+ assert_equal 'v0cRq7Yb2mZt9LxA3wN8eKd5pHs1.jpeg', file.original_filename
23
+ assert_equal 'jpeg', file.extension
24
+ end
25
+
26
+ def test_file_name_is_kept_when_its_extension_belongs_to_the_content_type
27
+ # jpg は image/jpeg の拡張子の一つなので、jpeg に付け替えられない (大文字小文字も区別しない)
28
+ file = build_file('photo.JPG', 'image/jpeg')
29
+
30
+ assert_equal 'photo.JPG', file.original_filename
31
+ assert_equal 'JPG', file.extension
32
+ end
33
+
34
+ def test_file_name_is_kept_when_the_content_type_is_not_registered
35
+ file = build_file('photo.jpg', 'image/x-unregistered-type')
36
+
37
+ assert_equal 'photo.jpg', file.original_filename
38
+ end
39
+
40
+ def test_mini_mime_is_used_when_mime_types_is_not_loaded
41
+ file = build_file(RESIZING_FILENAME, 'image/jpeg')
42
+
43
+ file.stub(:mime_types_available?, false) do
44
+ assert_equal 'v0cRq7Yb2mZt9LxA3wN8eKd5pHs1.jpeg', file.original_filename
45
+ end
46
+ end
47
+
48
+ def test_file_name_is_kept_when_mini_mime_does_not_know_the_content_type
49
+ file = build_file('photo.jpg', 'image/x-unregistered-type')
50
+
51
+ file.stub(:mime_types_available?, false) do
52
+ assert_equal 'photo.jpg', file.original_filename
53
+ end
54
+ end
55
+
56
+ def test_file_name_is_kept_when_no_lookup_library_is_loaded
57
+ file = build_file(RESIZING_FILENAME, 'image/jpeg')
58
+
59
+ file.stub(:mime_types_available?, false) do
60
+ file.stub(:mini_mime_available?, false) do
61
+ assert_equal RESIZING_FILENAME, file.original_filename
62
+ end
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def build_file(filename, content_type)
69
+ uploaded_file = ActionDispatch::Http::UploadedFile.new(
70
+ filename: filename,
71
+ type: content_type,
72
+ tempfile: File.open('test/data/images/sample1.jpg', 'r')
73
+ )
74
+ Resizing::CarrierWave::CallbackFile.new(uploaded_file)
75
+ end
76
+ end
77
+ end