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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'securerandom'
4
+
3
5
  module Resizing
4
6
  class MockClient
5
7
  def post(_file_or_binary, _options = {})
@@ -10,33 +12,47 @@ module Resizing
10
12
  def put(name, _file_or_binary, _options)
11
13
  r = load_yaml('test/vcr/client/put.yml')
12
14
  result = JSON.parse(r['string'])
13
- # replace name, public_id and version by name argument
15
+ # カセット内の値に依存せず、name と新しい version で id / public_id を組み立て直す
16
+ version = generate_version
14
17
  result['id'] = name
15
- result['public_id'].gsub!(/AWEaewfAreaweFAFASfwe/, name)
16
- result['public_id'].gsub!(/v6Ew3HmDAYfb3NMRdLxR45i_gXMbLlGyi/, "v#{Time.now.to_f}")
18
+ result['version'] = version
19
+ result['latest_version_id'] = version
20
+ result['public_id'] = build_public_id(result['public_id'], image_id: name, version: version)
17
21
  result
18
22
  end
19
23
 
20
24
  def delete(name)
21
25
  r = load_yaml('test/vcr/client/delete.yml')
22
26
  result = JSON.parse(r['string'])
23
- # replace name and public_id by name argument
27
+ # カセット内の値に依存せず、name で id / public_id を組み立て直す
24
28
  result['id'] = name
25
- result['public_id'].gsub!(/28c49144-c00d-4cb5-8619-98ce95977b9c/, name)
29
+ result['public_id'] = build_public_id(result['public_id'], image_id: name)
26
30
  result
27
31
  end
28
32
 
29
33
  def metadata(name, _options = {})
30
34
  r = load_yaml('test/vcr/client/metadata.yml')
31
35
  result = JSON.parse(r['string'])
32
- # replace name and public_id by name argument
36
+ # カセット内の値に依存せず、name で id / public_id を組み立て直す
33
37
  result['id'] = name
34
- result['public_id'].gsub!(/bfdaf2b3-7ec5-41f4-9caa-d53247dd9666/, name)
38
+ result['public_id'] = build_public_id(result['public_id'], image_id: name)
35
39
  result
36
40
  end
37
41
 
38
42
  private
39
43
 
44
+ # カセットの public_id を PublicId で分解し、image_id / version を差し替えて組み立て直す
45
+ def build_public_id(public_id, image_id:, version: nil)
46
+ parsed = Resizing::PublicId.new(public_id)
47
+ version ||= parsed.version
48
+ identifier = "/projects/#{parsed.project_id}/upload/images/#{image_id}"
49
+ version.nil? ? identifier : "#{identifier}/v#{version}"
50
+ end
51
+
52
+ def generate_version
53
+ SecureRandom.alphanumeric(32)
54
+ end
55
+
40
56
  def load_yaml(filename)
41
57
  path = "#{library_root}/#{filename}"
42
58
  YAML.load_file(path)['http_interactions'].first['response']['body']
@@ -7,8 +7,9 @@ module Resizing
7
7
  parsed
8
8
  end
9
9
 
10
+ # nil / 空文字 / 空白のみの文字列はいずれも「値なし」として扱う
10
11
  def empty?
11
- @public_id.to_s.empty?
12
+ @public_id.to_s.strip.empty?
12
13
  end
13
14
 
14
15
  def image_id
@@ -39,7 +40,7 @@ module Resizing
39
40
  private
40
41
 
41
42
  def parsed
42
- return nil if @public_id.nil?
43
+ return nil if empty?
43
44
 
44
45
  unless defined? @parsed
45
46
  @parsed = Resizing.separate_public_id(@public_id)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Resizing
4
- VERSION = '1.2.1'
4
+ VERSION = '1.3.1'
5
5
  end
data/lib/resizing.rb CHANGED
@@ -14,7 +14,6 @@ module Resizing
14
14
  autoload :Configuration, 'resizing/configuration'
15
15
  autoload :CarrierWave, 'resizing/carrier_wave'
16
16
  autoload :PublicId, 'resizing/public_id'
17
- autoload :Video, 'resizing/video'
18
17
  autoload :ActiveStorage, 'resizing/active_storage'
19
18
 
20
19
  class Error < StandardError; end
data/resizing.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = 'Client and utilities for Resizing '
13
13
  spec.homepage = 'https://github.com/jksy/resizing-gem'
14
14
  spec.license = 'MIT'
15
- spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0')
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.1.0')
16
16
 
17
17
  spec.metadata['allowed_push_host'] = 'https://rubygems.org'
18
18
 
@@ -26,8 +26,8 @@ Gem::Specification.new do |spec|
26
26
  `git ls-files -z`.split("\x0")
27
27
  end
28
28
  spec.require_paths = ['lib']
29
- spec.add_runtime_dependency 'faraday', '~> 2.3'
30
- spec.add_runtime_dependency 'faraday-multipart'
29
+ spec.add_runtime_dependency 'faraday', '>= 1.0', '< 3'
30
+ spec.add_runtime_dependency 'faraday-multipart', '>= 1.0'
31
31
  spec.add_development_dependency 'carrierwave', '~> 2.2.5'
32
32
  spec.add_development_dependency 'fog-aws'
33
33
  spec.add_development_dependency 'minitest', '~> 5.16'
@@ -1,21 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'test_helper'
4
+ require 'tempfile'
4
5
 
5
6
  module Resizing
6
7
  module CarrierWave
7
8
  module Storage
8
9
  class FileTest < Minitest::Test
10
+ include VCRRequestAssertions
11
+ include FakeApiClientAssertions
12
+ include ResizingTestConfiguration
13
+
9
14
  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
15
+ configure_resizing
19
16
  end
20
17
 
21
18
  def teardown
@@ -165,6 +162,455 @@ module Resizing
165
162
  file.delete
166
163
  end
167
164
  end
165
+
166
+ # ============================================================
167
+ # delete の詳細な挙動 (Resizing::Client を偽クライアントに差し替えて検証)
168
+ # ============================================================
169
+
170
+ IDENTIFIER = '/projects/e06e710d-f026-4dcf-b2c0-eab0de8bb83f/upload/images/14ea7aac-a194-4330-931f-6b562aec413d/v_8c5lEhDB5RT3PZp1Fn5PYGm9YVx_x0e'
171
+ IMAGE_ID = '14ea7aac-a194-4330-931f-6b562aec413d'
172
+ OTHER_IDENTIFIER = '/projects/e06e710d-f026-4dcf-b2c0-eab0de8bb83f/upload/images/new-image-id-2222-2222-222222222222/v2'
173
+ NOT_FOUND_RESPONSE = {
174
+ 'error' => 'ActiveRecord::RecordNotFound',
175
+ 'message' => "Upload::Image##{IMAGE_ID} not found(no record)"
176
+ }.freeze
177
+
178
+ def model_with_column(value)
179
+ model = TestModel.new
180
+ model.send(:write_attribute, :resizing_picture, value)
181
+ model
182
+ end
183
+
184
+ def test_delete_clears_column_when_deleted_image_is_current
185
+ model = model_with_column(IDENTIFIER)
186
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture, IDENTIFIER)
187
+
188
+ with_fake_api_client({ 'id' => IMAGE_ID }) do |fake|
189
+ file.delete
190
+ assert_equal [IMAGE_ID], fake.deleted_ids
191
+ end
192
+
193
+ assert_nil model.read_attribute(:resizing_picture)
194
+ end
195
+
196
+ def test_delete_keeps_column_when_deleting_previous_image_after_update
197
+ # 画像更新時: カラムは新しい画像を指しているが、削除対象は古い画像なのでカラムは消さない
198
+ model = model_with_column(OTHER_IDENTIFIER)
199
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture, IDENTIFIER)
200
+
201
+ with_fake_api_client({ 'id' => IMAGE_ID }) do |fake|
202
+ file.delete
203
+ assert_equal [IMAGE_ID], fake.deleted_ids
204
+ end
205
+
206
+ assert_equal OTHER_IDENTIFIER, model.read_attribute(:resizing_picture)
207
+ end
208
+
209
+ def test_delete_treats_not_found_as_success_and_clears_column
210
+ model = model_with_column(IDENTIFIER)
211
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture, IDENTIFIER)
212
+
213
+ with_fake_api_client(NOT_FOUND_RESPONSE) do
214
+ assert_nil file.delete
215
+ end
216
+
217
+ assert_nil model.read_attribute(:resizing_picture)
218
+ end
219
+
220
+ def test_delete_treats_not_found_of_previous_image_without_clearing_column
221
+ model = model_with_column(OTHER_IDENTIFIER)
222
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture, IDENTIFIER)
223
+
224
+ with_fake_api_client(NOT_FOUND_RESPONSE) { file.delete }
225
+
226
+ assert_equal OTHER_IDENTIFIER, model.read_attribute(:resizing_picture)
227
+ end
228
+
229
+ def test_delete_raises_api_error_when_response_id_does_not_match
230
+ model = model_with_column(IDENTIFIER)
231
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture, IDENTIFIER)
232
+
233
+ with_fake_api_client({ 'id' => 'someone-else' }) do
234
+ assert_raises(Resizing::APIError) { file.delete }
235
+ end
236
+
237
+ # 失敗時はカラムを変更しない
238
+ assert_equal IDENTIFIER, model.read_attribute(:resizing_picture)
239
+ end
240
+
241
+ def test_delete_uses_column_value_when_constructed_without_identifier
242
+ # remove! の経路では識別子なしで File が生成され、モデルのカラム値から削除対象を決める
243
+ model = model_with_column(IDENTIFIER)
244
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture)
245
+
246
+ with_fake_api_client({ 'id' => IMAGE_ID }) do |fake|
247
+ file.delete
248
+ assert_equal [IMAGE_ID], fake.deleted_ids
249
+ end
250
+
251
+ assert_nil model.read_attribute(:resizing_picture)
252
+ end
253
+
254
+ def test_delete_uses_previous_column_value_when_column_has_unsaved_change
255
+ # attribute_was: カラムが変更済み (保存前) の場合は変更前の値の画像を削除する
256
+ model = model_with_column(IDENTIFIER)
257
+ model.save!
258
+ model.send(:write_attribute, :resizing_picture, OTHER_IDENTIFIER)
259
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture)
260
+
261
+ with_fake_api_client({ 'id' => IMAGE_ID }) do |fake|
262
+ file.delete
263
+ assert_equal [IMAGE_ID], fake.deleted_ids
264
+ end
265
+
266
+ # 削除したのは古い画像なので、新しい値はそのまま残る
267
+ assert_equal OTHER_IDENTIFIER, model.read_attribute(:resizing_picture)
268
+ end
269
+
270
+ def test_delete_does_not_write_column_of_destroyed_model
271
+ model = model_with_column(IDENTIFIER)
272
+ model.save!
273
+
274
+ with_fake_api_client({ 'id' => IMAGE_ID }) do |fake|
275
+ ActiveRecord::Base.transaction do
276
+ model.destroy!
277
+ end
278
+ assert model.destroyed?
279
+ assert_equal [IMAGE_ID], fake.deleted_ids
280
+
281
+ # destroy 済み (frozen) のモデルに対して delete しても FrozenError にならない
282
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture, IDENTIFIER)
283
+ file.delete
284
+ end
285
+ end
286
+
287
+ # ============================================================
288
+ # store の詳細な挙動 (Resizing.post を差し替えて送信内容を検証)
289
+ # ============================================================
290
+
291
+ POST_RESPONSE = {
292
+ 'id' => IMAGE_ID,
293
+ 'public_id' => IDENTIFIER,
294
+ 'content_type' => 'image/jpeg'
295
+ }.freeze
296
+
297
+ def capture_post(response = POST_RESPONSE, &block)
298
+ captured = {}
299
+ responder = lambda do |content, options|
300
+ captured[:content] = content
301
+ captured[:options] = options
302
+ response.dup
303
+ end
304
+ Resizing.stub(:post, responder, &block)
305
+ captured
306
+ end
307
+
308
+ def new_storage_file(model = TestModel.new)
309
+ Resizing::CarrierWave::Storage::File.new(model.resizing_picture)
310
+ end
311
+
312
+ def test_store_posts_uploaded_file_with_its_content_type_and_original_filename
313
+ file = new_storage_file
314
+ uploaded_file = ActionDispatch::Http::UploadedFile.new(
315
+ filename: 'my photo.jpg',
316
+ type: 'image/jpeg',
317
+ tempfile: ::File.open('test/data/images/sample1.jpg', 'r')
318
+ )
319
+
320
+ captured = capture_post { file.store(uploaded_file) }
321
+
322
+ assert_equal 'image/jpeg', captured[:options][:content_type]
323
+ assert_equal 'my photo.jpg', captured[:options][:filename]
324
+ assert captured[:content].respond_to?(:read)
325
+ assert_equal 0, captured[:content].pos, 'IO should be rewound before upload'
326
+ end
327
+
328
+ def test_store_guesses_content_type_from_extension_for_plain_file
329
+ file = new_storage_file
330
+ source_file = ::File.open('test/data/images/sample1.jpg', 'r')
331
+ source_file.read(10) # 読み進めた状態でも巻き戻して送る
332
+
333
+ captured = capture_post { file.store(source_file) }
334
+
335
+ assert_equal 'image/jpeg', captured[:options][:content_type]
336
+ assert_equal 'sample1.jpg', captured[:options][:filename]
337
+ assert_equal 0, captured[:content].pos
338
+ end
339
+
340
+ # Issue #88: MIME::Types に登録のない拡張子でも例外にならず、
341
+ # application/octet-stream にフォールバックすること
342
+ def test_store_falls_back_to_octet_stream_for_unknown_extension
343
+ # 前提: この拡張子は MIME::Types に登録されていない
344
+ assert_empty MIME::Types.type_for('bookmark.url')
345
+
346
+ file = new_storage_file
347
+ tempfile = Tempfile.new(['bookmark', '.url'])
348
+ begin
349
+ tempfile.write("[InternetShortcut]\nURL=https://example.com/\n")
350
+ tempfile.rewind
351
+
352
+ captured = capture_post { file.store(tempfile) }
353
+
354
+ assert_equal 'application/octet-stream', captured[:options][:content_type]
355
+ assert_equal ::File.basename(tempfile.path), captured[:options][:filename]
356
+ ensure
357
+ tempfile.close!
358
+ end
359
+ end
360
+
361
+ def test_guess_content_type_falls_back_for_unknown_or_missing_path
362
+ file = new_storage_file
363
+
364
+ assert_equal 'image/jpeg', file.send(:guess_content_type, 'sample1.jpg')
365
+ assert_equal 'application/octet-stream', file.send(:guess_content_type, 'bookmark.url')
366
+ assert_equal 'application/octet-stream', file.send(:guess_content_type, 'no_extension')
367
+ assert_equal 'application/octet-stream', file.send(:guess_content_type, nil)
368
+ end
369
+
370
+ def test_store_sends_basename_only_as_filename
371
+ file = new_storage_file
372
+ uploaded_file = ActionDispatch::Http::UploadedFile.new(
373
+ filename: '/some/dir/photo.jpg',
374
+ type: 'image/jpeg',
375
+ tempfile: ::File.open('test/data/images/sample1.jpg', 'r')
376
+ )
377
+
378
+ captured = capture_post { file.store(uploaded_file) }
379
+
380
+ assert_equal 'photo.jpg', captured[:options][:filename]
381
+ end
382
+
383
+ def test_store_writes_public_id_into_model_column
384
+ model = TestModel.new
385
+ file = new_storage_file(model)
386
+
387
+ capture_post { file.store(::File.open('test/data/images/sample1.jpg', 'r')) }
388
+
389
+ assert_equal IDENTIFIER, model.read_attribute(:resizing_picture)
390
+ assert_equal IDENTIFIER, file.public_id.to_s
391
+ assert_equal IDENTIFIER, file.current_path
392
+ assert_equal IMAGE_ID, file.public_id.image_id
393
+ end
394
+
395
+ def test_store_uses_content_type_from_response
396
+ file = new_storage_file
397
+ response = POST_RESPONSE.merge('content_type' => 'image/webp')
398
+
399
+ capture_post(response) { file.store(::File.open('test/data/images/sample1.jpg', 'r')) }
400
+
401
+ assert_equal 'image/webp', file.content_type
402
+ end
403
+
404
+ def test_store_returns_true
405
+ file = new_storage_file
406
+ result = nil
407
+
408
+ capture_post { result = file.store(::File.open('test/data/images/sample1.jpg', 'r')) }
409
+
410
+ assert_equal true, result
411
+ end
412
+
413
+ def test_store_raises_for_storage_file_instance
414
+ model = TestModel.new
415
+ file = new_storage_file(model)
416
+ other = new_storage_file(model)
417
+
418
+ assert_raises(NotImplementedError) { file.store(other) }
419
+ end
420
+
421
+ # ============================================================
422
+ # current_path / name / public_id
423
+ # ============================================================
424
+
425
+ def test_current_path_falls_back_to_model_column
426
+ model = model_with_column(IDENTIFIER)
427
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture)
428
+
429
+ assert_equal IDENTIFIER, file.current_path
430
+ assert_equal IDENTIFIER, file.path
431
+ end
432
+
433
+ def test_current_path_prefers_identifier_over_model_column
434
+ model = model_with_column(OTHER_IDENTIFIER)
435
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture, IDENTIFIER)
436
+
437
+ assert_equal IDENTIFIER, file.current_path
438
+ end
439
+
440
+ def test_name_reads_from_model_column_not_identifier
441
+ model = model_with_column(OTHER_IDENTIFIER)
442
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture, IDENTIFIER)
443
+
444
+ assert_equal 'new-image-id-2222-2222-222222222222', file.name
445
+ end
446
+
447
+ def test_public_id_exposes_parsed_parts
448
+ model = TestModel.new
449
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture, IDENTIFIER)
450
+
451
+ assert_equal IMAGE_ID, file.public_id.image_id
452
+ assert_equal 'e06e710d-f026-4dcf-b2c0-eab0de8bb83f', file.public_id.project_id
453
+ assert_equal '_8c5lEhDB5RT3PZp1Fn5PYGm9YVx_x0e', file.public_id.version
454
+ end
455
+
456
+ def test_retrieve_replaces_public_id
457
+ model = TestModel.new
458
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture, IDENTIFIER)
459
+
460
+ file.retrieve(OTHER_IDENTIFIER)
461
+
462
+ assert_equal OTHER_IDENTIFIER, file.public_id.to_s
463
+ assert_equal OTHER_IDENTIFIER, file.current_path
464
+ end
465
+
466
+ # ============================================================
467
+ # Issue #91: 未定義の `file` を参照していたメソッド群
468
+ # (size / content_type / read / exists? / attributes)
469
+ # ============================================================
470
+
471
+ # test/vcr/client/metadata.yml のカセットに対応する identifier
472
+ METADATA_IDENTIFIER = '/projects/e06e710d-f026-4dcf-b2c0-eab0de8bb83f/upload/images/87263920-2081-498e-a107-9625f4fde01b/vHg9VFvdI6HRzLFbV495VdwVmHIspLRCo'
473
+ METADATA_SIZE = 848_590
474
+
475
+ def storage_file_for(identifier)
476
+ Resizing::CarrierWave::Storage::File.new(TestModel.new.resizing_picture, identifier)
477
+ end
478
+
479
+ def test_size_content_type_exists_and_attributes_come_from_metadata_api
480
+ file = storage_file_for(METADATA_IDENTIFIER)
481
+
482
+ # メタデータは1回だけ取得され、以降はメモ化される
483
+ assert_vcr_requests_made 'client/metadata' do
484
+ assert_equal METADATA_SIZE, file.size
485
+ assert_equal 'image/jpeg', file.content_type
486
+ assert file.exists?
487
+ assert_equal 'sample1.jpg', file.attributes['filename']
488
+ end
489
+ end
490
+
491
+ def test_metadata_backed_methods_do_not_raise_name_error_without_public_id
492
+ file = new_storage_file
493
+
494
+ # public_id がないのでリクエストは発行されず、既定値を返す
495
+ assert_vcr_no_requests 'client/metadata' do
496
+ assert_equal 0, file.size
497
+ assert_nil file.content_type
498
+ refute file.exists?
499
+ assert_nil file.attributes
500
+ end
501
+ end
502
+
503
+ def test_metadata_backed_methods_do_not_raise_when_api_fails
504
+ file = storage_file_for(IDENTIFIER)
505
+
506
+ Resizing.stub(:metadata, ->(*) { raise Resizing::APIError, 'boom' }) do
507
+ assert_equal 0, file.size
508
+ assert_nil file.content_type
509
+ refute file.exists?
510
+ assert_nil file.attributes
511
+ end
512
+ end
513
+
514
+ def test_metadata_not_found_response_is_treated_as_missing
515
+ file = storage_file_for(IDENTIFIER)
516
+ not_found = { 'error' => 'ActiveRecord::RecordNotFound' }
517
+
518
+ Resizing.stub(:metadata, ->(*) { not_found }) do
519
+ refute file.exists?
520
+ assert_equal 0, file.size
521
+ assert_nil file.content_type
522
+ end
523
+ end
524
+
525
+ def test_content_type_prefers_locally_known_value_over_metadata_api
526
+ file = new_storage_file
527
+
528
+ # store 済みで content_type が分かっている場合はメタデータを取りに行かない
529
+ assert_vcr_no_requests 'client/metadata' do
530
+ capture_post { file.store(::File.open('test/data/images/sample1.jpg', 'r')) }
531
+
532
+ assert_equal 'image/jpeg', file.content_type
533
+ end
534
+ end
535
+
536
+ def test_metadata_falls_back_to_model_column_when_built_without_identifier
537
+ model = model_with_column(METADATA_IDENTIFIER)
538
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture)
539
+
540
+ assert_vcr_requests_made 'client/metadata' do
541
+ assert_equal METADATA_SIZE, file.size
542
+ end
543
+ end
544
+
545
+ def test_retrieve_drops_cached_metadata_only_when_identifier_changes
546
+ file = storage_file_for(METADATA_IDENTIFIER)
547
+
548
+ assert_vcr_requests_made 'client/metadata' do
549
+ assert_equal METADATA_SIZE, file.size
550
+ # 同じ identifier での retrieve ではメタデータを再取得しない
551
+ file.retrieve(METADATA_IDENTIFIER)
552
+ assert_equal METADATA_SIZE, file.size
553
+ end
554
+
555
+ file.retrieve(IDENTIFIER)
556
+ Resizing.stub(:metadata, ->(*) { { 'size' => 1 } }) do
557
+ assert_equal 1, file.size
558
+ end
559
+ end
560
+
561
+ def test_read_raises_not_implemented_error_instead_of_name_error
562
+ file = storage_file_for(METADATA_IDENTIFIER)
563
+
564
+ error = assert_raises(NotImplementedError) { file.read }
565
+ assert_match(/Resizing/, error.message)
566
+ end
567
+
568
+ # Issue #91 の再現手順: DB から読み直したモデルの size が NameError にならないこと
569
+ # (CarrierWave の Uploader::Proxy#size は file.try(:size) を呼ぶ)
570
+ def test_uploader_proxy_size_and_content_type_for_model_loaded_from_db
571
+ model = model_with_column(METADATA_IDENTIFIER)
572
+ model.save!
573
+ loaded = TestModel.find(model.id)
574
+
575
+ assert_vcr_requests_made 'client/metadata' do
576
+ assert_equal METADATA_SIZE, loaded.resizing_picture.size
577
+ assert_equal 'image/jpeg', loaded.resizing_picture.content_type
578
+ end
579
+ end
580
+
581
+ # Issue #94: カラムに空文字が入っているレコードでも delete で例外にならない
582
+ def test_delete_does_nothing_when_column_is_empty_string
583
+ model = model_with_column('')
584
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture)
585
+
586
+ with_fake_api_client({ 'id' => IMAGE_ID }) do |fake|
587
+ assert_nil file.delete
588
+ assert_empty fake.deleted_ids
589
+ end
590
+
591
+ assert_equal '', model.read_attribute(:resizing_picture)
592
+ end
593
+
594
+ def test_delete_does_nothing_when_identifier_is_empty_string
595
+ model = model_with_column('')
596
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture, '')
597
+
598
+ with_fake_api_client({ 'id' => IMAGE_ID }) do |fake|
599
+ assert_nil file.delete
600
+ assert_empty fake.deleted_ids
601
+ end
602
+ end
603
+
604
+ def test_delete_with_valid_public_id_clears_model_column
605
+ VCR.use_cassette 'carrier_wave_test/remove_resizing_picture', record: :none do
606
+ model = model_with_column(IDENTIFIER)
607
+ file = Resizing::CarrierWave::Storage::File.new(model.resizing_picture, IDENTIFIER)
608
+
609
+ file.delete
610
+
611
+ assert_nil model.read_attribute(:resizing_picture)
612
+ end
613
+ end
168
614
  end
169
615
  end
170
616
  end