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.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/.rubocop.yml +46 -1
- data/CHANGELOG.md +2 -235
- data/CLAUDE.md +1 -0
- data/Gemfile +1 -0
- data/README.md +42 -5
- data/copy_tuner_client.gemspec +3 -3
- data/gemfiles/8.0.gemfile +3 -3
- data/gemfiles/8.1.gemfile +3 -3
- data/gemfiles/main.gemfile +3 -3
- data/lib/copy_tuner_client/cache.rb +34 -27
- data/lib/copy_tuner_client/client.rb +35 -23
- data/lib/copy_tuner_client/configuration.rb +87 -40
- data/lib/copy_tuner_client/copyray/rewriter.rb +1 -1
- data/lib/copy_tuner_client/copyray.rb +1 -0
- data/lib/copy_tuner_client/copyray_middleware.rb +31 -24
- data/lib/copy_tuner_client/dotted_hash.rb +7 -7
- data/lib/copy_tuner_client/engine.rb +4 -6
- data/lib/copy_tuner_client/helper_extension.rb +58 -54
- data/lib/copy_tuner_client/i18n_backend.rb +12 -14
- data/lib/copy_tuner_client/i18n_compat.rb +2 -1
- data/lib/copy_tuner_client/poller.rb +3 -4
- data/lib/copy_tuner_client/prefixed_logger.rb +13 -12
- data/lib/copy_tuner_client/process_guard.rb +29 -26
- data/lib/copy_tuner_client/queue_with_timeout.rb +29 -19
- data/lib/copy_tuner_client/rails.rb +9 -8
- data/lib/copy_tuner_client/request_sync.rb +26 -24
- data/lib/copy_tuner_client/simple_form_extention.rb +10 -6
- data/lib/copy_tuner_client/translation_log.rb +10 -5
- data/lib/copy_tuner_client/version.rb +1 -1
- data/lib/copy_tuner_client.rb +4 -8
- data/lib/tasks/copy_tuner_client_tasks.rake +14 -16
- data/skills/copy-tuner-to-locales-cleanup/SKILL.md +5 -5
- data/skills/copy-tuner-to-locales-migrate-prefix/SKILL.md +69 -37
- data/skills/copy-tuner-to-locales-migrate-prefix/references/example-touchpoints.md +31 -10
- data/skills/copy-tuner-to-locales-migrate-prefix/scripts/migrate_prefix.rb +158 -13
- data/skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb +19 -17
- data/spec/copy_tuner_client/cache_spec.rb +63 -57
- data/spec/copy_tuner_client/client_spec.rb +82 -81
- data/spec/copy_tuner_client/configuration_spec.rb +380 -206
- data/spec/copy_tuner_client/copyray/marker_spec.rb +2 -1
- data/spec/copy_tuner_client/copyray/rewriter_spec.rb +15 -10
- data/spec/copy_tuner_client/copyray_middleware_spec.rb +9 -6
- data/spec/copy_tuner_client/copyray_spec.rb +8 -8
- data/spec/copy_tuner_client/dotted_hash_spec.rb +33 -33
- data/spec/copy_tuner_client/helper_extension_spec.rb +47 -44
- data/spec/copy_tuner_client/i18n_backend_spec.rb +144 -143
- data/spec/copy_tuner_client/poller_spec.rb +25 -24
- data/spec/copy_tuner_client/prefixed_logger_spec.rb +15 -12
- data/spec/copy_tuner_client/process_guard_spec.rb +27 -27
- data/spec/copy_tuner_client/request_sync_spec.rb +51 -58
- data/spec/copy_tuner_client/translation_log_spec.rb +38 -24
- data/spec/copy_tuner_client/warden_integration_spec.rb +95 -0
- data/spec/copy_tuner_client_spec.rb +5 -6
- data/spec/support/client_spec_helpers.rb +1 -1
- data/spec/support/defines_constants.rb +3 -34
- data/spec/support/fake_client.rb +1 -3
- data/spec/support/fake_copy_tuner_app.rb +54 -62
- data/spec/support/fake_html_safe_string.rb +2 -3
- data/spec/support/fake_logger.rb +22 -21
- data/spec/support/fake_passenger.rb +4 -6
- data/spec/support/fake_unicorn.rb +2 -4
- data/spec/support/middleware_stack.rb +38 -4
- data/spec/support/writing_cache.rb +4 -4
- metadata +2 -1
|
@@ -1,33 +1,32 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
describe CopyTunerClient::Poller do
|
|
4
|
-
POLLING_DELAY = 0.5
|
|
5
|
-
|
|
6
4
|
let(:client) { FakeClient.new }
|
|
7
|
-
let(:cache) { CopyTunerClient::Cache.new(client, :
|
|
5
|
+
let(:cache) { CopyTunerClient::Cache.new(client, logger: FakeLogger.new) }
|
|
6
|
+
let!(:pollers) { [] }
|
|
7
|
+
|
|
8
|
+
def polling_delay
|
|
9
|
+
0.5
|
|
10
|
+
end
|
|
8
11
|
|
|
9
12
|
def build_poller(config = {})
|
|
10
13
|
config[:logger] ||= FakeLogger.new
|
|
11
|
-
config[:polling_delay] =
|
|
14
|
+
config[:polling_delay] = polling_delay
|
|
12
15
|
default_config = CopyTunerClient::Configuration.new.to_hash
|
|
13
16
|
poller = CopyTunerClient::Poller.new(cache, default_config.update(config))
|
|
14
|
-
|
|
17
|
+
pollers << poller
|
|
15
18
|
poller
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
def wait_for_next_sync
|
|
19
|
-
sleep(
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
before do
|
|
23
|
-
@pollers = []
|
|
22
|
+
sleep(polling_delay * 3)
|
|
24
23
|
end
|
|
25
24
|
|
|
26
25
|
after do
|
|
27
|
-
|
|
26
|
+
pollers.each(&:stop)
|
|
28
27
|
end
|
|
29
28
|
|
|
30
|
-
it
|
|
29
|
+
it 'polls after being started' do
|
|
31
30
|
poller = build_poller
|
|
32
31
|
poller.start
|
|
33
32
|
|
|
@@ -37,8 +36,8 @@ describe CopyTunerClient::Poller do
|
|
|
37
36
|
expect(cache['test.key']).to eq('value')
|
|
38
37
|
end
|
|
39
38
|
|
|
40
|
-
it "
|
|
41
|
-
|
|
39
|
+
it "doesn't poll before being started" do
|
|
40
|
+
build_poller
|
|
42
41
|
client['test.key'] = 'value'
|
|
43
42
|
|
|
44
43
|
wait_for_next_sync
|
|
@@ -46,7 +45,7 @@ describe CopyTunerClient::Poller do
|
|
|
46
45
|
expect(cache['test.key']).to be_nil
|
|
47
46
|
end
|
|
48
47
|
|
|
49
|
-
it
|
|
48
|
+
it 'stops polling when stopped' do
|
|
50
49
|
poller = build_poller
|
|
51
50
|
|
|
52
51
|
poller.start
|
|
@@ -58,12 +57,12 @@ describe CopyTunerClient::Poller do
|
|
|
58
57
|
expect(cache['test.key']).to be_nil
|
|
59
58
|
end
|
|
60
59
|
|
|
61
|
-
it
|
|
62
|
-
failure =
|
|
60
|
+
it 'stops polling with an invalid api key' do
|
|
61
|
+
failure = 'server is napping'
|
|
63
62
|
logger = FakeLogger.new
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
poller = build_poller(:
|
|
64
|
+
allow(cache).to receive(:download).and_raise(CopyTunerClient::InvalidApiKey.new(failure))
|
|
65
|
+
poller = build_poller(logger:)
|
|
67
66
|
|
|
68
67
|
cache['upload.key'] = 'upload'
|
|
69
68
|
poller.start
|
|
@@ -78,20 +77,22 @@ describe CopyTunerClient::Poller do
|
|
|
78
77
|
end
|
|
79
78
|
|
|
80
79
|
it "logs an error if the background thread can't start" do
|
|
81
|
-
|
|
80
|
+
allow(Thread).to receive(:new).and_return(nil)
|
|
82
81
|
logger = FakeLogger.new
|
|
83
82
|
|
|
84
|
-
build_poller(:
|
|
83
|
+
build_poller(logger:).start
|
|
85
84
|
|
|
86
85
|
expect(logger).to have_entry(:error, "Couldn't start poller thread")
|
|
87
86
|
end
|
|
88
87
|
|
|
89
|
-
it
|
|
88
|
+
it 'flushes the log when polling' do
|
|
90
89
|
logger = FakeLogger.new
|
|
91
|
-
|
|
90
|
+
allow(logger).to receive(:flush)
|
|
92
91
|
|
|
93
|
-
build_poller(:
|
|
92
|
+
build_poller(logger:).start
|
|
94
93
|
|
|
95
94
|
wait_for_next_sync
|
|
95
|
+
|
|
96
|
+
expect(logger).to have_received(:flush).at_least(:once)
|
|
96
97
|
end
|
|
97
98
|
end
|
|
@@ -1,35 +1,38 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
describe CopyTunerClient::PrefixedLogger do
|
|
4
|
+
subject(:prefixed_logger) { described_class.new(prefix, output_logger) }
|
|
5
|
+
|
|
4
6
|
let(:output_logger) { FakeLogger.new }
|
|
5
|
-
let(:prefix) {
|
|
7
|
+
let(:prefix) { '** NOTICE:' }
|
|
6
8
|
let(:thread_info) { "[P:#{Process.pid}] [T:#{Thread.current.object_id}]" }
|
|
7
|
-
subject { CopyTunerClient::PrefixedLogger.new(prefix, output_logger) }
|
|
8
9
|
|
|
9
|
-
it
|
|
10
|
-
expect(
|
|
10
|
+
it 'provides the prefix' do
|
|
11
|
+
expect(prefixed_logger.prefix).to eq(prefix)
|
|
11
12
|
end
|
|
12
13
|
|
|
13
|
-
it
|
|
14
|
-
expect(
|
|
14
|
+
it 'provides the logger' do
|
|
15
|
+
expect(prefixed_logger.original_logger).to eq(output_logger)
|
|
15
16
|
end
|
|
16
17
|
|
|
17
|
-
[
|
|
18
|
+
%i[debug info warn error fatal].each do |level|
|
|
18
19
|
it "prefixes #{level} log messages" do
|
|
19
20
|
message = 'hello'
|
|
20
|
-
|
|
21
|
+
prefixed_logger.public_send(level, message)
|
|
21
22
|
|
|
22
23
|
expect(output_logger).to have_entry(level, "#{prefix} #{thread_info} #{message}")
|
|
23
24
|
end
|
|
24
25
|
end
|
|
25
26
|
|
|
26
|
-
it
|
|
27
|
-
|
|
27
|
+
it 'calls flush for a logger that responds to flush' do
|
|
28
|
+
allow(output_logger).to receive(:flush)
|
|
29
|
+
|
|
30
|
+
prefixed_logger.flush
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
expect(output_logger).to have_received(:flush)
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
it "doesn't call flush for a logger that doesn't respond to flush" do
|
|
33
|
-
expect {
|
|
36
|
+
expect { prefixed_logger.flush }.not_to raise_error
|
|
34
37
|
end
|
|
35
38
|
end
|
|
@@ -3,17 +3,14 @@ require 'spec_helper'
|
|
|
3
3
|
describe CopyTunerClient::ProcessGuard do
|
|
4
4
|
include DefinesConstants
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
let!(:original_process_name) { $PROGRAM_NAME }
|
|
7
|
+
let(:cache) { instance_double(CopyTunerClient::Cache, flush: nil) }
|
|
8
|
+
let(:poller) { instance_double(CopyTunerClient::Poller, start: nil) }
|
|
9
9
|
|
|
10
10
|
after do
|
|
11
|
-
$0 =
|
|
11
|
+
$0 = original_process_name
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
let(:cache) { double('cache', flush: nil) }
|
|
15
|
-
let(:poller) { double('poller', start: nil) }
|
|
16
|
-
|
|
17
14
|
def build_process_guard(options = {})
|
|
18
15
|
preserve_exit_hook = options.delete(:preserve_exit_hook)
|
|
19
16
|
options[:logger] ||= FakeLogger.new
|
|
@@ -23,67 +20,70 @@ describe CopyTunerClient::ProcessGuard do
|
|
|
23
20
|
process_guard
|
|
24
21
|
end
|
|
25
22
|
|
|
26
|
-
it
|
|
27
|
-
expect(poller).to receive(:start)
|
|
23
|
+
it 'starts polling from a worker process' do
|
|
28
24
|
process_guard = build_process_guard
|
|
29
25
|
process_guard.start
|
|
26
|
+
|
|
27
|
+
expect(poller).to have_received(:start)
|
|
30
28
|
end
|
|
31
29
|
|
|
32
|
-
it
|
|
33
|
-
expect(poller).not_to receive(:start)
|
|
30
|
+
it 'registers passenger hooks from the passenger master' do
|
|
34
31
|
logger = FakeLogger.new
|
|
35
32
|
passenger = define_constant('PhusionPassenger', FakePassenger.new)
|
|
36
33
|
passenger.become_master
|
|
37
34
|
|
|
38
|
-
process_guard = build_process_guard(:
|
|
35
|
+
process_guard = build_process_guard(logger:)
|
|
39
36
|
process_guard.start
|
|
40
37
|
|
|
41
|
-
expect(
|
|
38
|
+
expect(poller).not_to have_received(:start)
|
|
39
|
+
expect(logger).to have_entry(:info, 'Registered Phusion Passenger fork hook')
|
|
42
40
|
end
|
|
43
41
|
|
|
44
|
-
it
|
|
45
|
-
expect(poller).to receive(:start)
|
|
42
|
+
it 'starts polling from a passenger worker' do
|
|
46
43
|
logger = FakeLogger.new
|
|
47
44
|
passenger = define_constant('PhusionPassenger', FakePassenger.new)
|
|
48
45
|
passenger.become_master
|
|
49
|
-
process_guard = build_process_guard(:
|
|
46
|
+
process_guard = build_process_guard(logger:)
|
|
50
47
|
|
|
51
48
|
process_guard.start
|
|
52
49
|
passenger.spawn
|
|
50
|
+
|
|
51
|
+
expect(poller).to have_received(:start)
|
|
53
52
|
end
|
|
54
53
|
|
|
55
|
-
it
|
|
56
|
-
expect(poller).not_to receive(:start)
|
|
54
|
+
it 'registers unicorn hooks from the unicorn master' do
|
|
57
55
|
logger = FakeLogger.new
|
|
58
56
|
define_constant('Unicorn', Module.new)
|
|
59
57
|
http_server = Class.new(FakeUnicornServer)
|
|
60
58
|
unicorn = define_constant('Unicorn::HttpServer', http_server).new
|
|
61
59
|
unicorn.become_master
|
|
62
60
|
|
|
63
|
-
process_guard = build_process_guard(:
|
|
61
|
+
process_guard = build_process_guard(logger:)
|
|
64
62
|
process_guard.start
|
|
65
63
|
|
|
66
|
-
expect(
|
|
64
|
+
expect(poller).not_to have_received(:start)
|
|
65
|
+
expect(logger).to have_entry(:info, 'Registered Unicorn fork hook')
|
|
67
66
|
end
|
|
68
67
|
|
|
69
|
-
it
|
|
70
|
-
expect(poller).to receive(:start)
|
|
68
|
+
it 'starts polling from a unicorn worker' do
|
|
71
69
|
logger = FakeLogger.new
|
|
72
70
|
define_constant('Unicorn', Module.new)
|
|
73
71
|
http_server = Class.new(FakeUnicornServer)
|
|
74
72
|
unicorn = define_constant('Unicorn::HttpServer', http_server).new
|
|
75
73
|
unicorn.become_master
|
|
76
|
-
process_guard = build_process_guard(:
|
|
74
|
+
process_guard = build_process_guard(logger:)
|
|
77
75
|
|
|
78
76
|
process_guard.start
|
|
79
77
|
unicorn.spawn
|
|
78
|
+
|
|
79
|
+
expect(poller).to have_received(:start)
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
xit "flushes when the process terminates" do
|
|
82
|
+
it 'flushes when the process terminates' do
|
|
84
83
|
cache = WritingCache.new
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
FileUtils.rm_f(File.join(PROJECT_ROOT, 'tmp', 'written_cache'))
|
|
85
|
+
fork do
|
|
86
|
+
process_guard = build_process_guard(cache:, preserve_exit_hook: true)
|
|
87
87
|
process_guard.start
|
|
88
88
|
exit
|
|
89
89
|
end
|
|
@@ -4,80 +4,73 @@ describe CopyTunerClient::RequestSync do
|
|
|
4
4
|
let(:poller) { {} }
|
|
5
5
|
let(:cache) { {} }
|
|
6
6
|
let(:response) { 'response' }
|
|
7
|
-
let(:
|
|
8
|
-
|
|
7
|
+
let(:app) { double('app', call: response) }
|
|
8
|
+
|
|
9
9
|
before do
|
|
10
|
-
allow(cache).to receive_messages(:
|
|
10
|
+
allow(cache).to receive_messages(flush: nil, download: nil)
|
|
11
11
|
allow(poller).to receive(:start_sync).and_return(nil)
|
|
12
12
|
end
|
|
13
|
-
subject { CopyTunerClient::RequestSync.new(app, :poller => poller, :cache => cache, :interval => 0) }
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
result = subject.call(env)
|
|
18
|
-
expect(result).to eq(response)
|
|
19
|
-
end
|
|
20
|
-
end
|
|
14
|
+
context 'interval が 0 の場合' do
|
|
15
|
+
subject(:request_sync) { described_class.new(app, poller:, cache:, interval: 0) }
|
|
21
16
|
|
|
22
|
-
|
|
23
|
-
let(:env) do
|
|
24
|
-
{ "PATH_INFO" => '/assets/choper.png' }
|
|
25
|
-
end
|
|
26
|
-
let(:poller) { {} }
|
|
27
|
-
let(:cache) { {} }
|
|
28
|
-
let(:response) { 'response' }
|
|
29
|
-
let(:app) { double('app', :call => response) }
|
|
30
|
-
before do
|
|
31
|
-
allow(cache).to receive_messages(:flush => nil, :download => nil)
|
|
32
|
-
allow(poller).to receive(:start_sync).and_return(nil)
|
|
33
|
-
end
|
|
34
|
-
subject { CopyTunerClient::RequestSync.new(app, :poller => poller, :cache => cache, :interval => 0) }
|
|
17
|
+
let(:env) { 'env' }
|
|
35
18
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
19
|
+
it 'invokes the upstream app' do
|
|
20
|
+
result = request_sync.call(env)
|
|
21
|
+
expect(app).to have_received(:call).with(env)
|
|
22
|
+
expect(result).to eq(response)
|
|
23
|
+
end
|
|
41
24
|
end
|
|
42
|
-
end
|
|
43
25
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
let(:cache) { {} }
|
|
47
|
-
let(:response) { 'response' }
|
|
48
|
-
let(:env) { 'env' }
|
|
49
|
-
let(:app) { double('app', :call => response) }
|
|
50
|
-
subject { CopyTunerClient::RequestSync.new(app, :poller => poller, :cache => cache, :interval => 10) }
|
|
51
|
-
before do
|
|
52
|
-
allow(cache).to receive_messages(:flush => nil, :download => nil)
|
|
53
|
-
allow(poller).to receive(:start_sync).and_return(nil)
|
|
54
|
-
end
|
|
26
|
+
context 'when serving assets' do
|
|
27
|
+
subject(:request_sync) { described_class.new(app, poller:, cache:, interval: 0) }
|
|
55
28
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
expect(cache).to receive(:download).once
|
|
59
|
-
subject.call(env)
|
|
29
|
+
let(:env) do
|
|
30
|
+
{ 'PATH_INFO' => '/assets/choper.png' }
|
|
60
31
|
end
|
|
61
|
-
end
|
|
62
32
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
subject.call(env)
|
|
33
|
+
it "don't start sync" do
|
|
34
|
+
request_sync.call(env)
|
|
35
|
+
request_sync.call(env)
|
|
67
36
|
|
|
68
|
-
expect(
|
|
69
|
-
|
|
37
|
+
expect(cache).to have_received(:download).once
|
|
38
|
+
expect(poller).not_to have_received(:start_sync)
|
|
70
39
|
end
|
|
71
40
|
end
|
|
72
41
|
|
|
73
|
-
context '
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
42
|
+
context 'interval が 10 の場合' do
|
|
43
|
+
subject(:request_sync) { described_class.new(app, poller:, cache:, interval: 10) }
|
|
44
|
+
|
|
45
|
+
let(:env) { 'env' }
|
|
46
|
+
|
|
47
|
+
context 'first request' do
|
|
48
|
+
it 'download' do
|
|
49
|
+
request_sync.call(env)
|
|
50
|
+
expect(cache).to have_received(:download).once
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context 'in interval request' do
|
|
55
|
+
it 'does not start sync for the second time' do
|
|
56
|
+
request_sync.call(env)
|
|
57
|
+
request_sync.call(env)
|
|
58
|
+
|
|
59
|
+
expect(cache).to have_received(:download).once
|
|
60
|
+
expect(poller).not_to have_received(:start_sync)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
context 'over interval request' do
|
|
65
|
+
it 'start sync for the second time' do
|
|
66
|
+
request_sync.call(env)
|
|
67
|
+
|
|
68
|
+
request_sync.last_synced = Time.now - 60
|
|
69
|
+
request_sync.call(env)
|
|
77
70
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
71
|
+
expect(cache).to have_received(:download).once
|
|
72
|
+
expect(poller).to have_received(:start_sync).once
|
|
73
|
+
end
|
|
81
74
|
end
|
|
82
75
|
end
|
|
83
76
|
end
|
|
@@ -5,46 +5,60 @@ describe CopyTunerClient::TranslationLog do
|
|
|
5
5
|
before { described_class.clear }
|
|
6
6
|
|
|
7
7
|
describe '.add' do
|
|
8
|
-
context '
|
|
9
|
-
|
|
8
|
+
context '初期化済みかつキーが local_first_key_regexp にマッチする場合' do
|
|
9
|
+
before { CopyTunerClient.configuration.local_first_key_regexp = /\Aviews\./ }
|
|
10
|
+
|
|
11
|
+
it 'マッチしたキーを記録しないこと' do
|
|
12
|
+
described_class.add('views.foo', 'Hello')
|
|
13
|
+
expect(described_class.translations).to be_empty
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'マッチしないキーは記録すること' do
|
|
17
|
+
described_class.add('messages.greeting', 'Hi')
|
|
18
|
+
expect(described_class.translations).to eq('messages.greeting' => 'Hi')
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context '初期化済みかつ local_first_key_regexp が未設定の場合' do
|
|
23
|
+
it 'キーを記録すること' do
|
|
10
24
|
described_class.add('views.foo', 'Hello')
|
|
11
25
|
expect(described_class.translations).to eq('views.foo' => 'Hello')
|
|
12
26
|
end
|
|
13
27
|
|
|
14
|
-
it '
|
|
28
|
+
it '既存のキーを上書きしないこと' do
|
|
15
29
|
described_class.add('views.foo', 'Hello')
|
|
16
30
|
described_class.add('views.foo', 'World')
|
|
17
31
|
expect(described_class.translations['views.foo']).to eq 'Hello'
|
|
18
32
|
end
|
|
33
|
+
end
|
|
19
34
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
it 'does not record the matching key' do
|
|
24
|
-
described_class.add('views.foo', 'Hello')
|
|
25
|
-
expect(described_class.translations).to be_empty
|
|
26
|
-
end
|
|
35
|
+
context '未初期化の場合' do
|
|
36
|
+
before { Thread.current[:translations] = nil }
|
|
27
37
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
end
|
|
38
|
+
it 'キーを無視すること' do
|
|
39
|
+
described_class.add('views.foo', 'Hello')
|
|
40
|
+
expect(described_class.initialized?).to be false
|
|
32
41
|
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
33
44
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
describe '.install_hook' do
|
|
46
|
+
# フック導入前後で I18n の特異クラスを復元し、他のテストへの副作用を防ぐ
|
|
47
|
+
around do |example|
|
|
48
|
+
original_singleton_methods = I18n.singleton_class.instance_methods(false)
|
|
49
|
+
example.run
|
|
50
|
+
(I18n.singleton_class.instance_methods(false) - original_singleton_methods).each do |method_name|
|
|
51
|
+
I18n.singleton_class.__send__(:remove_method, method_name)
|
|
39
52
|
end
|
|
40
53
|
end
|
|
41
54
|
|
|
42
|
-
context '
|
|
43
|
-
before {
|
|
55
|
+
context 'ミドルウェアが有効な場合' do
|
|
56
|
+
before { allow(CopyTunerClient.configuration).to receive(:enable_middleware?).and_return(true) }
|
|
44
57
|
|
|
45
|
-
it '
|
|
46
|
-
described_class.
|
|
47
|
-
expect(
|
|
58
|
+
it 'エラーを発生させずに I18n.translate をフックすること' do
|
|
59
|
+
expect { described_class.install_hook }.not_to raise_error
|
|
60
|
+
expect(I18n.translate(:hello, default: 'Hello')).to eq 'Hello'
|
|
61
|
+
expect(described_class.translations).to have_key('hello')
|
|
48
62
|
end
|
|
49
63
|
end
|
|
50
64
|
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'copy_tuner_client/copyray_middleware'
|
|
3
|
+
require 'copy_tuner_client/copyray'
|
|
4
|
+
require 'copy_tuner_client/copyray/marker'
|
|
5
|
+
require 'copy_tuner_client/translation_log'
|
|
6
|
+
|
|
7
|
+
# Warden::Manager の catch(:warden) と failure app 呼び出しだけを模した fake(warden gem に依存しないため)
|
|
8
|
+
class FakeWarden
|
|
9
|
+
def initialize(app, failure_app)
|
|
10
|
+
@app = app
|
|
11
|
+
@failure_app = failure_app
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(env)
|
|
15
|
+
result = catch(:warden) { @app.call(env) }
|
|
16
|
+
result.is_a?(Array) ? result : @failure_app.call(env)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# NOTE: CopyrayMiddleware 単体ではなく、Warden 相当の fake との組み合わせ位置を検証するため
|
|
21
|
+
# describe の第一引数はクラスではなく説明文にしている
|
|
22
|
+
describe 'throw :warden と CopyrayMiddleware の位置関係' do # rubocop:disable RSpec/DescribeClass
|
|
23
|
+
def marker(key)
|
|
24
|
+
CopyTunerClient::Copyray::Marker.encode(key)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# NOTE: authenticate_user! 失敗を模す。throw :warden するとこの @app.call(env) は正常リターンせず、
|
|
28
|
+
# CopyrayMiddleware が内側にあると Rewriter.rewrite が実行されない。
|
|
29
|
+
let(:throwing_app) { ->(_env) { throw :warden } }
|
|
30
|
+
let(:failure_status) { 200 }
|
|
31
|
+
let(:failure_app) do
|
|
32
|
+
->(_env) do
|
|
33
|
+
[failure_status, { 'Content-Type' => 'text/html' }, [failure_body]]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
let(:failure_body) { "<html><body><p>#{marker('devise.failure.unauthenticated')}Please sign in</p></body></html>" }
|
|
37
|
+
|
|
38
|
+
before do
|
|
39
|
+
CopyTunerClient.configure do |configuration|
|
|
40
|
+
configuration.project_id = 1
|
|
41
|
+
configuration.client = FakeClient.new
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# NOTE: append_js は Rails の ActionController::Base.helpers に依存するため、
|
|
46
|
+
# copyray_middleware_spec.rb と同じ手法で no-op スタブに差し替え、Rewriter の効果だけを見る。
|
|
47
|
+
def stub_append_js(middleware)
|
|
48
|
+
allow(middleware).to receive(:append_js) { |html, *| html }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context '内側構成(CopyrayMiddleware が Warden より内側)のとき' do
|
|
52
|
+
it 'マーカー ⟦CT: がレスポンスに残る(不具合の記録)' do
|
|
53
|
+
copyray = CopyTunerClient::CopyrayMiddleware.new(throwing_app)
|
|
54
|
+
stub_append_js(copyray)
|
|
55
|
+
warden = FakeWarden.new(copyray, failure_app)
|
|
56
|
+
|
|
57
|
+
_status, _headers, response = warden.call({})
|
|
58
|
+
result = response.join
|
|
59
|
+
|
|
60
|
+
expect(result).to match(CopyTunerClient::Copyray::Marker::SCAN_REGEXP)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
context '外側構成(CopyrayMiddleware が Warden より外側)のとき' do
|
|
65
|
+
it 'マーカーが消え data-copyray-key に変換される' do
|
|
66
|
+
warden = FakeWarden.new(throwing_app, failure_app)
|
|
67
|
+
copyray = CopyTunerClient::CopyrayMiddleware.new(warden)
|
|
68
|
+
stub_append_js(copyray)
|
|
69
|
+
|
|
70
|
+
_status, _headers, response = copyray.call({})
|
|
71
|
+
result = response.join
|
|
72
|
+
|
|
73
|
+
expect(result).to include('data-copyray-key="devise.failure.unauthenticated"')
|
|
74
|
+
expect(result).not_to match(CopyTunerClient::Copyray::Marker::SCAN_REGEXP)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
context 'failure app が 422 を返すとき(Devise.responder.error_status = :unprocessable_entity 相当)' do
|
|
78
|
+
let(:unprocessable_failure_app) do
|
|
79
|
+
->(_env) { [422, { 'Content-Type' => 'text/html' }, [failure_body]] }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'マーカーが消え data-copyray-key に変換される' do
|
|
83
|
+
warden = FakeWarden.new(throwing_app, unprocessable_failure_app)
|
|
84
|
+
copyray = CopyTunerClient::CopyrayMiddleware.new(warden)
|
|
85
|
+
stub_append_js(copyray)
|
|
86
|
+
|
|
87
|
+
_status, _headers, response = copyray.call({})
|
|
88
|
+
result = response.join
|
|
89
|
+
|
|
90
|
+
expect(result).to include('data-copyray-key="devise.failure.unauthenticated"')
|
|
91
|
+
expect(result).not_to match(CopyTunerClient::Copyray::Marker::SCAN_REGEXP)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
describe CopyTunerClient do
|
|
4
|
-
|
|
5
4
|
before do
|
|
6
|
-
allow(
|
|
5
|
+
allow(described_class.configuration).to receive_messages(cache: 'cache', client: 'client')
|
|
7
6
|
end
|
|
8
7
|
|
|
9
8
|
it 'delegates cache to the configuration object' do
|
|
10
|
-
expect(
|
|
11
|
-
expect(
|
|
9
|
+
expect(described_class.cache).to eq('cache')
|
|
10
|
+
expect(described_class.configuration).to have_received(:cache).once
|
|
12
11
|
end
|
|
13
12
|
|
|
14
13
|
it 'delegates client to the configuration object' do
|
|
15
|
-
expect(
|
|
16
|
-
expect(
|
|
14
|
+
expect(described_class.client).to eq('client')
|
|
15
|
+
expect(described_class.configuration).to have_received(:client).once
|
|
17
16
|
end
|
|
18
17
|
end
|
|
@@ -1,44 +1,13 @@
|
|
|
1
1
|
module DefinesConstants
|
|
2
|
-
def
|
|
3
|
-
super
|
|
4
|
-
example_group.class_eval do
|
|
5
|
-
before { @defined_constants = [] }
|
|
6
|
-
after { undefine_constants }
|
|
7
|
-
end
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def define_class(class_name, base = Object, &block)
|
|
2
|
+
def define_class(class_name, base = Object, &)
|
|
11
3
|
class_name = class_name.to_s.camelize
|
|
12
4
|
klass = Class.new(base)
|
|
13
5
|
define_constant(class_name, klass)
|
|
14
|
-
klass.class_eval(&
|
|
6
|
+
klass.class_eval(&) if block_given?
|
|
15
7
|
klass
|
|
16
8
|
end
|
|
17
9
|
|
|
18
10
|
def define_constant(path, value)
|
|
19
|
-
|
|
20
|
-
parent.const_set(name, value)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
@defined_constants << path
|
|
24
|
-
value
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def parse_constant(path)
|
|
28
|
-
parent_names = path.split('::')
|
|
29
|
-
name = parent_names.pop
|
|
30
|
-
parent = parent_names.inject(Object) do |ref, child_name|
|
|
31
|
-
ref.const_get(child_name)
|
|
32
|
-
end
|
|
33
|
-
yield(parent, name)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def undefine_constants
|
|
37
|
-
@defined_constants.reverse.each do |path|
|
|
38
|
-
parse_constant(path) do |parent, name|
|
|
39
|
-
parent.send(:remove_const, name)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
11
|
+
stub_const(path, value)
|
|
42
12
|
end
|
|
43
13
|
end
|
|
44
|
-
|