copy_tuner_client 2.1.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +46 -1
  4. data/CLAUDE.md +1 -0
  5. data/Gemfile +1 -0
  6. data/copy_tuner_client.gemspec +3 -3
  7. data/gemfiles/8.0.gemfile +3 -3
  8. data/gemfiles/8.1.gemfile +3 -3
  9. data/gemfiles/main.gemfile +3 -3
  10. data/lib/copy_tuner_client/cache.rb +34 -27
  11. data/lib/copy_tuner_client/client.rb +35 -23
  12. data/lib/copy_tuner_client/configuration.rb +62 -40
  13. data/lib/copy_tuner_client/copyray/rewriter.rb +1 -1
  14. data/lib/copy_tuner_client/copyray.rb +1 -0
  15. data/lib/copy_tuner_client/copyray_middleware.rb +31 -24
  16. data/lib/copy_tuner_client/dotted_hash.rb +7 -7
  17. data/lib/copy_tuner_client/engine.rb +4 -6
  18. data/lib/copy_tuner_client/helper_extension.rb +58 -54
  19. data/lib/copy_tuner_client/i18n_backend.rb +12 -14
  20. data/lib/copy_tuner_client/i18n_compat.rb +2 -1
  21. data/lib/copy_tuner_client/poller.rb +3 -4
  22. data/lib/copy_tuner_client/prefixed_logger.rb +13 -12
  23. data/lib/copy_tuner_client/process_guard.rb +29 -26
  24. data/lib/copy_tuner_client/queue_with_timeout.rb +29 -19
  25. data/lib/copy_tuner_client/rails.rb +9 -8
  26. data/lib/copy_tuner_client/request_sync.rb +26 -24
  27. data/lib/copy_tuner_client/simple_form_extention.rb +10 -6
  28. data/lib/copy_tuner_client/translation_log.rb +10 -5
  29. data/lib/copy_tuner_client/version.rb +1 -1
  30. data/lib/copy_tuner_client.rb +4 -8
  31. data/lib/tasks/copy_tuner_client_tasks.rake +14 -16
  32. data/skills/copy-tuner-to-locales-migrate-prefix/scripts/migrate_prefix.rb +14 -7
  33. data/skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb +19 -17
  34. data/spec/copy_tuner_client/cache_spec.rb +63 -57
  35. data/spec/copy_tuner_client/client_spec.rb +82 -81
  36. data/spec/copy_tuner_client/configuration_spec.rb +223 -222
  37. data/spec/copy_tuner_client/copyray/marker_spec.rb +2 -1
  38. data/spec/copy_tuner_client/copyray/rewriter_spec.rb +15 -10
  39. data/spec/copy_tuner_client/copyray_middleware_spec.rb +9 -6
  40. data/spec/copy_tuner_client/copyray_spec.rb +8 -8
  41. data/spec/copy_tuner_client/dotted_hash_spec.rb +33 -33
  42. data/spec/copy_tuner_client/helper_extension_spec.rb +47 -44
  43. data/spec/copy_tuner_client/i18n_backend_spec.rb +144 -143
  44. data/spec/copy_tuner_client/poller_spec.rb +25 -24
  45. data/spec/copy_tuner_client/prefixed_logger_spec.rb +15 -12
  46. data/spec/copy_tuner_client/process_guard_spec.rb +27 -27
  47. data/spec/copy_tuner_client/request_sync_spec.rb +51 -58
  48. data/spec/copy_tuner_client/translation_log_spec.rb +38 -24
  49. data/spec/copy_tuner_client_spec.rb +5 -6
  50. data/spec/support/client_spec_helpers.rb +1 -1
  51. data/spec/support/defines_constants.rb +3 -34
  52. data/spec/support/fake_client.rb +1 -3
  53. data/spec/support/fake_copy_tuner_app.rb +54 -62
  54. data/spec/support/fake_html_safe_string.rb +2 -3
  55. data/spec/support/fake_logger.rb +22 -21
  56. data/spec/support/fake_passenger.rb +4 -6
  57. data/spec/support/fake_unicorn.rb +2 -4
  58. data/spec/support/middleware_stack.rb +3 -3
  59. data/spec/support/writing_cache.rb +4 -4
  60. metadata +1 -1
@@ -2,34 +2,39 @@ require 'spec_helper'
2
2
 
3
3
  describe 'CopyTunerClient::I18nBackend' do
4
4
  # テスト用のキャッシュクラス:既存のHashインターフェースを維持しつつ新機能をサポート
5
- class TestCache < Hash
6
- def initialize(initial_etag = 'test-etag-1')
7
- super()
8
- @test_etag = initial_etag
9
- end
5
+ def test_cache_class
6
+ Class.new(Hash) do
7
+ def initialize(initial_etag = 'test-etag-1')
8
+ super()
9
+ @test_etag = initial_etag
10
+ end
10
11
 
11
- def version
12
- @test_etag
13
- end
12
+ def version
13
+ @test_etag
14
+ end
14
15
 
15
- def to_tree_hash
16
- CopyTunerClient::DottedHash.to_h(self)
17
- end
16
+ def to_tree_hash
17
+ CopyTunerClient::DottedHash.to_h(self)
18
+ end
18
19
 
19
- def wait_for_download
20
- # テスト用のスタブメソッド
21
- end
20
+ def wait_for_download
21
+ # テスト用のスタブメソッド
22
+ end
22
23
 
23
- def etag=(value)
24
- @test_etag = value
25
- end
24
+ def etag=(value)
25
+ @test_etag = value
26
+ end
26
27
 
27
- def etag
28
- @test_etag
28
+ def etag
29
+ @test_etag
30
+ end
29
31
  end
30
32
  end
31
33
 
32
- let(:cache) { TestCache.new }
34
+ subject(:backend) { build_backend }
35
+
36
+ let(:cache) { test_cache_class.new }
37
+ let!(:default_backend) { I18n.backend }
33
38
 
34
39
  def build_backend
35
40
  backend = CopyTunerClient::I18nBackend.new(cache)
@@ -37,23 +42,18 @@ describe 'CopyTunerClient::I18nBackend' do
37
42
  backend
38
43
  end
39
44
 
40
- before do
41
- @default_backend = I18n.backend
42
- end
43
-
44
- after { I18n.backend = @default_backend }
45
-
46
- subject { build_backend }
45
+ after { I18n.backend = default_backend }
47
46
 
48
47
  it 'ロケールファイルをリロードし、ダウンロード完了まで待機すること' do
49
- expect(I18n).to receive(:load_path).and_return([])
48
+ allow(I18n).to receive(:load_path).and_return([])
50
49
  # wait_for_downloadはTestCacheクラス内で呼ばれる
51
- subject.reload!
52
- subject.translate('en', 'test.key', :default => 'something')
50
+ backend.reload!
51
+ backend.translate('en', 'test.key', default: 'something')
52
+ expect(I18n).to have_received(:load_path)
53
53
  end
54
54
 
55
55
  it 'i18nのBaseバックエンドを継承していること' do
56
- is_expected.to be_kind_of(I18n::Backend::Base)
56
+ expect(backend).to be_a(I18n::Backend::Base)
57
57
  end
58
58
 
59
59
  it 'キャッシュからキーを検索できること' do
@@ -62,23 +62,23 @@ describe 'CopyTunerClient::I18nBackend' do
62
62
 
63
63
  backend = build_backend
64
64
 
65
- expect(backend.translate('en', 'test.key', :scope => 'prefix')).to eq(value)
65
+ expect(backend.translate('en', 'test.key', scope: 'prefix')).to eq(value)
66
66
  end
67
67
 
68
68
  it 'ロケールファイルとキャッシュから利用可能なロケールを取得できること' do
69
69
  allow(YAML).to receive(:unsafe_load_file).and_return({ 'es' => { 'key' => 'value' } })
70
- allow(I18n).to receive(:load_path).and_return(["test.yml"])
70
+ allow(I18n).to receive(:load_path).and_return(['test.yml'])
71
71
 
72
72
  cache['en.key'] = ''
73
73
  cache['fr.key'] = ''
74
74
 
75
- expect(subject.available_locales).to match_array([:en, :es, :fr])
75
+ expect(backend.available_locales).to contain_exactly(:en, :es, :fr)
76
76
  end
77
77
 
78
78
  it 'default付きで未登録キーをキューイングすること' do
79
79
  default = 'default value'
80
80
 
81
- expect(subject.translate('en', 'test.key', :default => default)).to eq(default)
81
+ expect(backend.translate('en', 'test.key', default:)).to eq(default)
82
82
 
83
83
  expect(cache['en.test.key']).to eq(default)
84
84
  end
@@ -86,14 +86,14 @@ describe 'CopyTunerClient::I18nBackend' do
86
86
  it 'defaultが配列(文字列1つ)の場合も未登録キーをキューイングすること' do
87
87
  default = 'default value'
88
88
 
89
- expect(subject.translate('en', 'test.key', :default => [default])).to eq(default)
89
+ expect(backend.translate('en', 'test.key', default: [default])).to eq(default)
90
90
 
91
91
  expect(cache['en.test.key']).to eq(default)
92
92
  end
93
93
 
94
94
  it 'defaultなしで未登録キーをキューイングすること' do
95
- expect { subject.translate('en', 'test.key') }.
96
- to throw_symbol(:exception)
95
+ expect { backend.translate('en', 'test.key') }
96
+ .to throw_symbol(:exception)
97
97
 
98
98
  expect(cache).to have_key 'en.test.key'
99
99
  expect(cache['en.test.key']).to be_nil
@@ -102,123 +102,121 @@ describe 'CopyTunerClient::I18nBackend' do
102
102
  it 'scope付きで未登録キーをキューイングすること' do
103
103
  default = 'default value'
104
104
 
105
- expect(subject.translate('en', 'key', :default => default, :scope => ['test'])).
106
- to eq(default)
105
+ expect(backend.translate('en', 'key', default:, scope: ['test']))
106
+ .to eq(default)
107
107
 
108
108
  expect(cache['en.test.key']).to eq(default)
109
109
  end
110
110
 
111
111
  it 'defaultがシンボルの場合は未登録キーをキューイングしないこと' do
112
- cache['en.key.one'] = "Expected"
112
+ cache['en.key.one'] = 'Expected'
113
113
 
114
- expect(subject.translate('en', 'key.three', :default => :"key.one")).to eq 'Expected'
114
+ expect(backend.translate('en', 'key.three', default: :'key.one')).to eq 'Expected'
115
115
 
116
116
  expect(cache).to have_key 'en.key.three'
117
117
  expect(cache['en.key.three']).to be_nil
118
118
 
119
- expect(subject.translate('en', 'key.three', :default => :"key.one")).to eq 'Expected'
119
+ expect(backend.translate('en', 'key.three', default: :'key.one')).to eq 'Expected'
120
120
  end
121
121
 
122
122
  it 'defaultが配列(シンボル含む)の場合は未登録キーをキューイングしないこと' do
123
- cache['en.key.one'] = "Expected"
123
+ cache['en.key.one'] = 'Expected'
124
124
 
125
- expect(subject.translate('en', 'key.three', :default => [:"key.two", :"key.one"])).to eq 'Expected'
125
+ expect(backend.translate('en', 'key.three', default: %i[key.two key.one])).to eq 'Expected'
126
126
 
127
127
  expect(cache).to have_key 'en.key.three'
128
128
  expect(cache['en.key.three']).to be_nil
129
129
 
130
- expect(subject.translate('en', 'key.three', :default => [:"key.two", :"key.one"])).to eq 'Expected'
130
+ expect(backend.translate('en', 'key.three', default: %i[key.two key.one])).to eq 'Expected'
131
131
  end
132
132
 
133
133
  it '補間付きで未登録キーをキューイングすること' do
134
- default = 'default %{interpolate}'
134
+ # I18n の補間構文 %{interpolate} であり Kernel#format 用の printf トークンではないため annotated 化は不適切
135
+ default = 'default %{interpolate}' # rubocop:disable Style/FormatStringToken
135
136
 
136
- expect(subject.translate('en', 'test.key', :default => default, :interpolate => 'interpolated')).to eq 'default interpolated'
137
+ expect(backend.translate('en', 'test.key', default:, interpolate: 'interpolated')).to eq 'default interpolated'
137
138
 
138
- expect(cache['en.test.key']).to eq 'default %{interpolate}'
139
+ expect(cache['en.test.key']).to eq 'default %{interpolate}' # rubocop:disable Style/FormatStringToken
139
140
  end
140
141
 
141
142
  # NOTE: backend は html_safe 化をしない(.html/_html キーの html_safe 化は ActionView の
142
143
  # TranslationHelper が担う)。html_escape 設定の有無に関わらず素の content を返す i18n 標準準拠の挙動。
143
144
  it 'html safeを付与しないこと' do
144
- cache['en.test.key'] = FakeHtmlSafeString.new("Hello")
145
+ cache['en.test.key'] = FakeHtmlSafeString.new('Hello')
145
146
  backend = build_backend
146
- expect(backend.translate('en', 'test.key')).to_not be_html_safe
147
+ expect(backend.translate('en', 'test.key')).not_to be_html_safe
147
148
  end
148
149
 
149
150
  it 'html_safe な値を渡しても backend が独自に html_safe 化しないこと' do
150
- cache['en.test.key'] = FakeHtmlSafeString.new("Hello").html_safe
151
+ cache['en.test.key'] = FakeHtmlSafeString.new('Hello').html_safe
151
152
  backend = build_backend
152
153
  expect(backend.translate('en', 'test.key')).to be_html_safe
153
154
  end
154
155
 
155
156
  it 'defaultが配列の場合に順に検索できること' do
156
- cache['en.key.one'] = "Expected"
157
+ cache['en.key.one'] = 'Expected'
157
158
  backend = build_backend
158
- expect(backend.translate('en', 'key.three', :default => [:"key.two", :"key.one"])).
159
- to eq('Expected')
159
+ expect(backend.translate('en', 'key.three', default: %i[key.two key.one]))
160
+ .to eq('Expected')
160
161
  end
161
162
 
162
163
  context '非文字列キーの場合' do
163
164
  it 'キャッシュに登録されないこと' do
164
- expect { subject.translate('en', {}) }.to throw_symbol(:exception)
165
+ expect { backend.translate('en', {}) }.to throw_symbol(:exception)
165
166
  expect(cache).not_to have_key 'en.{}'
166
167
  end
167
168
  end
168
169
 
169
170
  describe 'store_translations利用時' do
170
- subject { build_backend }
171
-
172
171
  it 'store_translationsで登録した値をdefaultとして利用できること' do
173
- subject.store_translations('en', 'test' => { 'key' => 'Expected' })
174
- expect(subject.translate('en', 'test.key', :default => 'Unexpected')).
175
- to include('Expected')
172
+ backend.store_translations('en', 'test' => { 'key' => 'Expected' })
173
+ expect(backend.translate('en', 'test.key', default: 'Unexpected'))
174
+ .to include('Expected')
176
175
  expect(cache['en.test.key']).to eq('Expected')
177
176
  end
178
177
 
179
178
  it '補間マーカーを保持したまま保存できること' do
180
- subject.store_translations('en', 'test' => { 'key' => '%{interpolate}' })
181
- expect(subject.translate('en', 'test.key', :interpolate => 'interpolated')).
182
- to include('interpolated')
183
- expect(cache['en.test.key']).to eq('%{interpolate}')
179
+ # I18n の補間構文 %{interpolate} であり Kernel#format 用の printf トークンではないため annotated 化は不適切
180
+ backend.store_translations('en', 'test' => { 'key' => '%{interpolate}' }) # rubocop:disable Style/FormatStringToken
181
+ expect(backend.translate('en', 'test.key', interpolate: 'interpolated'))
182
+ .to include('interpolated')
183
+ expect(cache['en.test.key']).to eq('%{interpolate}') # rubocop:disable Style/FormatStringToken
184
184
  end
185
185
 
186
186
  it 'store_translationsでキーがなければdefaultを利用すること' do
187
- expect(subject.translate('en', 'test.key', :default => 'Expected')).
188
- to include('Expected')
187
+ expect(backend.translate('en', 'test.key', default: 'Expected'))
188
+ .to include('Expected')
189
189
  end
190
190
 
191
191
  it 'キャッシュにキーがあればそちらを優先すること' do
192
- subject.store_translations('en', 'test' => { 'key' => 'Unexpected' })
192
+ backend.store_translations('en', 'test' => { 'key' => 'Unexpected' })
193
193
  cache['en.test.key'] = 'Expected'
194
- expect(subject.translate('en', 'test.key', :default => 'default')).
195
- to include('Expected')
194
+ expect(backend.translate('en', 'test.key', default: 'default'))
195
+ .to include('Expected')
196
196
  end
197
197
 
198
198
  it 'ネストしたハッシュを保存できること' do
199
- nested = { :nested => 'value' }
200
- subject.store_translations('en', 'key' => nested)
201
- expect(subject.translate('en', 'key', :default => 'Unexpected')).to eq(nested)
199
+ nested = { nested: 'value' }
200
+ backend.store_translations('en', 'key' => nested)
201
+ expect(backend.translate('en', 'key', default: 'Unexpected')).to eq(nested)
202
202
  expect(cache['en.key.nested']).to eq('value')
203
203
  end
204
204
 
205
205
  it '配列はそのまま返しキャッシュしないこと' do
206
206
  array = ['value']
207
- subject.store_translations('en', 'key' => array)
208
- expect(subject.translate('en', 'key', :default => 'Unexpected')).to eq(array)
207
+ backend.store_translations('en', 'key' => array)
208
+ expect(backend.translate('en', 'key', default: 'Unexpected')).to eq(array)
209
209
  expect(cache['en.key']).to be_nil
210
210
  end
211
211
 
212
212
  it 'defaultが配列の場合に順に検索できること' do
213
- subject.store_translations('en', 'key' => { 'one' => 'Expected' })
214
- expect(subject.translate('en', 'key.three', :default => [:"key.two", :"key.one"])).
215
- to include('Expected')
213
+ backend.store_translations('en', 'key' => { 'one' => 'Expected' })
214
+ expect(backend.translate('en', 'key.three', default: %i[key.two key.one]))
215
+ .to include('Expected')
216
216
  end
217
217
  end
218
218
 
219
219
  describe 'Fallbacks利用時' do
220
- subject { build_backend }
221
-
222
220
  before do
223
221
  CopyTunerClient::I18nBackend.class_eval do
224
222
  include I18n::Backend::Fallbacks
@@ -227,7 +225,7 @@ describe 'CopyTunerClient::I18nBackend' do
227
225
 
228
226
  it 'defaultとFallbacks併用時はキャッシュにデフォルト値を入れないこと' do
229
227
  default = 'default value'
230
- expect(subject.translate('en', 'test.key', :default => default)).to eq(default)
228
+ expect(backend.translate('en', 'test.key', default:)).to eq(default)
231
229
 
232
230
  # default と Fallbacks を併用した場合、キャッシュにデフォルト値は入らない仕様に変えた
233
231
  # その仕様にしないと、うまく Fallbacks の処理が動かないため
@@ -237,15 +235,13 @@ describe 'CopyTunerClient::I18nBackend' do
237
235
  end
238
236
 
239
237
  # NOTE: 色々考慮する必要があることが分かったため暫定対応として、ツリーキャッシュを使用しないようにしている
240
- describe 'ツリー構造のlookup' do # rubocop:disable Metrics/BlockLength
241
- subject { build_backend }
242
-
238
+ describe 'ツリー構造のlookup' do
243
239
  context '完全一致が存在する場合' do
244
240
  it 'ツリー構造より完全一致を優先すること' do
245
241
  cache['ja.views.hoge'] = 'exact_value'
246
242
  cache['ja.views.hoge.sub'] = 'sub_value'
247
243
 
248
- result = subject.translate('ja', 'views.hoge')
244
+ result = backend.translate('ja', 'views.hoge')
249
245
  expect(result).to eq('exact_value')
250
246
  end
251
247
  end
@@ -256,17 +252,17 @@ describe 'CopyTunerClient::I18nBackend' do
256
252
  cache['ja.views.fuga'] = 'test2'
257
253
  cache['ja.other.key'] = 'other'
258
254
 
259
- result = subject.translate('ja', 'views')
255
+ result = backend.translate('ja', 'views')
260
256
  expect(result).to eq({
261
- :hoge => 'test',
262
- :fuga => 'test2'
263
- })
257
+ hoge: 'test',
258
+ fuga: 'test2',
259
+ })
264
260
  end
265
261
 
266
262
  it '存在しない部分キーはnilを返すこと' do
267
263
  cache['ja.views.hoge'] = 'test'
268
264
 
269
- result = subject.translate('ja', 'nonexistent', default: nil)
265
+ result = backend.translate('ja', 'nonexistent', default: nil)
270
266
  expect(result).to be_nil
271
267
  end
272
268
 
@@ -275,16 +271,16 @@ describe 'CopyTunerClient::I18nBackend' do
275
271
  cache['ja.views.users.show'] = 'user show'
276
272
  cache['ja.views.posts.index'] = 'post index'
277
273
 
278
- result = subject.translate('ja', 'views')
274
+ result = backend.translate('ja', 'views')
279
275
  expect(result).to eq({
280
- :users => {
281
- :index => 'user index',
282
- :show => 'user show'
283
- },
284
- :posts => {
285
- :index => 'post index'
286
- }
287
- })
276
+ users: {
277
+ index: 'user index',
278
+ show: 'user show',
279
+ },
280
+ posts: {
281
+ index: 'post index',
282
+ },
283
+ })
288
284
  end
289
285
  end
290
286
 
@@ -297,18 +293,18 @@ describe 'CopyTunerClient::I18nBackend' do
297
293
  end
298
294
 
299
295
  it '完全一致を正しく扱うこと' do
300
- expect(subject.translate('ja', 'views.hoge')).to eq('exact_hoge')
296
+ expect(backend.translate('ja', 'views.hoge')).to eq('exact_hoge')
301
297
  end
302
298
 
303
299
  it 'ツリー構造を正しく扱うこと' do
304
- expect(subject.translate('ja', 'views.fuga')).to eq({
305
- :one => 'one',
306
- :two => 'two'
307
- })
300
+ expect(backend.translate('ja', 'views.fuga')).to eq({
301
+ one: 'one',
302
+ two: 'two',
303
+ })
308
304
  end
309
305
 
310
306
  it 'より深い完全一致も正しく扱うこと' do
311
- expect(subject.translate('ja', 'views.hoge.sub')).to eq('sub_value')
307
+ expect(backend.translate('ja', 'views.hoge.sub')).to eq('sub_value')
312
308
  end
313
309
  end
314
310
 
@@ -318,45 +314,47 @@ describe 'CopyTunerClient::I18nBackend' do
318
314
  cache['ja.views.fuga'] = 'test2'
319
315
 
320
316
  # 最初のlookupでツリーキャッシュが構築される
321
- result = subject.translate('ja', 'views')
317
+ result = backend.translate('ja', 'views')
322
318
  expect(result).to eq({
323
- :hoge => 'test',
324
- :fuga => 'test2'
325
- })
319
+ hoge: 'test',
320
+ fuga: 'test2',
321
+ })
326
322
  end
327
323
 
328
324
  it '2回目以降はツリーキャッシュを再利用すること' do
329
325
  cache['ja.views.hoge'] = 'test'
330
326
 
331
327
  # 1回目
332
- subject.translate('ja', 'views')
328
+ backend.translate('ja', 'views')
333
329
 
334
330
  # ツリーキャッシュの再構築が発生しないことを確認
335
- expect(cache).not_to receive(:to_tree_hash)
331
+ allow(cache).to receive(:to_tree_hash)
336
332
 
337
333
  # 2回目
338
- subject.translate('ja', 'views')
334
+ backend.translate('ja', 'views')
335
+
336
+ expect(cache).not_to have_received(:to_tree_hash)
339
337
  end
340
338
 
341
339
  it 'キャッシュバージョンが変わった場合はツリーキャッシュを再構築すること' do
342
340
  cache['ja.views.hoge'] = 'test'
343
- subject.translate('ja', 'views')
341
+ backend.translate('ja', 'views')
344
342
 
345
343
  # ETag(バージョン)を変更してキャッシュを更新
346
344
  cache.etag = '"new_etag"'
347
345
 
348
346
  # 新しい値を追加
349
347
  cache['ja.views.new'] = 'new value'
350
- result = subject.translate('ja', 'views')
351
- expect(result).to include(:new => 'new value')
348
+ result = backend.translate('ja', 'views')
349
+ expect(result).to include(new: 'new value')
352
350
  end
353
351
 
354
352
  it 'キャッシュバージョンがnilでも正常に動作すること' do
355
353
  cache['ja.views.test'] = 'value'
356
354
  cache.etag = nil
357
355
 
358
- result = subject.translate('ja', 'views')
359
- expect(result).to eq({ :test => 'value' })
356
+ result = backend.translate('ja', 'views')
357
+ expect(result).to eq({ test: 'value' })
360
358
  end
361
359
  end
362
360
 
@@ -368,16 +366,17 @@ describe 'CopyTunerClient::I18nBackend' do
368
366
  end
369
367
 
370
368
  # 初回のツリーキャッシュ構築
371
- subject.translate('ja', 'category1')
369
+ backend.translate('ja', 'category1')
372
370
 
373
371
  # ETag が変わらない限り、再構築されない
374
- expect(cache).not_to receive(:to_tree_hash)
372
+ allow(cache).to receive(:to_tree_hash)
375
373
 
376
374
  # 複数回の lookup が高速で実行される
377
375
  start_time = Time.now
378
- 10.times { subject.translate('ja', 'category2') }
376
+ 10.times { backend.translate('ja', 'category2') }
379
377
  end_time = Time.now
380
378
 
379
+ expect(cache).not_to have_received(:to_tree_hash)
381
380
  # 10ms 以下で完了することを確認
382
381
  expect((end_time - start_time) * 1000).to be < 10
383
382
  end
@@ -385,37 +384,39 @@ describe 'CopyTunerClient::I18nBackend' do
385
384
 
386
385
  context 'エッジケース' do
387
386
  it '空キャッシュでも正常に動作すること' do
388
- result = subject.translate('ja', 'views', default: nil)
387
+ result = backend.translate('ja', 'views', default: nil)
389
388
  expect(result).to be_nil
390
389
  end
391
390
 
392
391
  it '1階層のキーも正常に扱えること' do
393
392
  cache['ja.simple'] = 'simple value'
394
393
 
395
- result = subject.translate('ja', 'simple')
394
+ result = backend.translate('ja', 'simple')
396
395
  expect(result).to eq('simple value')
397
396
  end
398
397
 
399
398
  it 'ignored_keysの機能がツリーlookupでも維持されること' do
400
399
  # ignored_keys 設定
401
- allow(CopyTunerClient.configuration).to receive(:ignored_keys).and_return(['views.secret'])
402
- handler = double('ignored_key_handler')
403
- allow(CopyTunerClient.configuration).to receive(:ignored_key_handler).and_return(handler)
400
+ handler = instance_double(Proc)
401
+ allow(CopyTunerClient.configuration).to receive_messages(ignored_keys: ['views.secret'],
402
+ ignored_key_handler: handler)
404
403
 
405
404
  cache['ja.views.public'] = 'public'
406
405
  cache['ja.views.secret'] = 'secret'
407
406
 
408
407
  # ignored_key_handler が呼ばれることを確認
409
- expect(handler).to receive(:call).with(instance_of(CopyTunerClient::IgnoredKey))
408
+ allow(handler).to receive(:call)
410
409
 
411
410
  # ignored_keys が動作することを確認
412
- subject.translate('ja', 'views.secret')
411
+ backend.translate('ja', 'views.secret')
412
+
413
+ expect(handler).to have_received(:call).with(instance_of(CopyTunerClient::IgnoredKey))
413
414
  end
414
415
 
415
416
  it 'stringキーが存在する場合のsub-keyアクセスでエラーが発生しないこと' do
416
417
  cache['ja.hoge'] = 'hoge value'
417
418
 
418
- result = subject.translate('ja', 'hoge.hello', default: nil)
419
+ result = backend.translate('ja', 'hoge.hello', default: nil)
419
420
  expect(result).to be_nil
420
421
  end
421
422
 
@@ -425,20 +426,20 @@ describe 'CopyTunerClient::I18nBackend' do
425
426
  cache['ja.dummy'] = 'dummy'
426
427
 
427
428
  expect {
428
- subject.translate('ja', 'enumerize.infection_control.body_temperature.36.5', default: nil)
429
+ backend.translate('ja', 'enumerize.infection_control.body_temperature.36.5', default: nil)
429
430
  }.not_to raise_error
430
431
  end
431
432
 
432
433
  it '数値セグメントキーが存在しても通常キーのlookupが壊れないこと' do
433
434
  cache['ja.views.hoge'] = 'normal'
434
435
 
435
- expect { subject.translate('ja', 'enumerize.body_temperature.36.5', default: nil) }.not_to raise_error
436
- expect(subject.translate('ja', 'views.hoge')).to eq('normal')
436
+ expect { backend.translate('ja', 'enumerize.body_temperature.36.5', default: nil) }.not_to raise_error
437
+ expect(backend.translate('ja', 'views.hoge')).to eq('normal')
437
438
  end
438
439
  end
439
440
  end
440
441
 
441
- describe 'local_first_key_regexp(ローカル優先キー)' do # rubocop:disable Metrics/BlockLength
442
+ describe 'local_first_key_regexp(ローカル優先キー)' do
442
443
  after { CopyTunerClient.configuration.local_first_key_regexp = nil }
443
444
 
444
445
  # ローカル config/locales 相当のデータを I18n::Backend::Simple 側だけに格納する。
@@ -457,9 +458,9 @@ describe 'CopyTunerClient::I18nBackend' do
457
458
  it 'views.* でも従来どおり copy_tuner(cache)を優先すること' do
458
459
  CopyTunerClient.configuration.local_first_key_regexp = nil
459
460
  cache['ja.views.foo'] = 'copy tuner value'
460
- store_local(subject, :ja, views: { foo: 'local value' })
461
+ store_local(backend, :ja, views: { foo: 'local value' })
461
462
 
462
- expect(subject.translate('ja', 'views.foo')).to eq('copy tuner value')
463
+ expect(backend.translate('ja', 'views.foo')).to eq('copy tuner value')
463
464
  end
464
465
 
465
466
  # NOTE: number.*.format は precision 等の非文字列値が store_item で落ちるため、
@@ -467,17 +468,17 @@ describe 'CopyTunerClient::I18nBackend' do
467
468
  it 'number.*.format は cache に値があってもローカル YAML を優先すること' do
468
469
  CopyTunerClient.configuration.local_first_key_regexp = nil
469
470
  cache['ja.number.currency.format.unit'] = '$'
470
- store_local(subject, :ja, number: { currency: { format: { unit: '円' } } })
471
+ store_local(backend, :ja, number: { currency: { format: { unit: '円' } } })
471
472
 
472
- expect(subject.translate('ja', 'number.currency.format')).to eq(unit: '円')
473
+ expect(backend.translate('ja', 'number.currency.format')).to eq(unit: '円')
473
474
  end
474
475
 
475
476
  it 'アプリ独自の number キーは従来どおり copy_tuner を優先すること' do
476
477
  CopyTunerClient.configuration.local_first_key_regexp = nil
477
478
  cache['ja.number.gift_amount'] = 'copy tuner value'
478
- store_local(subject, :ja, number: { gift_amount: 'local value' })
479
+ store_local(backend, :ja, number: { gift_amount: 'local value' })
479
480
 
480
- expect(subject.translate('ja', 'number.gift_amount')).to eq('copy tuner value')
481
+ expect(backend.translate('ja', 'number.gift_amount')).to eq('copy tuner value')
481
482
  end
482
483
  end
483
484
 
@@ -486,20 +487,20 @@ describe 'CopyTunerClient::I18nBackend' do
486
487
 
487
488
  it 'views.* は cache に値があってもローカル YAML を優先すること' do
488
489
  cache['ja.views.foo'] = 'copy tuner value'
489
- store_local(subject, :ja, views: { foo: 'local value' })
490
+ store_local(backend, :ja, views: { foo: 'local value' })
490
491
 
491
- expect(subject.translate('ja', 'views.foo')).to eq('local value')
492
+ expect(backend.translate('ja', 'views.foo')).to eq('local value')
492
493
  end
493
494
 
494
495
  it 'views.* 以外のキーは従来どおり copy_tuner を優先すること' do
495
496
  cache['ja.messages.foo'] = 'copy tuner value'
496
- store_local(subject, :ja, messages: { foo: 'local value' })
497
+ store_local(backend, :ja, messages: { foo: 'local value' })
497
498
 
498
- expect(subject.translate('ja', 'messages.foo')).to eq('copy tuner value')
499
+ expect(backend.translate('ja', 'messages.foo')).to eq('copy tuner value')
499
500
  end
500
501
 
501
502
  it 'views.* がローカルにも cache にも無いとき nil を返し、空キー登録(アップロード)をしないこと' do
502
- spy_cache = TestCache.new
503
+ spy_cache = test_cache_class.new
503
504
  allow(spy_cache).to receive(:[]=).and_call_original
504
505
  backend = CopyTunerClient::I18nBackend.new(spy_cache)
505
506
  I18n.backend = backend