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
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ module Resizing
6
+ # Resizing::CarrierWave を include したアップローダ単体の挙動をテストする
7
+ # (モデルの保存・削除を伴う結合テストは carrier_wave_test.rb を参照)
8
+ class CarrierWaveUploaderTest < Minitest::Test
9
+ include VCRRequestAssertions
10
+ include ResizingTestConfiguration
11
+
12
+ IDENTIFIER = ResizingTestConfiguration::UPLOADED_IDENTIFIER
13
+
14
+ class TransformationUploader < ::CarrierWave::Uploader::Base
15
+ include Resizing::CarrierWave
16
+
17
+ process resize_to_fit: [100, 200]
18
+ process transformation: { quality: 80, fetch_format: 'webp' }
19
+ end
20
+
21
+ class FormatOverridesFetchFormatUploader < ::CarrierWave::Uploader::Base
22
+ include Resizing::CarrierWave
23
+
24
+ process transformation: { fetch_format: 'webp', format: 'png' }
25
+ end
26
+
27
+ class UnsupportedProcessorUploader < ::CarrierWave::Uploader::Base
28
+ include Resizing::CarrierWave
29
+
30
+ process convert: 'png'
31
+ end
32
+
33
+ class NoProcessorUploader < ::CarrierWave::Uploader::Base
34
+ include Resizing::CarrierWave
35
+ end
36
+
37
+ def setup
38
+ configure_resizing
39
+ end
40
+
41
+ def teardown
42
+ # NOP
43
+ end
44
+
45
+ def model_with_image(model_class = TestModel, identifier = IDENTIFIER)
46
+ model = model_class.new
47
+ model.send(:write_attribute, :resizing_picture, identifier)
48
+ model
49
+ end
50
+
51
+ # ============================================================
52
+ # include 時の設定
53
+ # ============================================================
54
+
55
+ def test_including_module_sets_remote_storage
56
+ assert_equal Resizing::CarrierWave::Storage::Remote, NoProcessorUploader.storage
57
+ assert_instance_of Resizing::CarrierWave::Storage::Remote, NoProcessorUploader.new.send(:storage)
58
+ end
59
+
60
+ def test_railtie_is_defined
61
+ assert Resizing::CarrierWave::Railtie < ::Rails::Railtie
62
+ end
63
+
64
+ # ============================================================
65
+ # transform_string
66
+ # ============================================================
67
+
68
+ def test_transform_string_for_resize_to_limit_without_height
69
+ assert_equal 'c_limit,w_1000', ResizingUploader.new.transform_string
70
+ end
71
+
72
+ def test_transform_string_for_version_with_resize_to_fill
73
+ assert_equal 'c_fill,w_40,h_40', TestModel.new.resizing_picture.versions[:small].transform_string
74
+ end
75
+
76
+ def test_transform_string_joins_multiple_processors_with_slash
77
+ assert_equal 'c_fit,w_100,h_200/q_80,f_webp', TransformationUploader.new.transform_string
78
+ end
79
+
80
+ def test_transform_string_format_takes_precedence_over_fetch_format
81
+ assert_equal 'f_png', FormatOverridesFetchFormatUploader.new.transform_string
82
+ end
83
+
84
+ def test_transform_string_is_empty_without_processors
85
+ assert_equal '', NoProcessorUploader.new.transform_string
86
+ end
87
+
88
+ def test_transform_string_raises_for_unsupported_processor
89
+ error = assert_raises(NotImplementedError) { UnsupportedProcessorUploader.new.transform_string }
90
+ assert_includes error.message, 'convert'
91
+ end
92
+
93
+ # ============================================================
94
+ # url
95
+ # ============================================================
96
+
97
+ def test_url_is_built_from_configured_image_host_and_column_value
98
+ model = model_with_image
99
+
100
+ assert_equal "http://192.168.56.101:5000#{IDENTIFIER}/", model.resizing_picture.url
101
+
102
+ # image_host を差し替えると URL も追従する (保存済みカラム値にはホストを含まない)
103
+ configure_resizing(image_host: 'https://cdn.example.com')
104
+ assert_equal "https://cdn.example.com#{IDENTIFIER}/", model.resizing_picture.url
105
+ end
106
+
107
+ def test_url_with_version_appends_transform_string
108
+ model = model_with_image
109
+
110
+ assert_equal "http://192.168.56.101:5000#{IDENTIFIER}/c_fill,w_40,h_40", model.resizing_picture.url(:small)
111
+ end
112
+
113
+ def test_url_accepts_string_version_name
114
+ model = model_with_image
115
+
116
+ assert_equal model.resizing_picture.url(:small), model.resizing_picture.url('small')
117
+ end
118
+
119
+ def test_url_raises_for_unknown_version
120
+ model = model_with_image
121
+
122
+ error = assert_raises(RuntimeError) { model.resizing_picture.url(:nope) }
123
+ assert_includes error.message, 'nope'
124
+ assert_includes error.message, 'small'
125
+ end
126
+
127
+ def test_url_returns_default_url_when_column_is_blank
128
+ assert_nil TestModel.new.resizing_picture.url
129
+ assert_equal 'http://example.com/test.jpg', TestModelWithDefaultURL.new.resizing_picture.url
130
+ end
131
+
132
+ def test_url_ignores_version_when_column_is_blank
133
+ # カラムが空なら version 指定があっても default_url を返す
134
+ assert_nil TestModel.new.resizing_picture.url(:small)
135
+ end
136
+
137
+ def test_model_url_helper_delegates_to_uploader_url
138
+ model = model_with_image
139
+
140
+ assert_equal model.resizing_picture.url, model.resizing_picture_url
141
+ assert_equal model.resizing_picture.url(:small), model.resizing_picture_url(:small)
142
+ end
143
+
144
+ # ============================================================
145
+ # identifier / filename / file
146
+ # ============================================================
147
+
148
+ def test_filename_and_identifier_return_column_value
149
+ model = model_with_image
150
+
151
+ assert_equal IDENTIFIER, model.resizing_picture.filename
152
+ assert_equal IDENTIFIER, model.resizing_picture.identifier
153
+ end
154
+
155
+ def test_file_reflects_column_value
156
+ model = model_with_image
157
+ file = model.resizing_picture.file
158
+
159
+ assert_instance_of Resizing::CarrierWave::Storage::File, file
160
+ assert_equal IDENTIFIER, file.public_id.to_s
161
+ assert_equal IDENTIFIER, file.path
162
+ end
163
+
164
+ def test_uploader_is_present_when_column_has_value
165
+ model = model_with_image
166
+
167
+ assert model.resizing_picture.present?
168
+ refute model.resizing_picture.blank?
169
+ end
170
+
171
+ # ============================================================
172
+ # cache! / store_versions! / remove_versions! / rename
173
+ # ============================================================
174
+
175
+ def test_cache_with_nil_does_nothing
176
+ uploader = TestModel.new.resizing_picture
177
+
178
+ assert_vcr_no_requests 'carrier_wave_test/save' do
179
+ assert_nil uploader.cache!(nil)
180
+ end
181
+ assert_nil uploader.file
182
+ end
183
+
184
+ def test_store_versions_and_remove_versions_are_noop
185
+ uploader = TestModel.new.resizing_picture
186
+
187
+ assert_nil uploader.store_versions!
188
+ assert_nil uploader.remove_versions!
189
+ end
190
+
191
+ def test_rename_raises_not_implemented_error
192
+ assert_raises(NotImplementedError) { TestModel.new.resizing_picture.rename }
193
+ end
194
+
195
+ # ============================================================
196
+ # format
197
+ # ============================================================
198
+
199
+ def test_format_related_methods_default_to_nil
200
+ uploader = TestModel.new.resizing_picture
201
+
202
+ assert_nil uploader.requested_format
203
+ assert_nil uploader.default_format
204
+ assert_nil uploader.format
205
+ end
206
+
207
+ def test_format_uses_overridden_default_format
208
+ assert_equal 'jpg', TestJPGModel.new.resizing_picture.format
209
+ end
210
+ end
211
+ end
@@ -4,23 +4,14 @@ require 'test_helper'
4
4
 
5
5
  module Resizing
6
6
  class ClientTest < Minitest::Test
7
- def setup
8
- @configuration_template = {
9
- image_host: 'http://192.168.56.101:5000',
10
- video_host: 'http://192.168.56.101:5000',
11
- project_id: 'e06e710d-f026-4dcf-b2c0-eab0de8bb83f',
12
- secret_token: 'ewbym2r1pk49x1d2lxdbiiavnqp25j2kh00hsg3koy0ppm620x5mhlmgl3rq5ci8',
13
- open_timeout: 10,
14
- response_timeout: 20
15
- }
16
- end
7
+ include ResizingTestConfiguration
17
8
 
18
9
  def teardown
19
10
  # NOP
20
11
  end
21
12
 
22
13
  def test_is_initialized
23
- Resizing.configure = @configuration_template
14
+ Resizing.configure = configuration_template
24
15
 
25
16
  client = Resizing::Client.new
26
17
  assert(!client.config.nil?)
@@ -28,14 +19,14 @@ module Resizing
28
19
  end
29
20
 
30
21
  def test_is_initialized_with_configuration
31
- config = Resizing::Configuration.new(@configuration_template)
22
+ config = Resizing::Configuration.new(configuration_template)
32
23
  client = Resizing::Client.new(config)
33
24
  assert(!client.config.nil?)
34
25
  assert_equal(client.config, config)
35
26
  end
36
27
 
37
28
  def test_is_postable_with_filename
38
- Resizing.configure = @configuration_template
29
+ Resizing.configure = configuration_template
39
30
 
40
31
  client = Resizing::Client.new
41
32
  VCR.use_cassette 'client/post', record: :once do
@@ -53,7 +44,7 @@ module Resizing
53
44
  end
54
45
 
55
46
  def test_is_unpostable_with_filename
56
- Resizing.configure = @configuration_template
47
+ Resizing.configure = configuration_template
57
48
 
58
49
  client = Resizing::Client.new
59
50
  VCR.use_cassette 'client/post', record: :once do
@@ -64,7 +55,7 @@ module Resizing
64
55
  end
65
56
 
66
57
  def test_is_postable_with_file
67
- Resizing.configure = @configuration_template
58
+ Resizing.configure = configuration_template
68
59
 
69
60
  client = Resizing::Client.new
70
61
  VCR.use_cassette 'client/post', record: :once do
@@ -83,7 +74,7 @@ module Resizing
83
74
  end
84
75
 
85
76
  def test_is_timeout_with_post_method
86
- Resizing.configure = @configuration_template.merge(project_id: 'timeout_project_id')
77
+ Resizing.configure = configuration_template.merge(project_id: 'timeout_project_id')
87
78
 
88
79
  client = Resizing::Client.new
89
80
  f = File.open('test/data/images/sample1.jpg', 'r')
@@ -93,7 +84,7 @@ module Resizing
93
84
  end
94
85
 
95
86
  def test_is_putable_file
96
- Resizing.configure = @configuration_template
87
+ Resizing.configure = configuration_template
97
88
 
98
89
  client = Resizing::Client.new
99
90
  VCR.use_cassette 'client/put', record: :once do
@@ -115,7 +106,7 @@ module Resizing
115
106
  end
116
107
 
117
108
  def test_is_timeout_with_put_method
118
- Resizing.configure = @configuration_template.merge(project_id: 'timeout_project_id')
109
+ Resizing.configure = configuration_template.merge(project_id: 'timeout_project_id')
119
110
 
120
111
  client = Resizing::Client.new
121
112
  name = 'AWEaewfAreaweFAFASfwe'
@@ -127,7 +118,7 @@ module Resizing
127
118
  end
128
119
 
129
120
  def test_raise_error
130
- Resizing.configure = @configuration_template
121
+ Resizing.configure = configuration_template
131
122
 
132
123
  client = Resizing::Client.new
133
124
  VCR.use_cassette 'client/error', record: :once do
@@ -139,7 +130,7 @@ module Resizing
139
130
  end
140
131
 
141
132
  def test_handleable_response_body_from_resizing
142
- Resizing.configure = @configuration_template
133
+ Resizing.configure = configuration_template
143
134
 
144
135
  client = Resizing::Client.new
145
136
  VCR.use_cassette 'client/error', record: :once do
@@ -159,7 +150,7 @@ module Resizing
159
150
  def test_get_the_metadata
160
151
  # TODO
161
152
 
162
- Resizing.configure = @configuration_template
153
+ Resizing.configure = configuration_template
163
154
 
164
155
  client = Resizing::Client.new
165
156
  VCR.use_cassette 'client/metadata', record: :once do
@@ -202,7 +193,7 @@ module Resizing
202
193
  end
203
194
 
204
195
  def test_get_raises_not_implemented_error
205
- Resizing.configure = @configuration_template
196
+ Resizing.configure = configuration_template
206
197
  client = Resizing::Client.new
207
198
 
208
199
  assert_raises NotImplementedError do
@@ -211,7 +202,7 @@ module Resizing
211
202
  end
212
203
 
213
204
  def test_post_raises_error_without_content_type
214
- Resizing.configure = @configuration_template
205
+ Resizing.configure = configuration_template
215
206
  client = Resizing::Client.new
216
207
 
217
208
  assert_raises ArgumentError do
@@ -220,7 +211,7 @@ module Resizing
220
211
  end
221
212
 
222
213
  def test_post_raises_error_with_invalid_io
223
- Resizing.configure = @configuration_template
214
+ Resizing.configure = configuration_template
224
215
  client = Resizing::Client.new
225
216
 
226
217
  assert_raises ArgumentError do
@@ -229,12 +220,320 @@ module Resizing
229
220
  end
230
221
 
231
222
  def test_put_raises_error_without_content_type
232
- Resizing.configure = @configuration_template
223
+ Resizing.configure = configuration_template
233
224
  client = Resizing::Client.new
234
225
 
235
226
  assert_raises ArgumentError do
236
227
  client.put('image_id', 'test/data/images/sample1.jpg', {})
237
228
  end
238
229
  end
230
+
231
+ # ============================================================
232
+ # 偽 HTTP クライアントを使ったリクエスト内容・レスポンス処理のテスト
233
+ # VCR カセットでは表現しづらい (URL, ヘッダ, multipart パラメータ, 異常系ステータス) を検証する
234
+ # ============================================================
235
+
236
+ # Faraday::Connection の代わりに使う偽クライアント
237
+ # 発行されたリクエスト (メソッド, URL, パラメータ, ヘッダ) を記録し、あらかじめ設定したレスポンスを返す
238
+ class FakeHttpClient
239
+ FakeRequest = Struct.new(:headers)
240
+ FakeResponse = Struct.new(:status, :body)
241
+
242
+ attr_reader :requests
243
+
244
+ def initialize(status: 200, body: '{}', respond_with_nil: false)
245
+ @response = respond_with_nil ? nil : FakeResponse.new(status, body)
246
+ @requests = []
247
+ end
248
+
249
+ %i[post put delete get].each do |verb|
250
+ define_method(verb) do |url, params = nil, &block|
251
+ request = FakeRequest.new({})
252
+ block&.call(request)
253
+ @requests << { method: verb, url: url, params: params, headers: request.headers }
254
+ @response
255
+ end
256
+ end
257
+
258
+ def last_request
259
+ @requests.last
260
+ end
261
+ end
262
+
263
+ def client_with_fake_http(**fake_options)
264
+ Resizing.configure = configuration_template
265
+ client = Resizing::Client.new
266
+ fake = FakeHttpClient.new(**fake_options)
267
+ client.instance_variable_set(:@http_client, fake)
268
+ [client, fake]
269
+ end
270
+
271
+ def base_url
272
+ "#{configuration_template[:image_host]}/projects/#{configuration_template[:project_id]}/upload/images"
273
+ end
274
+
275
+ def sample_path
276
+ 'test/data/images/sample1.jpg'
277
+ end
278
+
279
+ def not_found_body(image_id)
280
+ %({"error":"ActiveRecord::RecordNotFound","message":"Upload::Image##{image_id} not found(no record)"})
281
+ end
282
+
283
+ def forbidden_body
284
+ '{"error":"AuthenticatableSecretToken::SecretTokenError","message":"invalid token is found"}'
285
+ end
286
+
287
+ def test_post_sends_multipart_image_to_upload_url_with_auth_header
288
+ client, fake = client_with_fake_http(body: '{"id":"posted"}')
289
+
290
+ Timecop.freeze do
291
+ result = client.post(sample_path, content_type: 'image/jpeg')
292
+
293
+ request = fake.last_request
294
+ assert_equal :post, request[:method]
295
+ assert_equal "#{base_url}/", request[:url]
296
+ assert_equal client.config.generate_auth_header, request[:headers]['X-ResizingToken']
297
+
298
+ part = request[:params][:image]
299
+ assert_instance_of Faraday::Multipart::FilePart, part
300
+ assert_equal 'image/jpeg', part.content_type
301
+ assert_equal 'sample1.jpg', part.original_filename
302
+
303
+ assert_equal({ 'id' => 'posted' }, result)
304
+ end
305
+ end
306
+
307
+ def test_post_uses_filename_option_over_path_basename
308
+ client, fake = client_with_fake_http
309
+
310
+ client.post(sample_path, content_type: 'image/jpeg', filename: 'renamed.jpg')
311
+
312
+ assert_equal 'renamed.jpg', fake.last_request[:params][:image].original_filename
313
+ end
314
+
315
+ def test_post_accepts_io_like_object
316
+ client, fake = client_with_fake_http
317
+ io = StringIO.new(File.binread(sample_path))
318
+
319
+ client.post(io, content_type: 'image/png', filename: 'from_io.png')
320
+
321
+ part = fake.last_request[:params][:image]
322
+ assert_same io, part.io
323
+ assert_equal 'image/png', part.content_type
324
+ assert_equal 'from_io.png', part.original_filename
325
+ end
326
+
327
+ def test_post_accepts_created_status
328
+ client, = client_with_fake_http(status: 201, body: '{"id":"created"}')
329
+
330
+ assert_equal({ 'id' => 'created' }, client.post(sample_path, content_type: 'image/jpeg'))
331
+ end
332
+
333
+ def test_post_raises_api_error_with_server_message_on_error_status
334
+ client, = client_with_fake_http(status: 403, body: forbidden_body)
335
+
336
+ error = assert_raises(Resizing::APIError) { client.post(sample_path, content_type: 'image/jpeg') }
337
+
338
+ assert_equal 'invalid token is found', error.message
339
+ assert_equal JSON.parse(forbidden_body), error.decoded_body
340
+ end
341
+
342
+ def test_post_raises_api_error_on_not_found
343
+ client, = client_with_fake_http(status: 404, body: not_found_body('x'))
344
+
345
+ assert_raises(Resizing::APIError) { client.post(sample_path, content_type: 'image/jpeg') }
346
+ end
347
+
348
+ def test_post_raises_api_error_with_status_code_when_body_is_not_json
349
+ client, = client_with_fake_http(status: 502, body: '<html>Bad Gateway</html>')
350
+
351
+ error = assert_raises(Resizing::APIError) { client.post(sample_path, content_type: 'image/jpeg') }
352
+
353
+ assert_equal 'invalid http status code 502', error.message
354
+ assert_equal({}, error.decoded_body)
355
+ end
356
+
357
+ def test_post_raises_api_error_when_response_is_nil
358
+ client, = client_with_fake_http(respond_with_nil: true)
359
+
360
+ error = assert_raises(Resizing::APIError) { client.post(sample_path, content_type: 'image/jpeg') }
361
+ assert_equal 'No response is returned', error.message
362
+ end
363
+
364
+ def test_put_sends_multipart_image_to_image_url_with_auth_header
365
+ client, fake = client_with_fake_http(body: '{"id":"img-1"}')
366
+
367
+ result = client.put('img-1', sample_path, content_type: 'image/jpeg')
368
+
369
+ request = fake.last_request
370
+ assert_equal :put, request[:method]
371
+ assert_equal "#{base_url}/img-1", request[:url]
372
+ assert_match(/\Av1,\d+,[0-9a-f]{64}\z/, request[:headers]['X-ResizingToken'])
373
+ part = request[:params][:image]
374
+ assert_instance_of Faraday::Multipart::FilePart, part
375
+ assert_equal 'image/jpeg', part.content_type
376
+ assert_equal 'sample1.jpg', part.original_filename
377
+ assert_equal({ 'id' => 'img-1' }, result)
378
+ end
379
+
380
+ def test_put_raises_api_error_on_error_status
381
+ client, = client_with_fake_http(status: 403, body: forbidden_body)
382
+
383
+ error = assert_raises(Resizing::APIError) { client.put('img-1', sample_path, content_type: 'image/jpeg') }
384
+ assert_equal 'invalid token is found', error.message
385
+ end
386
+
387
+ def test_put_raises_error_with_invalid_io
388
+ Resizing.configure = configuration_template
389
+ client = Resizing::Client.new
390
+
391
+ assert_raises ArgumentError do
392
+ client.put('image_id', 12_345, content_type: 'image/jpeg')
393
+ end
394
+ end
395
+
396
+ def test_delete_sends_delete_request_to_image_url_with_auth_header
397
+ client, fake = client_with_fake_http(body: '{"id":"img-1"}')
398
+
399
+ result = client.delete('img-1')
400
+
401
+ request = fake.last_request
402
+ assert_equal :delete, request[:method]
403
+ assert_equal "#{base_url}/img-1", request[:url]
404
+ assert_match(/\Av1,\d+,[0-9a-f]{64}\z/, request[:headers]['X-ResizingToken'])
405
+ assert_equal({ 'id' => 'img-1' }, result)
406
+ end
407
+
408
+ def test_delete_returns_error_body_without_raising_on_not_found
409
+ client, = client_with_fake_http(status: 404, body: not_found_body('img-1'))
410
+
411
+ result = client.delete('img-1')
412
+
413
+ assert_equal 'ActiveRecord::RecordNotFound', result['error']
414
+ end
415
+
416
+ def test_delete_raises_api_error_on_other_error_status
417
+ client, = client_with_fake_http(status: 403, body: forbidden_body)
418
+
419
+ error = assert_raises(Resizing::APIError) { client.delete('img-1') }
420
+ assert_equal 'invalid token is found', error.message
421
+ end
422
+
423
+ def test_delete_raises_api_error_when_response_is_nil
424
+ client, = client_with_fake_http(respond_with_nil: true)
425
+
426
+ assert_raises(Resizing::APIError) { client.delete('img-1') }
427
+ end
428
+
429
+ def test_metadata_sends_get_request_to_metadata_url_with_auth_header
430
+ client, fake = client_with_fake_http(body: '{"id":"img-1","width":100}')
431
+
432
+ result = client.metadata('img-1')
433
+
434
+ request = fake.last_request
435
+ assert_equal :get, request[:method]
436
+ assert_equal "#{base_url}/img-1/metadata", request[:url]
437
+ assert_match(/\Av1,\d+,[0-9a-f]{64}\z/, request[:headers]['X-ResizingToken'])
438
+ assert_equal({ 'id' => 'img-1', 'width' => 100 }, result)
439
+ end
440
+
441
+ def test_metadata_returns_error_body_without_raising_on_not_found_by_default
442
+ client, = client_with_fake_http(status: 404, body: not_found_body('img-1'))
443
+
444
+ result = client.metadata('img-1')
445
+
446
+ assert_equal 'ActiveRecord::RecordNotFound', result['error']
447
+ end
448
+
449
+ def test_metadata_raises_api_error_on_not_found_when_requested
450
+ client, = client_with_fake_http(status: 404, body: not_found_body('img-1'))
451
+
452
+ error = assert_raises(Resizing::APIError) { client.metadata('img-1', when_not_found: :raise) }
453
+
454
+ assert_equal 'Upload::Image#img-1 not found(no record)', error.message
455
+ assert_equal 'ActiveRecord::RecordNotFound', error.decoded_body['error']
456
+ end
457
+
458
+ def test_metadata_raises_api_error_on_other_error_status
459
+ client, = client_with_fake_http(status: 500, body: 'Internal Server Error')
460
+
461
+ error = assert_raises(Resizing::APIError) { client.metadata('img-1') }
462
+ assert_equal 'invalid http status code 500', error.message
463
+ end
464
+
465
+ def test_metadata_raises_api_error_when_response_is_nil
466
+ client, = client_with_fake_http(respond_with_nil: true)
467
+
468
+ assert_raises(Resizing::APIError) { client.metadata('img-1') }
469
+ end
470
+
471
+ # ============================================================
472
+ # VCR カセットを使った delete / timeout のテスト
473
+ # ============================================================
474
+
475
+ def test_is_deletable
476
+ # client/delete.yml は別プロジェクト ID で記録されている
477
+ Resizing.configure = configuration_template.merge(project_id: '098a2a0d-c387-4135-a071-1254d6d7e70a')
478
+ client = Resizing::Client.new
479
+ image_id = '28c49144-c00d-4cb5-8619-98ce95977b9c'
480
+
481
+ VCR.use_cassette 'client/delete', record: :none do
482
+ r = client.delete(image_id)
483
+
484
+ assert_equal image_id, r['id']
485
+ assert_equal '098a2a0d-c387-4135-a071-1254d6d7e70a', r['project_id']
486
+ assert_equal(
487
+ "/projects/098a2a0d-c387-4135-a071-1254d6d7e70a/upload/images/#{image_id}/v1Id850__tqNsnoGWWUibtIBZ5NgjV45M",
488
+ r['public_id']
489
+ )
490
+ end
491
+ end
492
+
493
+ def test_is_timeout_with_delete_method
494
+ Resizing.configure = configuration_template.merge(project_id: 'timeout_project_id')
495
+ client = Resizing::Client.new
496
+
497
+ assert_raises Resizing::APIError do
498
+ client.delete('some_image_id')
499
+ end
500
+ end
501
+
502
+ def test_is_timeout_with_metadata_method
503
+ Resizing.configure = configuration_template.merge(project_id: 'timeout_project_id')
504
+ client = Resizing::Client.new
505
+
506
+ assert_raises Resizing::APIError do
507
+ client.metadata('some_image_id')
508
+ end
509
+ end
510
+
511
+ # ============================================================
512
+ # 設定の受け渡し
513
+ # ============================================================
514
+
515
+ def test_is_initialized_with_hash
516
+ client = Resizing::Client.new(configuration_template)
517
+
518
+ assert_equal Resizing::Configuration.new(configuration_template), client.config
519
+ end
520
+
521
+ def test_each_client_instance_keeps_its_own_configuration
522
+ Resizing.configure = configuration_template
523
+ other = Resizing::Client.new(configuration_template.merge(project_id: 'other-project'))
524
+
525
+ assert_equal 'other-project', other.config.project_id
526
+ assert_equal configuration_template[:project_id], Resizing::Client.new.config.project_id
527
+ end
528
+
529
+ def test_client_uses_configuration_at_instantiation
530
+ Resizing.configure = configuration_template
531
+ client = Resizing::Client.new
532
+
533
+ # インスタンス生成後にグローバル設定を差し替えても既存クライアントには影響しない
534
+ Resizing.configure = configuration_template.merge(project_id: 'replaced-project')
535
+
536
+ assert_equal configuration_template[:project_id], client.config.project_id
537
+ end
239
538
  end
240
539
  end