copy_tuner_client 2.1.2 → 2.2.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +46 -1
  4. data/CHANGELOG.md +2 -235
  5. data/CLAUDE.md +1 -0
  6. data/Gemfile +1 -0
  7. data/README.md +42 -5
  8. data/copy_tuner_client.gemspec +3 -3
  9. data/gemfiles/8.0.gemfile +3 -3
  10. data/gemfiles/8.1.gemfile +3 -3
  11. data/gemfiles/main.gemfile +3 -3
  12. data/lib/copy_tuner_client/cache.rb +34 -27
  13. data/lib/copy_tuner_client/client.rb +35 -23
  14. data/lib/copy_tuner_client/configuration.rb +87 -40
  15. data/lib/copy_tuner_client/copyray/rewriter.rb +1 -1
  16. data/lib/copy_tuner_client/copyray.rb +1 -0
  17. data/lib/copy_tuner_client/copyray_middleware.rb +31 -24
  18. data/lib/copy_tuner_client/dotted_hash.rb +7 -7
  19. data/lib/copy_tuner_client/engine.rb +4 -6
  20. data/lib/copy_tuner_client/helper_extension.rb +58 -54
  21. data/lib/copy_tuner_client/i18n_backend.rb +12 -14
  22. data/lib/copy_tuner_client/i18n_compat.rb +2 -1
  23. data/lib/copy_tuner_client/poller.rb +3 -4
  24. data/lib/copy_tuner_client/prefixed_logger.rb +13 -12
  25. data/lib/copy_tuner_client/process_guard.rb +29 -26
  26. data/lib/copy_tuner_client/queue_with_timeout.rb +29 -19
  27. data/lib/copy_tuner_client/rails.rb +9 -8
  28. data/lib/copy_tuner_client/request_sync.rb +26 -24
  29. data/lib/copy_tuner_client/simple_form_extention.rb +10 -6
  30. data/lib/copy_tuner_client/translation_log.rb +10 -5
  31. data/lib/copy_tuner_client/version.rb +1 -1
  32. data/lib/copy_tuner_client.rb +4 -8
  33. data/lib/tasks/copy_tuner_client_tasks.rake +14 -16
  34. data/skills/copy-tuner-to-locales-cleanup/SKILL.md +5 -5
  35. data/skills/copy-tuner-to-locales-migrate-prefix/SKILL.md +69 -37
  36. data/skills/copy-tuner-to-locales-migrate-prefix/references/example-touchpoints.md +31 -10
  37. data/skills/copy-tuner-to-locales-migrate-prefix/scripts/migrate_prefix.rb +158 -13
  38. data/skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb +19 -17
  39. data/spec/copy_tuner_client/cache_spec.rb +63 -57
  40. data/spec/copy_tuner_client/client_spec.rb +82 -81
  41. data/spec/copy_tuner_client/configuration_spec.rb +380 -206
  42. data/spec/copy_tuner_client/copyray/marker_spec.rb +2 -1
  43. data/spec/copy_tuner_client/copyray/rewriter_spec.rb +15 -10
  44. data/spec/copy_tuner_client/copyray_middleware_spec.rb +9 -6
  45. data/spec/copy_tuner_client/copyray_spec.rb +8 -8
  46. data/spec/copy_tuner_client/dotted_hash_spec.rb +33 -33
  47. data/spec/copy_tuner_client/helper_extension_spec.rb +47 -44
  48. data/spec/copy_tuner_client/i18n_backend_spec.rb +144 -143
  49. data/spec/copy_tuner_client/poller_spec.rb +25 -24
  50. data/spec/copy_tuner_client/prefixed_logger_spec.rb +15 -12
  51. data/spec/copy_tuner_client/process_guard_spec.rb +27 -27
  52. data/spec/copy_tuner_client/request_sync_spec.rb +51 -58
  53. data/spec/copy_tuner_client/translation_log_spec.rb +38 -24
  54. data/spec/copy_tuner_client/warden_integration_spec.rb +95 -0
  55. data/spec/copy_tuner_client_spec.rb +5 -6
  56. data/spec/support/client_spec_helpers.rb +1 -1
  57. data/spec/support/defines_constants.rb +3 -34
  58. data/spec/support/fake_client.rb +1 -3
  59. data/spec/support/fake_copy_tuner_app.rb +54 -62
  60. data/spec/support/fake_html_safe_string.rb +2 -3
  61. data/spec/support/fake_logger.rb +22 -21
  62. data/spec/support/fake_passenger.rb +4 -6
  63. data/spec/support/fake_unicorn.rb +2 -4
  64. data/spec/support/middleware_stack.rb +38 -4
  65. data/spec/support/writing_cache.rb +4 -4
  66. metadata +2 -1
@@ -1,18 +1,71 @@
1
1
  require 'spec_helper'
2
2
 
3
+ shared_context 'stubbed configuration' do
4
+ subject(:configuration) { CopyTunerClient::Configuration.new }
5
+
6
+ let(:backend) { instance_double(CopyTunerClient::I18nBackend) }
7
+ let(:cache) { instance_double(CopyTunerClient::Cache, download: 'download') }
8
+ let(:logger) { FakeLogger.new }
9
+ let(:poller) { instance_double(CopyTunerClient::Poller) }
10
+ let(:process_guard) { instance_double(CopyTunerClient::ProcessGuard, start: nil) }
11
+
12
+ before do
13
+ allow(CopyTunerClient::I18nBackend).to receive(:new).and_return(backend)
14
+ allow(CopyTunerClient::Client).to receive(:new).and_return(instance_double(CopyTunerClient::Client))
15
+ allow(CopyTunerClient::Cache).to receive(:new).and_return(cache)
16
+ allow(CopyTunerClient::Poller).to receive(:new).and_return(poller)
17
+ allow(CopyTunerClient::ProcessGuard).to receive(:new).and_return(process_guard)
18
+ configuration.logger = logger
19
+ # NOTE: apply は project_id 必須になったため、未設定だと raise する。applied 系テストは
20
+ # project_id 自体を検証しないので適当な値を補っておく
21
+ configuration.project_id ||= 1
22
+ apply
23
+ end
24
+ end
25
+
26
+ shared_examples_for 'applied configuration' do
27
+ include_context 'stubbed configuration'
28
+
29
+ it { is_expected.to be_applied }
30
+
31
+ it 'builds and assigns an I18n backend' do
32
+ expect(CopyTunerClient::I18nBackend).to have_received(:new).with(cache)
33
+ expect(I18n.backend).to eq(backend)
34
+ end
35
+
36
+ it 'builds and assigns a poller' do
37
+ expect(CopyTunerClient::Poller).to have_received(:new).with(cache, configuration.to_hash)
38
+ end
39
+
40
+ it 'builds a process guard' do
41
+ expect(CopyTunerClient::ProcessGuard).to have_received(:new)
42
+ .with(cache, poller, configuration.to_hash)
43
+ end
44
+
45
+ it 'logs that it is ready' do
46
+ expect(logger).to have_entry(:info, "Client #{CopyTunerClient::VERSION} ready")
47
+ end
48
+
49
+ it 'logs environment info' do
50
+ expect(logger).to have_entry(:info, "Environment Info: #{configuration.environment_info}")
51
+ end
52
+ end
53
+
3
54
  describe CopyTunerClient::Configuration do
55
+ subject(:configuration) { described_class.new }
56
+
4
57
  RSpec::Matchers.define :have_config_option do |option|
5
58
  match do |config|
6
59
  expect(config).to respond_to(option)
7
60
 
8
- if instance_variables.include?(:'@default')
9
- expect(config.send(option)).to eq(@default)
10
- end
61
+ # .default チェーン指定時のみ既定値を検証する DSL のため条件分岐が必須
62
+ expect(config.public_send(option)).to eq(@default) if instance_variables.include?(:@default) # rubocop:disable Sgcop/Rspec/ConditionalExample
11
63
 
12
- if @overridable
64
+ # .overridable チェーン指定時のみ代入可否を検証する DSL のため条件分岐が必須
65
+ if @overridable # rubocop:disable Sgcop/Rspec/ConditionalExample
13
66
  value = 'a value'
14
- config.send(:"#{option}=", value)
15
- expect(config.send(option)).to eq(value)
67
+ config.public_send(:"#{option}=", value)
68
+ expect(config.public_send(option)).to eq(value)
16
69
  end
17
70
  end
18
71
 
@@ -47,142 +100,145 @@ describe CopyTunerClient::Configuration do
47
100
  it { is_expected.to have_config_option(:cache).overridable }
48
101
  it { is_expected.to have_config_option(:local_first_key_regexp).overridable.default(nil) }
49
102
 
50
- it 'should provide default values for secure connections' do
51
- config = CopyTunerClient::Configuration.new
103
+ it 'provides default values for secure connections' do
104
+ config = described_class.new
52
105
  config.secure = true
53
106
  expect(config.port).to eq(443)
54
107
  expect(config.protocol).to eq('https')
55
108
  end
56
109
 
57
- it 'should provide default values for insecure connections' do
58
- config = CopyTunerClient::Configuration.new
110
+ it 'provides default values for insecure connections' do
111
+ config = described_class.new
59
112
  config.secure = false
60
113
  expect(config.port).to eq(80)
61
114
  expect(config.protocol).to eq('http')
62
115
  end
63
116
 
64
- it 'should not cache inferred ports' do
65
- config = CopyTunerClient::Configuration.new
117
+ it 'does not cache inferred ports' do
118
+ config = described_class.new
66
119
  config.secure = false
67
120
  config.port
68
121
  config.secure = true
69
122
  expect(config.port).to eq(443)
70
123
  end
71
124
 
72
- it 'should act like a hash' do
73
- config = CopyTunerClient::Configuration.new
125
+ it 'acts like a hash' do
126
+ config = described_class.new
74
127
  hash = config.to_hash
75
128
 
76
- [:api_key, :environment_name, :host, :http_open_timeout,
77
- :http_read_timeout, :client_name, :client_url, :client_version, :port,
78
- :protocol, :proxy_host, :proxy_pass, :proxy_port, :proxy_user, :secure,
79
- :development_environments, :logger, :framework, :ca_file].each do |option|
80
- expect(hash[option]).to eq(config[option])
129
+ # hash が元 config の各 option と同期していることを検証するため、比較先をリテラルに書き出せない
130
+ %i[
131
+ api_key environment_name host http_open_timeout
132
+ http_read_timeout client_name client_url client_version port
133
+ protocol proxy_host proxy_pass proxy_port proxy_user secure
134
+ development_environments logger framework ca_file
135
+ ].each do |option|
136
+ expect(hash[option]).to eq(config[option]) # rubocop:disable Sgcop/Rspec/NoMethodCallInExpectation
81
137
  end
82
138
 
83
- expect(hash[:public]).to eq(config.public?)
139
+ expect(hash[:public]).to eq(config.public?) # rubocop:disable Sgcop/Rspec/NoMethodCallInExpectation
84
140
  end
85
141
 
86
- it 'should be mergable' do
87
- config = CopyTunerClient::Configuration.new
142
+ it 'is mergable' do
143
+ config = described_class.new
88
144
  hash = config.to_hash
89
- expect(config.merge(:key => 'value')).to eq(hash.merge(:key => 'value'))
145
+ expect(config.merge(key: 'value')).to eq(hash.merge(key: 'value')) # rubocop:disable Sgcop/Rspec/NoMethodCallInExpectation
90
146
  end
91
147
 
92
- it 'should use development and staging as development environments by default' do
93
- config = CopyTunerClient::Configuration.new
94
- expect(config.development_environments).to match_array(%w(development staging))
148
+ it 'uses development and staging as development environments by default' do
149
+ config = described_class.new
150
+ expect(config.development_environments).to match_array(%w[development staging])
95
151
  end
96
152
 
97
- it 'should use test and cucumber as test environments by default' do
98
- config = CopyTunerClient::Configuration.new
99
- expect(config.test_environments).to match_array(%w(test cucumber))
153
+ it 'uses test and cucumber as test environments by default' do
154
+ config = described_class.new
155
+ expect(config.test_environments).to match_array(%w[test cucumber])
100
156
  end
101
157
 
102
- it 'should be test in a test environment' do
103
- config = CopyTunerClient::Configuration.new
104
- config.test_environments = %w(test)
158
+ it 'is test in a test environment' do
159
+ config = described_class.new
160
+ config.test_environments = %w[test]
105
161
  config.environment_name = 'test'
106
162
  expect(config).to be_test
107
163
  end
108
164
 
109
- it 'should be public in a public environment' do
110
- config = CopyTunerClient::Configuration.new
111
- config.development_environments = %w(development)
165
+ it 'is public in a public environment' do
166
+ config = described_class.new
167
+ config.development_environments = %w[development]
112
168
  config.environment_name = 'production'
113
169
  expect(config).to be_public
114
170
  expect(config).not_to be_development
115
171
  end
116
172
 
117
- it 'should be development in a development environment' do
118
- config = CopyTunerClient::Configuration.new
119
- config.development_environments = %w(staging)
173
+ it 'is development in a development environment' do
174
+ config = described_class.new
175
+ config.development_environments = %w[staging]
120
176
  config.environment_name = 'staging'
121
177
  expect(config).to be_development
122
178
  expect(config).not_to be_public
123
179
  end
124
180
 
125
- it 'should be public without an environment name' do
126
- config = CopyTunerClient::Configuration.new
181
+ it 'is public without an environment name' do
182
+ config = described_class.new
127
183
  expect(config).to be_public
128
184
  end
129
185
 
130
- it 'should yield and save a configuration when configuring' do
186
+ it 'yields and save a configuration when configuring' do
131
187
  yielded_configuration = nil
132
188
 
133
- CopyTunerClient.configure(false) do |config|
189
+ CopyTunerClient.configure(apply: false) do |config|
134
190
  yielded_configuration = config
135
191
  end
136
192
 
137
- expect(yielded_configuration).to be_kind_of(CopyTunerClient::Configuration)
193
+ expect(yielded_configuration).to be_a(described_class)
138
194
  expect(CopyTunerClient.configuration).to eq(yielded_configuration)
139
195
  end
140
196
 
141
197
  it 'does not apply the configuration when asked not to' do
142
198
  logger = FakeLogger.new
143
- CopyTunerClient.configure(false) { |config| config.logger = logger }
199
+ CopyTunerClient.configure(apply: false) { |config| config.logger = logger }
144
200
  expect(CopyTunerClient.configuration).not_to be_applied
145
201
  expect(logger.entries[:info]).to be_empty
146
202
  end
147
203
 
148
- it 'should not remove existing config options when configuring twice' do
204
+ it 'does not remove existing config options when configuring twice' do
149
205
  first_config = nil
150
206
 
151
- CopyTunerClient.configure(false) do |config|
207
+ CopyTunerClient.configure(apply: false) do |config|
152
208
  first_config = config
153
209
  end
154
210
 
155
- CopyTunerClient.configure(false) do |config|
211
+ CopyTunerClient.configure(apply: false) do |config|
156
212
  expect(config).to eq(first_config)
157
213
  end
158
214
  end
159
215
 
160
216
  it 'starts out unapplied' do
161
- expect(CopyTunerClient::Configuration.new).not_to be_applied
217
+ expect(described_class.new).not_to be_applied
162
218
  end
163
219
 
164
220
  it 'logs to $stdout by default' do
165
221
  logger = FakeLogger.new
166
- expect(Logger).to receive(:new).with($stdout).and_return(logger)
167
- config = CopyTunerClient::Configuration.new
222
+ allow(Logger).to receive(:new).with($stdout).and_return(logger)
223
+ config = described_class.new
168
224
  expect(config.logger.original_logger).to eq(logger)
169
225
  end
170
226
 
171
227
  it 'generates environment info without a framework' do
172
- subject.environment_name = 'production'
173
- expect(subject.environment_info).to eq("[Ruby: #{RUBY_VERSION}] [Env: production]")
228
+ configuration.environment_name = 'production'
229
+ expect(configuration.environment_info).to eq("[Ruby: #{RUBY_VERSION}] [Env: production]")
174
230
  end
175
231
 
176
232
  it 'generates environment info with a framework' do
177
- subject.environment_name = 'production'
178
- subject.framework = 'Sinatra: 1.0.0'
179
- expect(subject.environment_info).
180
- to eq("[Ruby: #{RUBY_VERSION}] [Sinatra: 1.0.0] [Env: production]")
233
+ configuration.environment_name = 'production'
234
+ configuration.framework = 'Sinatra: 1.0.0'
235
+ expect(configuration.environment_info)
236
+ .to eq("[Ruby: #{RUBY_VERSION}] [Sinatra: 1.0.0] [Env: production]")
181
237
  end
182
238
 
183
239
  it 'prefixes log entries' do
184
240
  logger = FakeLogger.new
185
- config = CopyTunerClient::Configuration.new
241
+ config = described_class.new
186
242
 
187
243
  config.logger = logger
188
244
 
@@ -192,29 +248,29 @@ describe CopyTunerClient::Configuration do
192
248
  end
193
249
 
194
250
  describe '#local_first_key?' do
195
- let(:config) { CopyTunerClient::Configuration.new }
251
+ let(:config) { described_class.new }
196
252
 
197
253
  it 'returns false when local_first_key_regexp is nil (default)' do
198
- expect(config.local_first_key?('views.foo.bar')).to eq false
254
+ expect(config.local_first_key?('views.foo.bar')).to be false
199
255
  end
200
256
 
201
257
  context 'when local_first_key_regexp is set' do
202
258
  before { config.local_first_key_regexp = /\Aviews\./ }
203
259
 
204
260
  it 'returns true for a matching key' do
205
- expect(config.local_first_key?('views.foo.bar')).to eq true
261
+ expect(config.local_first_key?('views.foo.bar')).to be true
206
262
  end
207
263
 
208
264
  it 'returns false for a non-matching key' do
209
- expect(config.local_first_key?('models.foo.bar')).to eq false
265
+ expect(config.local_first_key?('models.foo.bar')).to be false
210
266
  end
211
267
 
212
268
  it 'returns false for a nil key' do
213
- expect(config.local_first_key?(nil)).to eq false
269
+ expect(config.local_first_key?(nil)).to be false
214
270
  end
215
271
 
216
272
  it 'coerces a Symbol key before matching' do
217
- expect(config.local_first_key?(:'views.foo')).to eq true
273
+ expect(config.local_first_key?(:'views.foo')).to be true
218
274
  end
219
275
  end
220
276
 
@@ -222,40 +278,40 @@ describe CopyTunerClient::Configuration do
222
278
  # ユーザー設定の有無によらず常にローカル優先(組み込み判定)になる
223
279
  context 'with built-in Rails number format keys' do
224
280
  it 'returns true for built-in number format keys even when local_first_key_regexp is nil' do
225
- expect(config.local_first_key?('number.format')).to eq true
226
- expect(config.local_first_key?('number.currency.format')).to eq true
227
- expect(config.local_first_key?('number.currency.format.precision')).to eq true
228
- expect(config.local_first_key?('number.percentage.format')).to eq true
229
- expect(config.local_first_key?('number.human.format.significant')).to eq true
281
+ expect(config.local_first_key?('number.format')).to be true
282
+ expect(config.local_first_key?('number.currency.format')).to be true
283
+ expect(config.local_first_key?('number.currency.format.precision')).to be true
284
+ expect(config.local_first_key?('number.percentage.format')).to be true
285
+ expect(config.local_first_key?('number.human.format.significant')).to be true
230
286
  end
231
287
 
232
288
  it 'returns false for app-defined number keys (not Rails format subtrees)' do
233
- expect(config.local_first_key?('number.gift_amount')).to eq false
234
- expect(config.local_first_key?('number.my_currency.unit')).to eq false
289
+ expect(config.local_first_key?('number.gift_amount')).to be false
290
+ expect(config.local_first_key?('number.my_currency.unit')).to be false
235
291
  end
236
292
 
237
293
  it 'returns false for string-only number subtrees and non-number keys' do
238
- expect(config.local_first_key?('number.human.storage_units.units.byte.one')).to eq false
239
- expect(config.local_first_key?('date.formats.default')).to eq false
240
- expect(config.local_first_key?('time.formats.short')).to eq false
241
- expect(config.local_first_key?('datetime.distance_in_words.x')).to eq false
242
- expect(config.local_first_key?('views.foo')).to eq false
243
- expect(config.local_first_key?('numbers.foo')).to eq false
294
+ expect(config.local_first_key?('number.human.storage_units.units.byte.one')).to be false
295
+ expect(config.local_first_key?('date.formats.default')).to be false
296
+ expect(config.local_first_key?('time.formats.short')).to be false
297
+ expect(config.local_first_key?('datetime.distance_in_words.x')).to be false
298
+ expect(config.local_first_key?('views.foo')).to be false
299
+ expect(config.local_first_key?('numbers.foo')).to be false
244
300
  end
245
301
 
246
302
  it 'keeps protecting built-in keys without breaking a user-set regexp' do
247
303
  config.local_first_key_regexp = /\Aviews\./
248
304
 
249
- expect(config.local_first_key?('number.currency.format')).to eq true
250
- expect(config.local_first_key?('views.foo')).to eq true
251
- expect(config.local_first_key?('models.foo')).to eq false
305
+ expect(config.local_first_key?('number.currency.format')).to be true
306
+ expect(config.local_first_key?('views.foo')).to be true
307
+ expect(config.local_first_key?('models.foo')).to be false
252
308
  end
253
309
  end
254
310
  end
255
311
 
256
312
  describe 'project_id の必須化' do
257
313
  let(:config) do
258
- config = CopyTunerClient::Configuration.new
314
+ config = described_class.new
259
315
  config.api_key = 'abc123'
260
316
  config
261
317
  end
@@ -273,186 +329,304 @@ describe CopyTunerClient::Configuration do
273
329
  expect(config.project_url).to include('/projects/77')
274
330
  end
275
331
  end
276
- end
277
332
 
278
- shared_context 'stubbed configuration' do
279
- subject { CopyTunerClient::Configuration.new }
280
- let(:backend) { double('i18n-backend') }
281
- let(:cache) { double('cache', download: "download") }
282
- let(:client) { double('client') }
283
- let(:logger) { FakeLogger.new }
284
- let(:poller) { double('poller') }
285
- let(:process_guard) { double('process_guard', start: nil) }
333
+ context 'applied when testing' do
334
+ it_behaves_like 'applied configuration' do
335
+ it 'does not start the process guard' do
336
+ expect(process_guard).not_to have_received(:start)
337
+ end
338
+ end
286
339
 
287
- before do
288
- allow(CopyTunerClient::I18nBackend).to receive(:new).and_return(backend)
289
- allow(CopyTunerClient::Client).to receive(:new).and_return(client)
290
- allow(CopyTunerClient::Cache).to receive(:new).and_return(cache)
291
- allow(CopyTunerClient::Poller).to receive(:new).and_return(poller)
292
- allow(CopyTunerClient::ProcessGuard).to receive(:new).and_return(process_guard)
293
- subject.logger = logger
294
- # NOTE: apply は project_id 必須になったため、未設定だと raise する。applied 系テストは
295
- # project_id 自体を検証しないので適当な値を補っておく
296
- subject.project_id ||= 1
297
- apply
340
+ def apply
341
+ configuration.environment_name = 'test'
342
+ configuration.apply
343
+ end
298
344
  end
299
- end
300
-
301
- shared_examples_for 'applied configuration' do
302
- include_context 'stubbed configuration'
303
345
 
304
- it { is_expected.to be_applied }
346
+ context 'applied when not testing' do
347
+ it_behaves_like 'applied configuration' do
348
+ it 'starts the process guard' do
349
+ expect(process_guard).to have_received(:start)
350
+ end
351
+ end
305
352
 
306
- it 'builds and assigns an I18n backend' do
307
- expect(CopyTunerClient::I18nBackend).to have_received(:new).with(cache)
308
- expect(I18n.backend).to eq(backend)
353
+ def apply
354
+ configuration.environment_name = 'development'
355
+ configuration.apply
356
+ end
309
357
  end
310
358
 
311
- it 'builds and assigns a poller' do
312
- expect(CopyTunerClient::Poller).to have_received(:new).with(cache, subject.to_hash)
313
- end
359
+ context 'applied when developing with middleware' do
360
+ it_behaves_like 'applied configuration' do
361
+ it 'adds the sync middleware' do
362
+ expect(middleware).to include(CopyTunerClient::RequestSync)
363
+ end
364
+ end
314
365
 
315
- it 'builds a process guard' do
316
- expect(CopyTunerClient::ProcessGuard).to have_received(:new).
317
- with(cache, poller, subject.to_hash)
318
- end
366
+ let(:middleware) { MiddlewareStack.new }
319
367
 
320
- it 'logs that it is ready' do
321
- expect(logger).to have_entry(:info, "Client #{CopyTunerClient::VERSION} ready")
368
+ def apply
369
+ configuration.middleware = middleware
370
+ configuration.environment_name = 'development'
371
+ configuration.apply
372
+ end
322
373
  end
323
374
 
324
- it 'logs environment info' do
325
- expect(logger).to have_entry(:info, "Environment Info: #{subject.environment_info}")
326
- end
327
- end
375
+ context 'applied when developing without middleware' do
376
+ it_behaves_like 'applied configuration'
328
377
 
329
- describe CopyTunerClient::Configuration, 'applied when testing' do
330
- it_should_behave_like 'applied configuration' do
331
- it 'does not start the process guard' do
332
- expect(process_guard).not_to receive(:start)
378
+ def apply
379
+ configuration.middleware = nil
380
+ configuration.environment_name = 'development'
381
+ configuration.apply
333
382
  end
334
383
  end
335
384
 
336
- def apply
337
- subject.environment_name = 'test'
338
- subject.apply
339
- end
340
- end
385
+ context 'applied with middleware when not developing' do
386
+ let(:middleware) { MiddlewareStack.new }
341
387
 
342
- describe CopyTunerClient::Configuration, 'applied when not testing' do
343
- it_should_behave_like 'applied configuration' do
344
- it 'starts the process guard' do
345
- expect(process_guard).to have_received(:start)
388
+ it_behaves_like 'applied configuration'
389
+
390
+ def apply
391
+ configuration.middleware = middleware
392
+ configuration.environment_name = 'test'
393
+ configuration.apply
346
394
  end
347
- end
348
395
 
349
- def apply
350
- subject.environment_name = 'development'
351
- subject.apply
396
+ it 'does not add the sync middleware' do
397
+ expect(middleware).not_to include(CopyTunerClient::RequestSync)
398
+ end
352
399
  end
353
- end
354
400
 
355
- describe CopyTunerClient::Configuration, 'applied when developing with middleware' do
356
- it_should_behave_like 'applied configuration' do
357
- it 'adds the sync middleware' do
358
- expect(middleware).to include(CopyTunerClient::RequestSync)
401
+ describe 'middleware_position の初期値' do
402
+ let(:config) { described_class.new }
403
+
404
+ context 'Warden::Manager と Devise がどちらも定義済みのとき' do
405
+ before do
406
+ stub_const('Warden::Manager', Class.new)
407
+ stub_const('Devise', Module.new)
408
+ end
409
+
410
+ it '{ before: Warden::Manager } になる' do
411
+ expect(config.middleware_position).to eq({ before: Warden::Manager })
412
+ end
359
413
  end
360
- end
361
414
 
362
- let(:middleware) { MiddlewareStack.new }
415
+ context 'Warden::Manager が未定義のとき' do
416
+ before { hide_const('Warden::Manager') }
363
417
 
364
- def apply
365
- subject.middleware = middleware
366
- subject.environment_name = 'development'
367
- subject.apply
368
- end
369
- end
418
+ it 'nil のままになる' do
419
+ expect(config.middleware_position).to be_nil
420
+ end
421
+ end
422
+
423
+ # NOTE: authtrail のように warden を require するだけで Warden::Manager をスタックへ積まない
424
+ # gem があるため、Warden 単独では既定位置にしない(insert_before が起動時例外になる)。
425
+ context 'Warden::Manager は定義済みだが Devise が未定義のとき' do
426
+ before do
427
+ stub_const('Warden::Manager', Class.new)
428
+ hide_const('Devise')
429
+ end
370
430
 
371
- describe CopyTunerClient::Configuration, 'applied when developing without middleware' do
372
- it_should_behave_like 'applied configuration'
431
+ it 'nil のままになる' do
432
+ expect(config.middleware_position).to be_nil
433
+ end
434
+ end
373
435
 
374
- def apply
375
- subject.middleware = nil
376
- subject.environment_name = 'development'
377
- subject.apply
436
+ context 'configure ブロック内で明示指定したとき' do
437
+ before do
438
+ stub_const('Warden::Manager', Class.new)
439
+ stub_const('Devise', Module.new)
440
+ end
441
+
442
+ it '明示指定した値が優先される' do
443
+ other = Class.new
444
+ CopyTunerClient.configure(apply: false) do |c|
445
+ c.middleware_position = { before: other }
446
+ end
447
+ expect(CopyTunerClient.configuration.middleware_position).to eq({ before: other })
448
+ end
449
+ end
378
450
  end
379
- end
380
451
 
381
- describe CopyTunerClient::Configuration, 'applied with middleware when not developing' do
382
- it_should_behave_like 'applied configuration'
452
+ context 'Warden::Manager がスタックにある状態で apply したとき' do
453
+ it_behaves_like 'applied configuration' do
454
+ it 'デフォルト値経由で Warden::Manager の直前に RequestSync → CopyrayMiddleware の順で入る' do
455
+ expect(middleware.classes).to eq(
456
+ [
457
+ Rack::ETag, Rack::TempfileReaper, CopyTunerClient::RequestSync, CopyTunerClient::CopyrayMiddleware,
458
+ Warden::Manager, Rack::Static
459
+ ]
460
+ )
461
+ end
383
462
 
384
- let(:middleware) { MiddlewareStack.new }
463
+ it 'Warden::Manager より外側に配置される' do
464
+ expect(middleware.index(CopyTunerClient::RequestSync)).to be < middleware.index(Warden::Manager)
465
+ expect(middleware.index(CopyTunerClient::CopyrayMiddleware)).to be < middleware.index(Warden::Manager)
466
+ end
385
467
 
386
- def apply
387
- subject.middleware = middleware
388
- subject.environment_name = 'test'
389
- subject.apply
390
- end
468
+ it 'RequestSync に poller / cache / interval / ignore_regex が渡る' do
469
+ args = middleware.args_for(CopyTunerClient::RequestSync)
470
+ expect(args.first).to include(
471
+ poller:, # rubocop:disable Sgcop/Rspec/NoMethodCallInExpectation
472
+ cache:,
473
+ interval: configuration.sync_interval,
474
+ ignore_regex: configuration.sync_ignore_path_regex
475
+ )
476
+ end
477
+ end
391
478
 
392
- it 'does not add the sync middleware' do
393
- expect(middleware).not_to include(CopyTunerClient::RequestSync)
394
- end
395
- end
479
+ # NOTE: 実アプリの bin/rails middleware 出力を模した標準スタック。Warden::Manager / Devise は
480
+ # gem の依存にないため stub_const で fake の定数として定義する。
481
+ before do
482
+ stub_const('Warden::Manager', Class.new)
483
+ stub_const('Devise', Module.new)
484
+ end
396
485
 
397
- describe CopyTunerClient::Configuration, 'applied without locale filter' do
398
- include_context 'stubbed configuration'
486
+ let(:middleware) do
487
+ MiddlewareStack.new([Rack::ETag, Rack::TempfileReaper, Warden::Manager, Rack::Static])
488
+ end
399
489
 
400
- def apply
401
- subject.apply
490
+ def apply
491
+ configuration.middleware = middleware
492
+ configuration.environment_name = 'development'
493
+ configuration.apply
494
+ end
402
495
  end
403
496
 
404
- it 'should have locales [:en]' do
405
- expect(subject.locales).to eq [:en]
406
- end
407
- end
497
+ context 'Warden::Manager が未定義の状態で apply したとき' do
498
+ it_behaves_like 'applied configuration' do
499
+ it 'Warden::Manager 未定義時は従来どおりスタック末尾へ use される' do
500
+ expect(middleware.classes).to eq([CopyTunerClient::RequestSync, CopyTunerClient::CopyrayMiddleware])
501
+ end
502
+ end
408
503
 
409
- describe CopyTunerClient::Configuration, 'applied with locale filter' do
410
- include_context 'stubbed configuration'
504
+ before { hide_const('Warden::Manager') }
411
505
 
412
- def apply
413
- subject.locales = %i(en ja)
414
- subject.apply
415
- end
506
+ let(:middleware) { MiddlewareStack.new }
416
507
 
417
- it 'should have locales %i(en ja)' do
418
- expect(subject.locales).to eq %i(en ja)
508
+ def apply
509
+ configuration.middleware = middleware
510
+ configuration.environment_name = 'development'
511
+ configuration.apply
512
+ end
419
513
  end
420
- end
421
514
 
422
- describe CopyTunerClient::Configuration, 'applied with Rails i18n config' do
423
- let!(:rails_defined) { Object.const_defined?(:Rails) }
515
+ context 'middleware_position after を明示指定して apply したとき' do
516
+ it_behaves_like 'applied configuration' do
517
+ it '{after: X} 指定時は X の直後に RequestSync → CopyrayMiddleware の順で入る' do
518
+ expect(middleware.classes).to eq(
519
+ [:a, :x, CopyTunerClient::RequestSync, CopyTunerClient::CopyrayMiddleware, :b]
520
+ )
521
+ end
522
+ end
424
523
 
425
- def self.with_config(i18n_options)
426
- before do
427
- Object.const_set :Rails, Module.new unless rails_defined
428
- i18n = double('i18n', i18n_options)
429
- allow(Rails).to receive_message_chain(:application, :config, :i18n) { i18n }
524
+ let(:middleware) { MiddlewareStack.new(%i[a x b]) }
525
+
526
+ def apply
527
+ configuration.middleware = middleware
528
+ configuration.middleware_position = { after: :x }
529
+ configuration.environment_name = 'development'
530
+ configuration.apply
430
531
  end
532
+ end
533
+
534
+ context 'middleware_position に before を明示指定して apply したとき' do
535
+ it_behaves_like 'applied configuration' do
536
+ it '{before: X} 指定時は X の直前に RequestSync → CopyrayMiddleware の順で入る' do
537
+ expect(middleware.classes).to eq(
538
+ [:a, CopyTunerClient::RequestSync, CopyTunerClient::CopyrayMiddleware, :x, :b]
539
+ )
540
+ end
541
+ end
542
+
543
+ let(:middleware) { MiddlewareStack.new(%i[a x b]) }
431
544
 
432
- after do
433
- Object.send(:remove_const, :Rails) unless rails_defined
545
+ def apply
546
+ configuration.middleware = middleware
547
+ configuration.middleware_position = { before: :x }
548
+ configuration.environment_name = 'development'
549
+ configuration.apply
434
550
  end
435
551
  end
436
552
 
437
- def apply
438
- subject.apply
553
+ # NOTE: { before: SomeClass if cond } のように条件次第で値が nil になる書き方を想定する。
554
+ # キーの有無だけで分岐すると insert_before(nil) が対象を見つけられず例外になる。
555
+ context 'middleware_position の値が nil のとき' do
556
+ it_behaves_like 'applied configuration' do
557
+ it '例外を投げずスタック末尾へ use する' do
558
+ expect(middleware.classes).to eq(
559
+ [:a, :x, :b, CopyTunerClient::RequestSync, CopyTunerClient::CopyrayMiddleware]
560
+ )
561
+ end
562
+ end
563
+
564
+ let(:middleware) { MiddlewareStack.new(%i[a x b]) }
565
+
566
+ def apply
567
+ configuration.middleware = middleware
568
+ configuration.middleware_position = { before: nil }
569
+ configuration.environment_name = 'development'
570
+ configuration.apply
571
+ end
439
572
  end
440
573
 
441
- context 'with available_locales' do
442
- with_config(available_locales: %i(en ja))
574
+ context 'applied without locale filter' do
443
575
  include_context 'stubbed configuration'
444
576
 
445
- it 'should have locales %i(en ja)' do
446
- expect(subject.locales).to eq %i(en ja)
577
+ def apply
578
+ configuration.apply
579
+ end
580
+
581
+ it 'has locales [:en]' do
582
+ expect(configuration.locales).to eq [:en]
447
583
  end
448
584
  end
449
585
 
450
- context 'with default_locale' do
451
- with_config(available_locales: %i(ja))
586
+ context 'applied with locale filter' do
452
587
  include_context 'stubbed configuration'
453
588
 
454
- it 'should have locales %i(ja)' do
455
- expect(subject.locales).to eq %i(ja)
589
+ def apply
590
+ configuration.locales = %i[en ja]
591
+ configuration.apply
592
+ end
593
+
594
+ it 'has locales %i(en ja)' do
595
+ expect(configuration.locales).to eq %i[en ja]
596
+ end
597
+ end
598
+
599
+ context 'applied with Rails i18n config' do
600
+ def self.with_config(i18n_options)
601
+ before do
602
+ stub_const('Rails', Module.new)
603
+ i18n = double('i18n', i18n_options)
604
+ config = double('config', i18n:)
605
+ application = double('application', config:)
606
+ allow(Rails).to receive(:application).and_return(application)
607
+ end
608
+ end
609
+
610
+ def apply
611
+ configuration.apply
612
+ end
613
+
614
+ context 'with available_locales' do
615
+ with_config(available_locales: %i[en ja])
616
+ include_context 'stubbed configuration'
617
+
618
+ it 'has locales %i(en ja)' do
619
+ expect(configuration.locales).to eq %i[en ja]
620
+ end
621
+ end
622
+
623
+ context 'with default_locale' do
624
+ with_config(available_locales: %i[ja])
625
+ include_context 'stubbed configuration'
626
+
627
+ it 'has locales %i(ja)' do
628
+ expect(configuration.locales).to eq %i[ja]
629
+ end
456
630
  end
457
631
  end
458
632
  end