resizing 1.2.2 → 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.
- checksums.yaml +4 -4
- data/.devcontainer/Dockerfile +73 -0
- data/.devcontainer/compose.yaml +58 -0
- data/.devcontainer/devcontainer-lock.json +9 -0
- data/.devcontainer/devcontainer.json +32 -0
- data/.devcontainer/post-create.sh +41 -0
- data/.github/copilot-instructions.md +1 -0
- data/.github/labeler.yml +28 -0
- data/.github/release.yml +35 -0
- data/.github/workflows/labeler.yml +20 -0
- data/.github/workflows/lint.yml +37 -0
- data/.github/workflows/test.yml +16 -9
- data/AGENTS.md +1 -0
- data/CHANGELOG.md +3 -0
- data/CLAUDE.md +68 -0
- data/Gemfile +9 -3
- data/README.md +114 -2
- data/Rakefile +5 -7
- data/bin/setup +3 -0
- data/git-hooks/pre-push +6 -0
- data/git-hooks/pre-push-ruby.sh +48 -0
- data/lib/resizing/carrier_wave/callback_file.rb +88 -0
- data/lib/resizing/carrier_wave/storage/file.rb +84 -36
- data/lib/resizing/carrier_wave/storage/remote.rb +7 -3
- data/lib/resizing/carrier_wave.rb +41 -6
- data/lib/resizing/client.rb +0 -1
- data/lib/resizing/configurable.rb +1 -1
- data/lib/resizing/configuration.rb +40 -10
- data/lib/resizing/mock_client.rb +23 -7
- data/lib/resizing/public_id.rb +3 -2
- data/lib/resizing/version.rb +1 -1
- data/lib/resizing.rb +0 -1
- data/resizing.gemspec +3 -3
- data/test/resizing/carrier_wave/storage/file_test.rb +455 -9
- data/test/resizing/carrier_wave/storage/remote_test.rb +91 -9
- data/test/resizing/carrier_wave_cache_callbacks_test.rb +224 -0
- data/test/resizing/carrier_wave_callback_file_test.rb +77 -0
- data/test/resizing/carrier_wave_test.rb +201 -26
- data/test/resizing/carrier_wave_uploader_test.rb +211 -0
- data/test/resizing/client_test.rb +324 -25
- data/test/resizing/configuration_test.rb +235 -10
- data/test/resizing/http_clientable_test.rb +27 -0
- data/test/resizing/mock_client_test.rb +111 -1
- data/test/resizing/public_id_test.rb +96 -0
- data/test/resizing_module_test.rb +110 -0
- data/test/resizing_test.rb +8 -0
- data/test/test_helper.rb +297 -9
- metadata +33 -9
|
@@ -4,10 +4,11 @@ require 'test_helper'
|
|
|
4
4
|
|
|
5
5
|
module Resizing
|
|
6
6
|
class ConfigurationTest < Minitest::Test
|
|
7
|
+
ACTIVE_SUPPORT_OBJECT_EXTENSIONS = %i[present? blank? try].freeze
|
|
8
|
+
|
|
7
9
|
def setup
|
|
8
10
|
@template = {
|
|
9
11
|
image_host: 'http://192.168.56.101:5000',
|
|
10
|
-
video_host: 'http://192.168.56.101:5000',
|
|
11
12
|
project_id: '098a2a0d-c387-4135-a071-1254d6d7e70a',
|
|
12
13
|
secret_token: '4g1cshg2lq8j93ufhvqrpjswxmtjz12yhfvq6w79jpwi7cr7nnknoqgwzkwerbs6',
|
|
13
14
|
open_timeout: 10,
|
|
@@ -26,11 +27,13 @@ module Resizing
|
|
|
26
27
|
assert_equal(config.image_host, Resizing::Configuration::DEFAULT_IMAGE_HOST)
|
|
27
28
|
end
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
template.
|
|
32
|
-
|
|
33
|
-
assert_equal(config.video_host
|
|
30
|
+
# 動画 API の廃止にともない video_host は非推奨。値は保持するが参照時に警告を出す
|
|
31
|
+
def test_that_video_host_reader_warns_and_still_returns_the_default
|
|
32
|
+
config = Resizing::Configuration.new @template.dup
|
|
33
|
+
|
|
34
|
+
_out, err = capture_io { assert_equal('https://video.resizing.net', config.video_host) }
|
|
35
|
+
|
|
36
|
+
assert_includes err, Resizing::Configuration::DEPRECATED_VIDEO_HOST_MESSAGE
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
def test_that_it_need_raise_exception_if_host_presented
|
|
@@ -41,16 +44,77 @@ module Resizing
|
|
|
41
44
|
end
|
|
42
45
|
end
|
|
43
46
|
|
|
44
|
-
def
|
|
47
|
+
def test_that_it_does_not_raise_exception_if_host_is_nil
|
|
45
48
|
template = @template.dup
|
|
49
|
+
template[:host] = nil
|
|
46
50
|
config = Resizing::Configuration.new template
|
|
47
51
|
assert_equal(config.image_host, template[:image_host])
|
|
48
52
|
end
|
|
49
53
|
|
|
50
|
-
def
|
|
54
|
+
def test_that_it_does_not_raise_exception_if_host_is_empty_string
|
|
55
|
+
['', ' '].each do |host|
|
|
56
|
+
template = @template.dup
|
|
57
|
+
template[:host] = host
|
|
58
|
+
config = Resizing::Configuration.new template
|
|
59
|
+
assert_equal(config.image_host, template[:image_host])
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Rails 外(ActiveSupport 未ロード)から利用されるため、
|
|
64
|
+
# Configuration は present? / blank? などの ActiveSupport 拡張に依存してはいけない。
|
|
65
|
+
# see: https://github.com/jksy/resizing-gem/issues/87
|
|
66
|
+
def test_that_it_does_not_depend_on_active_support_object_extensions
|
|
67
|
+
without_active_support_object_extensions do
|
|
68
|
+
template = @template.dup
|
|
69
|
+
config = Resizing::Configuration.new template
|
|
70
|
+
assert_equal(config.image_host, template[:image_host])
|
|
71
|
+
|
|
72
|
+
template_with_host = @template.dup
|
|
73
|
+
template_with_host[:host] = 'https://www.resizing.net'
|
|
74
|
+
assert_raises ConfigurationError do
|
|
75
|
+
Resizing::Configuration.new template_with_host
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_that_it_has_same_image_host_value
|
|
51
81
|
template = @template.dup
|
|
52
82
|
config = Resizing::Configuration.new template
|
|
53
|
-
assert_equal(config.
|
|
83
|
+
assert_equal(config.image_host, template[:image_host])
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_that_passing_video_host_warns_but_keeps_the_value
|
|
87
|
+
template = @template.dup
|
|
88
|
+
template[:video_host] = 'http://192.168.56.101:5000'
|
|
89
|
+
|
|
90
|
+
_out, err = capture_io { @config = Resizing::Configuration.new template }
|
|
91
|
+
|
|
92
|
+
assert_includes err, Resizing::Configuration::DEPRECATED_VIDEO_HOST_MESSAGE
|
|
93
|
+
capture_io { assert_equal(template[:video_host], @config.video_host) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# 非推奨だが == の比較対象からは外さない。比較そのものは警告を出さない
|
|
97
|
+
def test_equality_still_compares_the_deprecated_video_host
|
|
98
|
+
base = other = nil
|
|
99
|
+
capture_io do
|
|
100
|
+
base = Resizing::Configuration.new @template.merge(video_host: 'https://a.example.com')
|
|
101
|
+
other = Resizing::Configuration.new @template.merge(video_host: 'https://b.example.com')
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
_out, err = capture_io { refute_equal base, other }
|
|
105
|
+
|
|
106
|
+
assert_empty err
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_that_the_default_video_host_constant_is_deprecated
|
|
110
|
+
previous = Warning[:deprecated]
|
|
111
|
+
Warning[:deprecated] = true
|
|
112
|
+
|
|
113
|
+
_out, err = capture_io { Resizing::Configuration::DEFAULT_VIDEO_HOST }
|
|
114
|
+
|
|
115
|
+
assert_match(/DEFAULT_VIDEO_HOST is deprecated/, err)
|
|
116
|
+
ensure
|
|
117
|
+
Warning[:deprecated] = previous
|
|
54
118
|
end
|
|
55
119
|
|
|
56
120
|
def test_that_it_has_no_project_id
|
|
@@ -69,7 +133,7 @@ module Resizing
|
|
|
69
133
|
|
|
70
134
|
def test_that_it_has_no_secret_token
|
|
71
135
|
template = @template.dup
|
|
72
|
-
template.delete(:
|
|
136
|
+
template.delete(:secret_token)
|
|
73
137
|
assert_raises ConfigurationError do
|
|
74
138
|
Resizing::Configuration.new template
|
|
75
139
|
end
|
|
@@ -263,5 +327,166 @@ module Resizing
|
|
|
263
327
|
|
|
264
328
|
assert_equal true, config.enable_mock
|
|
265
329
|
end
|
|
330
|
+
|
|
331
|
+
def test_that_it_has_default_response_timeout
|
|
332
|
+
template = @template.dup
|
|
333
|
+
template.delete(:response_timeout)
|
|
334
|
+
config = Resizing::Configuration.new template
|
|
335
|
+
assert_equal(Resizing::Configuration::DEFAULT_RESPONSE_TIMEOUT, config.response_timeout)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def test_that_it_has_same_response_timeout
|
|
339
|
+
config = Resizing::Configuration.new @template
|
|
340
|
+
assert_equal(@template[:response_timeout], config.response_timeout)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def test_that_it_raises_when_initialized_without_hash
|
|
344
|
+
assert_raises(ConfigurationError) { Resizing::Configuration.new }
|
|
345
|
+
assert_raises(ConfigurationError) { Resizing::Configuration.new(nil) }
|
|
346
|
+
assert_raises(ConfigurationError) { Resizing::Configuration.new('not a hash') }
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def test_that_it_can_be_initialized_with_only_required_keys
|
|
350
|
+
config = Resizing::Configuration.new(project_id: 'project', secret_token: 'token')
|
|
351
|
+
|
|
352
|
+
assert_equal 'project', config.project_id
|
|
353
|
+
assert_equal 'token', config.secret_token
|
|
354
|
+
assert_equal Resizing::Configuration::DEFAULT_IMAGE_HOST, config.image_host
|
|
355
|
+
assert_equal Resizing::Configuration::DEFAULT_OPEN_TIMEOUT, config.open_timeout
|
|
356
|
+
assert_equal Resizing::Configuration::DEFAULT_RESPONSE_TIMEOUT, config.response_timeout
|
|
357
|
+
assert_equal false, config.enable_mock
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def test_that_string_attributes_are_frozen_copies_of_input
|
|
361
|
+
template = @template.dup
|
|
362
|
+
template[:image_host] = +'http://mutable.host'
|
|
363
|
+
template[:project_id] = +'mutable-project'
|
|
364
|
+
template[:secret_token] = +'mutable-token'
|
|
365
|
+
config = Resizing::Configuration.new template
|
|
366
|
+
|
|
367
|
+
assert config.image_host.frozen?
|
|
368
|
+
assert config.project_id.frozen?
|
|
369
|
+
assert config.secret_token.frozen?
|
|
370
|
+
|
|
371
|
+
# 入力の Hash の文字列を後から変更しても設定値には影響しない
|
|
372
|
+
template[:image_host] << '/changed'
|
|
373
|
+
template[:project_id] << '-changed'
|
|
374
|
+
template[:secret_token] << '-changed'
|
|
375
|
+
assert_equal 'http://mutable.host', config.image_host
|
|
376
|
+
assert_equal 'mutable-project', config.project_id
|
|
377
|
+
assert_equal 'mutable-token', config.secret_token
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def test_that_auth_header_is_version_timestamp_and_sha256_of_timestamp_and_secret_token
|
|
381
|
+
# サーバー側 (ResizingTokenAuthenticator) は "v1,<unix time>,<sha256>" 形式を期待している
|
|
382
|
+
now = Time.parse('2024-01-02 03:04:05 UTC')
|
|
383
|
+
Timecop.freeze(now) do
|
|
384
|
+
config = Resizing::Configuration.new @template
|
|
385
|
+
header = config.generate_auth_header
|
|
386
|
+
|
|
387
|
+
assert_match(/\Av\d,\d+,[0-9a-f]{64}\z/, header)
|
|
388
|
+
|
|
389
|
+
version, timestamp, token = header.split(',')
|
|
390
|
+
assert_equal 'v1', version
|
|
391
|
+
assert_equal now.to_i.to_s, timestamp
|
|
392
|
+
assert_equal Digest::SHA2.hexdigest("#{now.to_i}|#{@template[:secret_token]}"), token
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def test_that_auth_header_changes_with_time
|
|
397
|
+
config = Resizing::Configuration.new @template
|
|
398
|
+
first = Timecop.freeze(Time.parse('2024-01-01 00:00:00 UTC')) { config.generate_auth_header }
|
|
399
|
+
second = Timecop.freeze(Time.parse('2024-01-01 00:00:01 UTC')) { config.generate_auth_header }
|
|
400
|
+
|
|
401
|
+
refute_equal first, second
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def test_that_it_return_image_url_with_transformations
|
|
405
|
+
config = Resizing::Configuration.new @template
|
|
406
|
+
assert_equal(
|
|
407
|
+
'http://192.168.56.101:5000/projects/098a2a0d-c387-4135-a071-1254d6d7e70a/upload/images/some-image-id/w_100,h_200',
|
|
408
|
+
config.generate_image_url('some-image-id', nil, [{ w: 100, h: 200 }])
|
|
409
|
+
)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def test_that_it_return_image_url_with_version_id_and_transformations
|
|
413
|
+
config = Resizing::Configuration.new @template
|
|
414
|
+
assert_equal(
|
|
415
|
+
'http://192.168.56.101:5000/projects/098a2a0d-c387-4135-a071-1254d6d7e70a/upload/images/some-image-id/vversion-id/w_40,h_40,c_fill/f_webp',
|
|
416
|
+
config.generate_image_url('some-image-id', 'version-id', [{ c: 'fill', w: 40, h: 40 }, { f: 'webp' }])
|
|
417
|
+
)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def test_that_it_return_image_url_accepts_single_hash_transformation
|
|
421
|
+
config = Resizing::Configuration.new @template
|
|
422
|
+
assert_equal(
|
|
423
|
+
'http://192.168.56.101:5000/projects/098a2a0d-c387-4135-a071-1254d6d7e70a/upload/images/some-image-id/w_100',
|
|
424
|
+
config.generate_image_url('some-image-id', nil, { w: 100 })
|
|
425
|
+
)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def test_that_it_return_image_url_without_trailing_slash_for_empty_transformation
|
|
429
|
+
config = Resizing::Configuration.new @template
|
|
430
|
+
expected = 'http://192.168.56.101:5000/projects/098a2a0d-c387-4135-a071-1254d6d7e70a/upload/images/some-image-id'
|
|
431
|
+
|
|
432
|
+
assert_equal expected, config.generate_image_url('some-image-id', nil, [])
|
|
433
|
+
assert_equal expected, config.generate_image_url('some-image-id', nil, [{}])
|
|
434
|
+
assert_equal expected, config.generate_image_url('some-image-id', nil, { unknown: 1 })
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def test_transformation_path_normalizes_key_order_to_transform_options_order
|
|
438
|
+
# Hash#slice は引数の順で返すため、入力順に関係なく TRANSFORM_OPTIONS の順 (w, h, f, c, q) になる
|
|
439
|
+
config = Resizing::Configuration.new @template
|
|
440
|
+
|
|
441
|
+
assert_equal 'w_100,h_200', config.transformation_path(h: 200, w: 100)
|
|
442
|
+
assert_equal 'w_100,h_200', config.transformation_path(w: 100, h: 200)
|
|
443
|
+
assert_equal 'w_40,h_40,f_webp,c_fill,q_80', config.transformation_path(q: 80, c: 'fill', f: 'webp', h: 40, w: 40)
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def test_transformation_path_with_quality
|
|
447
|
+
config = Resizing::Configuration.new @template
|
|
448
|
+
|
|
449
|
+
assert_equal 'q_80', config.transformation_path(q: 80)
|
|
450
|
+
assert_equal 'w_100,q_auto', config.transformation_path(w: 100, q: 'auto')
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
def test_equality_returns_false_when_any_compared_attribute_differs
|
|
454
|
+
base = Resizing::Configuration.new @template
|
|
455
|
+
{
|
|
456
|
+
secret_token: 'different-token',
|
|
457
|
+
open_timeout: 99,
|
|
458
|
+
response_timeout: 99
|
|
459
|
+
}.each do |key, value|
|
|
460
|
+
other = Resizing::Configuration.new @template.merge(key => value)
|
|
461
|
+
refute_equal base, other, "#{key} should be compared by =="
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
private
|
|
466
|
+
|
|
467
|
+
def without_active_support_object_extensions
|
|
468
|
+
removed = each_active_support_extension.map do |klass, method_name|
|
|
469
|
+
klass.send(:alias_method, backup_name(method_name), method_name)
|
|
470
|
+
klass.send(:undef_method, method_name)
|
|
471
|
+
[klass, method_name]
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
yield
|
|
475
|
+
ensure
|
|
476
|
+
removed&.each do |klass, method_name|
|
|
477
|
+
klass.send(:alias_method, method_name, backup_name(method_name))
|
|
478
|
+
klass.send(:remove_method, backup_name(method_name))
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def each_active_support_extension
|
|
483
|
+
[NilClass, String].product(ACTIVE_SUPPORT_OBJECT_EXTENSIONS).select do |klass, method_name|
|
|
484
|
+
klass.method_defined?(method_name)
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
def backup_name(method_name)
|
|
489
|
+
:"__resizing_test_original_#{method_name}"
|
|
490
|
+
end
|
|
266
491
|
end
|
|
267
492
|
end
|
|
@@ -80,5 +80,32 @@ module Resizing
|
|
|
80
80
|
assert_includes e.message, 'TimeoutError'
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
|
+
|
|
84
|
+
def test_http_client_uses_multipart_and_url_encoded_request_middleware
|
|
85
|
+
handlers = @client.http_client.builder.handlers
|
|
86
|
+
|
|
87
|
+
assert_includes handlers, Faraday::Multipart::Middleware
|
|
88
|
+
assert_includes handlers, Faraday::Request::UrlEncoded
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_handle_faraday_error_does_not_rescue_other_faraday_errors
|
|
92
|
+
# TimeoutError 以外の Faraday エラーはそのまま伝播する (現状の仕様)
|
|
93
|
+
assert_raises Faraday::ConnectionFailed do
|
|
94
|
+
@client.handle_faraday_error { raise Faraday::ConnectionFailed, 'connection refused' }
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def test_handle_faraday_error_does_not_rescue_non_faraday_errors
|
|
99
|
+
assert_raises RuntimeError do
|
|
100
|
+
@client.handle_faraday_error { raise 'unexpected' }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def test_handle_timeout_error_message_includes_original_message
|
|
105
|
+
error = Faraday::TimeoutError.new('execution expired')
|
|
106
|
+
|
|
107
|
+
raised = assert_raises(Resizing::APIError) { @client.handle_timeout_error(error) }
|
|
108
|
+
assert_includes raised.message, 'execution expired'
|
|
109
|
+
end
|
|
83
110
|
end
|
|
84
111
|
end
|
|
@@ -34,9 +34,50 @@ module Resizing
|
|
|
34
34
|
assert_includes result['public_id'], name
|
|
35
35
|
assert result.key?('latest_version_id')
|
|
36
36
|
assert result.key?('latest_etag')
|
|
37
|
+
|
|
38
|
+
# id と public_id の image_id が一致していること
|
|
39
|
+
public_id = Resizing::PublicId.new(result['public_id'])
|
|
40
|
+
assert_equal name, public_id.image_id
|
|
41
|
+
# カセットに含まれる元の image_id が残っていないこと
|
|
42
|
+
refute_includes result['public_id'], 'AWEaewfAreaweFAFASfwe'
|
|
43
|
+
# version がカセットの固定値から更新されていること
|
|
44
|
+
refute_equal 'fztekhN_WoeXo8ZkCZ4i5jcQvmPpZewR', result['version']
|
|
45
|
+
refute_includes result['public_id'], 'vfztekhN_WoeXo8ZkCZ4i5jcQvmPpZewR'
|
|
46
|
+
# version / latest_version_id / public_id の version が揃っていること
|
|
47
|
+
assert_equal result['version'], public_id.version
|
|
48
|
+
assert_equal result['latest_version_id'], public_id.version
|
|
37
49
|
end
|
|
38
50
|
end
|
|
39
51
|
|
|
52
|
+
def test_put_generates_new_version_for_each_call
|
|
53
|
+
name = 'test-image-123'
|
|
54
|
+
first = @client.put(name, nil, {})
|
|
55
|
+
second = @client.put(name, nil, {})
|
|
56
|
+
|
|
57
|
+
refute_equal first['version'], second['version']
|
|
58
|
+
refute_equal first['public_id'], second['public_id']
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_metadata_returns_public_id_for_given_name
|
|
62
|
+
name = 'metadata-test-image'
|
|
63
|
+
result = @client.metadata(name)
|
|
64
|
+
public_id = Resizing::PublicId.new(result['public_id'])
|
|
65
|
+
|
|
66
|
+
assert_equal result['id'], public_id.image_id
|
|
67
|
+
assert_equal name, public_id.image_id
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_delete_keeps_version_from_cassette
|
|
71
|
+
name = 'delete-test-image'
|
|
72
|
+
result = @client.delete(name)
|
|
73
|
+
public_id = Resizing::PublicId.new(result['public_id'])
|
|
74
|
+
|
|
75
|
+
assert_equal name, public_id.image_id
|
|
76
|
+
assert_equal result['project_id'], public_id.project_id
|
|
77
|
+
assert_equal result['latest_version_id'], public_id.version
|
|
78
|
+
refute_includes result['public_id'], '28c49144-c00d-4cb5-8619-98ce95977b9c'
|
|
79
|
+
end
|
|
80
|
+
|
|
40
81
|
def test_delete_returns_parsed_json_with_modified_name
|
|
41
82
|
VCR.use_cassette('client/delete', record: :none) do
|
|
42
83
|
name = 'delete-test-image'
|
|
@@ -55,8 +96,17 @@ module Resizing
|
|
|
55
96
|
|
|
56
97
|
assert_instance_of Hash, result
|
|
57
98
|
assert_equal name, result['id']
|
|
58
|
-
# The cassette contains a fixed public_id, so we just check it exists
|
|
59
99
|
assert result.key?('public_id')
|
|
100
|
+
|
|
101
|
+
# id と public_id の image_id が一致していること
|
|
102
|
+
public_id = Resizing::PublicId.new(result['public_id'])
|
|
103
|
+
assert_equal name, public_id.image_id
|
|
104
|
+
assert_equal result['id'], public_id.image_id
|
|
105
|
+
# カセットに含まれる元の image_id が残っていないこと
|
|
106
|
+
refute_includes result['public_id'], '87263920-2081-498e-a107-9625f4fde01b'
|
|
107
|
+
# project_id と version はカセットの値を引き継ぐこと
|
|
108
|
+
assert_equal result['project_id'], public_id.project_id
|
|
109
|
+
assert_equal result['version'], public_id.version
|
|
60
110
|
end
|
|
61
111
|
end
|
|
62
112
|
|
|
@@ -71,5 +121,65 @@ module Resizing
|
|
|
71
121
|
assert result['latest_etag'].is_a?(String)
|
|
72
122
|
end
|
|
73
123
|
end
|
|
124
|
+
|
|
125
|
+
def test_mock_client_does_not_perform_http_requests
|
|
126
|
+
# VCR はカセット外の HTTP 接続を禁止しているため、カセットなしで成功すれば通信していないことが分かる
|
|
127
|
+
post_result = @client.post('test/data/images/sample1.jpg', content_type: 'image/jpeg')
|
|
128
|
+
assert_equal '87263920-2081-498e-a107-9625f4fde01b', post_result['id']
|
|
129
|
+
|
|
130
|
+
refute_nil @client.put('img', nil, {})
|
|
131
|
+
refute_nil @client.delete('img')
|
|
132
|
+
refute_nil @client.metadata('img')
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def test_post_returns_public_id_parsable_by_public_id
|
|
136
|
+
result = @client.post(nil)
|
|
137
|
+
public_id = Resizing::PublicId.new(result['public_id'])
|
|
138
|
+
|
|
139
|
+
assert_equal result['id'], public_id.image_id
|
|
140
|
+
assert_equal result['project_id'], public_id.project_id
|
|
141
|
+
assert_equal result['latest_version_id'], public_id.version
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def test_put_returns_public_id_for_given_name
|
|
145
|
+
name = 'test-image-123'
|
|
146
|
+
result = @client.put(name, nil, {})
|
|
147
|
+
public_id = Resizing::PublicId.new(result['public_id'])
|
|
148
|
+
|
|
149
|
+
assert_equal name, public_id.image_id
|
|
150
|
+
refute_nil public_id.version
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def test_delete_returns_public_id_for_given_name
|
|
154
|
+
name = 'delete-test-image'
|
|
155
|
+
result = @client.delete(name)
|
|
156
|
+
public_id = Resizing::PublicId.new(result['public_id'])
|
|
157
|
+
|
|
158
|
+
assert_equal name, public_id.image_id
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def test_each_call_returns_independent_result
|
|
162
|
+
first = @client.post(nil)
|
|
163
|
+
second = @client.post(nil)
|
|
164
|
+
|
|
165
|
+
refute_same first, second
|
|
166
|
+
first['id'] = 'changed'
|
|
167
|
+
first['public_id'] << '/changed'
|
|
168
|
+
assert_equal '87263920-2081-498e-a107-9625f4fde01b', second['id']
|
|
169
|
+
refute_includes second['public_id'], 'changed'
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def test_mock_client_is_selected_by_resizing_module_when_enable_mock
|
|
173
|
+
Resizing.configure = {
|
|
174
|
+
image_host: 'https://img.example.com',
|
|
175
|
+
project_id: 'project123',
|
|
176
|
+
secret_token: 'token123',
|
|
177
|
+
enable_mock: true
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
result = Resizing.post('test/data/images/sample1.jpg', content_type: 'image/jpeg')
|
|
181
|
+
|
|
182
|
+
assert_equal '87263920-2081-498e-a107-9625f4fde01b', result['id']
|
|
183
|
+
end
|
|
74
184
|
end
|
|
75
185
|
end
|
|
@@ -37,5 +37,101 @@ module Resizing
|
|
|
37
37
|
public_id = Resizing::PublicId.new @public_id_as_string
|
|
38
38
|
assert_equal @public_id_as_string, public_id.to_s
|
|
39
39
|
end
|
|
40
|
+
|
|
41
|
+
def test_empty_is_false_for_valid_public_id
|
|
42
|
+
public_id = Resizing::PublicId.new @public_id_as_string
|
|
43
|
+
refute public_id.empty?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_filename_returns_image_id
|
|
47
|
+
public_id = Resizing::PublicId.new @public_id_as_string
|
|
48
|
+
assert_equal @image_id, public_id.filename
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_version_is_nil_when_public_id_has_no_version
|
|
52
|
+
public_id = Resizing::PublicId.new "/projects/#{@project_id}/upload/images/#{@image_id}"
|
|
53
|
+
|
|
54
|
+
assert_nil public_id.version
|
|
55
|
+
assert_equal @image_id, public_id.image_id
|
|
56
|
+
assert_equal @project_id, public_id.project_id
|
|
57
|
+
assert_equal "/projects/#{@project_id}/upload/images/#{@image_id}", public_id.identifier
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_version_may_start_with_underscore
|
|
61
|
+
# 実際の API が返すバージョン ID の形式 (test/vcr/carrier_wave_test/save.yml より)
|
|
62
|
+
public_id = Resizing::PublicId.new "/projects/#{@project_id}/upload/images/#{@image_id}/v_8c5lEhDB5RT3PZp1Fn5PYGm9YVx_x0e"
|
|
63
|
+
|
|
64
|
+
assert_equal '_8c5lEhDB5RT3PZp1Fn5PYGm9YVx_x0e', public_id.version
|
|
65
|
+
assert_equal @image_id, public_id.image_id
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def test_nil_public_id_is_empty_and_has_no_parts
|
|
69
|
+
public_id = Resizing::PublicId.new nil
|
|
70
|
+
|
|
71
|
+
assert public_id.empty?
|
|
72
|
+
assert_nil public_id.image_id
|
|
73
|
+
assert_nil public_id.project_id
|
|
74
|
+
assert_nil public_id.version
|
|
75
|
+
assert_equal '', public_id.to_s
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_empty_string_public_id_is_empty_and_has_no_parts
|
|
79
|
+
public_id = Resizing::PublicId.new ''
|
|
80
|
+
|
|
81
|
+
assert public_id.empty?
|
|
82
|
+
assert_nil public_id.image_id
|
|
83
|
+
assert_nil public_id.project_id
|
|
84
|
+
assert_nil public_id.version
|
|
85
|
+
assert_equal '', public_id.to_s
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def test_empty_string_public_id_does_not_raise
|
|
89
|
+
# Issue #94: nil は許容されるのに空文字は RuntimeError になっていた
|
|
90
|
+
Resizing::PublicId.new ''
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_empty_string_public_id_behaves_same_as_nil
|
|
94
|
+
from_empty_string = Resizing::PublicId.new ''
|
|
95
|
+
from_nil = Resizing::PublicId.new nil
|
|
96
|
+
|
|
97
|
+
parts = ->(public_id) { [public_id.empty?, public_id.image_id, public_id.project_id, public_id.version, public_id.to_s] }
|
|
98
|
+
|
|
99
|
+
assert_equal parts.call(from_nil), parts.call(from_empty_string)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def test_whitespace_only_public_id_is_treated_as_empty
|
|
103
|
+
public_id = Resizing::PublicId.new " \t\n "
|
|
104
|
+
|
|
105
|
+
assert public_id.empty?
|
|
106
|
+
assert_nil public_id.image_id
|
|
107
|
+
assert_nil public_id.project_id
|
|
108
|
+
assert_nil public_id.version
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def test_invalid_public_id_raises_error
|
|
112
|
+
assert_raises(RuntimeError) { Resizing::PublicId.new 'not/a/public/id' }
|
|
113
|
+
assert_raises(RuntimeError) { Resizing::PublicId.new "/projects/#{@project_id}/upload/videos/#{@image_id}" }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def test_project_id_must_consist_of_hex_digits_and_hyphens
|
|
117
|
+
# public_id の project_id 部分は UUID 形式 ([0-9a-f-]+) のみ受け付ける
|
|
118
|
+
assert_raises(RuntimeError) { Resizing::PublicId.new "/projects/project123/upload/images/#{@image_id}" }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def test_image_id_accepts_non_uuid_value
|
|
122
|
+
public_id = Resizing::PublicId.new "/projects/#{@project_id}/upload/images/AWEaewfAreaweFAFASfwe/vabc"
|
|
123
|
+
|
|
124
|
+
assert_equal 'AWEaewfAreaweFAFASfwe', public_id.image_id
|
|
125
|
+
assert_equal 'abc', public_id.version
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def test_parses_public_id_embedded_in_full_url
|
|
129
|
+
# match はアンカーなしのため image_host 付きの URL からも各要素を取り出せる
|
|
130
|
+
public_id = Resizing::PublicId.new "https://img.resizing.net#{@public_id_as_string}"
|
|
131
|
+
|
|
132
|
+
assert_equal @project_id, public_id.project_id
|
|
133
|
+
assert_equal @image_id, public_id.image_id
|
|
134
|
+
assert_equal @version, public_id.version
|
|
135
|
+
end
|
|
40
136
|
end
|
|
41
137
|
end
|
|
@@ -203,4 +203,114 @@ class ResizingModuleTest < Minitest::Test
|
|
|
203
203
|
assert_instance_of Hash, result
|
|
204
204
|
assert_equal 'image_id', result['id']
|
|
205
205
|
end
|
|
206
|
+
|
|
207
|
+
def test_post_delegates_to_client
|
|
208
|
+
Resizing.configure = {
|
|
209
|
+
image_host: 'https://img.example.com',
|
|
210
|
+
project_id: 'project123',
|
|
211
|
+
secret_token: 'token123',
|
|
212
|
+
enable_mock: true
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
result = Resizing.post('test/data/images/sample1.jpg', content_type: 'image/jpeg')
|
|
216
|
+
|
|
217
|
+
assert_instance_of Hash, result
|
|
218
|
+
assert_equal '87263920-2081-498e-a107-9625f4fde01b', result['id']
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def test_client_raises_configuration_error_when_not_configured
|
|
222
|
+
assert_raises(Resizing::ConfigurationError) { Resizing.client }
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def test_api_methods_raise_configuration_error_when_not_configured
|
|
226
|
+
assert_raises(Resizing::ConfigurationError) { Resizing.post('dummy', content_type: 'image/jpeg') }
|
|
227
|
+
assert_raises(Resizing::ConfigurationError) { Resizing.put('id', 'dummy', content_type: 'image/jpeg') }
|
|
228
|
+
assert_raises(Resizing::ConfigurationError) { Resizing.delete('id') }
|
|
229
|
+
assert_raises(Resizing::ConfigurationError) { Resizing.metadata('id', {}) }
|
|
230
|
+
assert_raises(Resizing::ConfigurationError) { Resizing.url_from_image_id('id') }
|
|
231
|
+
assert_raises(Resizing::ConfigurationError) { Resizing.generate_identifier }
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def test_configure_setter_raises_for_invalid_hash
|
|
235
|
+
assert_raises(Resizing::ConfigurationError) { Resizing.configure = { project_id: 'only-project' } }
|
|
236
|
+
assert_raises(Resizing::ConfigurationError) { Resizing.configure = { secret_token: 'only-token' } }
|
|
237
|
+
assert_raises(Resizing::ConfigurationError) do
|
|
238
|
+
Resizing.configure = { project_id: 'p', secret_token: 's', host: 'deprecated-host' }
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def test_configure_setter_replaces_previous_configuration
|
|
243
|
+
Resizing.configure = { project_id: 'first', secret_token: 'token' }
|
|
244
|
+
Resizing.configure = { project_id: 'second', secret_token: 'token' }
|
|
245
|
+
|
|
246
|
+
assert_equal 'second', Resizing.configure.project_id
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def test_client_reflects_latest_configuration
|
|
250
|
+
Resizing.configure = { project_id: 'p', secret_token: 's', enable_mock: false }
|
|
251
|
+
assert_instance_of Resizing::Client, Resizing.client
|
|
252
|
+
|
|
253
|
+
Resizing.configure = { project_id: 'p', secret_token: 's', enable_mock: true }
|
|
254
|
+
assert_instance_of Resizing::MockClient, Resizing.client
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def test_separate_public_id_extracts_project_id_image_id_and_version
|
|
258
|
+
matched = Resizing.separate_public_id(
|
|
259
|
+
'/projects/098a2a0d-c387-4135-a071-1254d6d7e70a/upload/images/28c49144-c00d-4cb5-8619-98ce95977b9c/v1Id850q34fgsaer23w'
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
assert_equal '098a2a0d-c387-4135-a071-1254d6d7e70a', matched[:project_id]
|
|
263
|
+
assert_equal '28c49144-c00d-4cb5-8619-98ce95977b9c', matched[:image_id]
|
|
264
|
+
assert_equal '1Id850q34fgsaer23w', matched[:version]
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def test_separate_public_id_without_version
|
|
268
|
+
matched = Resizing.separate_public_id(
|
|
269
|
+
'/projects/098a2a0d-c387-4135-a071-1254d6d7e70a/upload/images/28c49144-c00d-4cb5-8619-98ce95977b9c'
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
assert_equal '28c49144-c00d-4cb5-8619-98ce95977b9c', matched[:image_id]
|
|
273
|
+
assert_nil matched[:version]
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def test_separate_public_id_returns_nil_for_unrelated_string
|
|
277
|
+
assert_nil Resizing.separate_public_id('/foo/bar')
|
|
278
|
+
assert_nil Resizing.separate_public_id('')
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def test_generate_identifier_returns_new_value_each_time
|
|
282
|
+
Resizing.configure = { project_id: '098a2a0d-c387-4135-a071-1254d6d7e70a', secret_token: 'token' }
|
|
283
|
+
|
|
284
|
+
first = Resizing.generate_identifier
|
|
285
|
+
second = Resizing.generate_identifier
|
|
286
|
+
|
|
287
|
+
refute_equal first, second
|
|
288
|
+
# 生成した identifier は PublicId として解釈できる
|
|
289
|
+
assert_equal '098a2a0d-c387-4135-a071-1254d6d7e70a', Resizing::PublicId.new(first).project_id
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def test_url_from_image_id_uses_default_image_host_when_not_specified
|
|
293
|
+
Resizing.configure = { project_id: 'project123', secret_token: 'token123' }
|
|
294
|
+
|
|
295
|
+
assert_equal 'https://img.resizing.net/projects/project123/upload/images/image456', Resizing.url_from_image_id('image456')
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Issue #95: 実体のないファイルを指す autoload が残っていると
|
|
299
|
+
# その定数を参照した瞬間に LoadError になるため、
|
|
300
|
+
# 全 autoload 先が実際にロードできることを確認する
|
|
301
|
+
def test_all_autoload_targets_are_loadable
|
|
302
|
+
source = File.read(File.expand_path('../lib/resizing.rb', __dir__))
|
|
303
|
+
const_names = source.scan(/autoload\s+:(\w+),/).flatten
|
|
304
|
+
|
|
305
|
+
refute_empty const_names
|
|
306
|
+
|
|
307
|
+
const_names.each do |const_name|
|
|
308
|
+
assert Resizing.const_get(const_name), "Resizing::#{const_name} should be loadable"
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def test_video_constant_is_not_registered
|
|
313
|
+
refute_includes Resizing.constants, :Video
|
|
314
|
+
assert_raises(NameError) { Resizing.const_get(:Video) }
|
|
315
|
+
end
|
|
206
316
|
end
|
data/test/resizing_test.rb
CHANGED
|
@@ -6,4 +6,12 @@ class ResizingTest < Minitest::Test
|
|
|
6
6
|
def test_that_it_has_a_version_number
|
|
7
7
|
refute_nil ::Resizing::VERSION
|
|
8
8
|
end
|
|
9
|
+
|
|
10
|
+
def test_version_follows_semantic_versioning
|
|
11
|
+
assert_match(/\A\d+\.\d+\.\d+(\.pre)?\z/, ::Resizing::VERSION)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_version_is_frozen_string
|
|
15
|
+
assert ::Resizing::VERSION.frozen?
|
|
16
|
+
end
|
|
9
17
|
end
|