janus-ar 8.0.0 → 8.0.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.
@@ -1,82 +1,78 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe ActiveRecord::ConnectionAdapters::JanusMysql2Adapter do
4
- subject { described_class.new(config) }
5
-
6
- it { expect(described_class::FOUND_ROWS).to eq 'FOUND_ROWS' }
7
-
8
- let(:database) { 'test' }
9
- let(:primary_config) do
10
- {
11
- 'username' => 'primary',
12
- 'password' => 'primary_password',
13
- 'host' => '127.0.0.1',
14
- 'ssl' => true,
15
- 'ssl_mode' => 'REQUIRED',
16
- 'tls_min_version' => 3,
17
- }
18
- end
19
- let(:replica_config) do
20
- {
21
- 'username' => 'replica',
22
- 'password' => 'replica_password',
23
- 'host' => '127.0.0.1',
24
- 'pool' => 500,
25
- 'ssl' => true,
26
- 'ssl_mode' => 'REQUIRED',
27
- 'tls_min_version' => 3,
28
- }
29
- end
30
- let(:config) do
31
- {
32
- database:,
33
- adapter: 'janus_mysql2',
34
- janus: {
35
- 'primary' => primary_config,
36
- 'replica' => replica_config,
37
- },
38
- }
39
- end
40
-
41
- describe 'Configuration' do
42
- it 'creates primary connection as expected' do
43
- config = primary_config.dup.freeze
44
- expect(subject.config).to eq config.merge('database' => database,
45
- 'flags' => ::Janus::Client::FOUND_ROWS).symbolize_keys
46
- end
47
-
48
- it 'creates replica connection as expected' do
49
- config = replica_config.dup.freeze
50
- expect(
51
- subject.replica_connection.instance_variable_get(:@config)
52
- ).to eq config.merge('database' => database, 'flags' => ::Janus::Client::FOUND_ROWS).symbolize_keys
53
- end
54
-
55
- context 'Rails sets empty database for server connection' do
56
- let(:database) { nil }
57
-
58
- it 'creates primary connection as expected' do
59
- config = primary_config.dup.freeze
60
- expect(subject.config).to eq config.merge(
61
- 'database' => nil,
62
- 'flags' => ::Janus::Client::FOUND_ROWS
63
- ).symbolize_keys
64
- end
65
-
66
- it 'creates replica connection as expected' do
67
- config = replica_config.dup.freeze
68
- expect(
69
- subject.replica_connection.instance_variable_get(:@config)
70
- ).to eq config.merge('database' => nil, 'flags' => ::Janus::Client::FOUND_ROWS).symbolize_keys
71
- end
72
- end
73
- end
74
-
75
- describe 'Integration tests' do
76
- describe 'Integration tests' do
77
- let(:table_name) { 'table_name_mysql2' }
78
-
79
- it_behaves_like 'a mysql like server'
80
- end
81
- end
82
- end
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe ActiveRecord::ConnectionAdapters::JanusMysql2Adapter do
4
+ subject { described_class.new(config) }
5
+
6
+ let(:database) { 'test' }
7
+ let(:primary_config) do
8
+ {
9
+ 'username' => 'primary',
10
+ 'password' => 'primary_password',
11
+ 'host' => '127.0.0.1',
12
+ 'ssl' => true,
13
+ 'ssl_mode' => 'REQUIRED',
14
+ 'tls_min_version' => 3,
15
+ }
16
+ end
17
+ let(:replica_config) do
18
+ {
19
+ 'username' => 'replica',
20
+ 'password' => 'replica_password',
21
+ 'host' => '127.0.0.1',
22
+ 'pool' => 500,
23
+ 'ssl' => true,
24
+ 'ssl_mode' => 'REQUIRED',
25
+ 'tls_min_version' => 3,
26
+ }
27
+ end
28
+ let(:config) do
29
+ {
30
+ database:,
31
+ adapter: 'janus_mysql2',
32
+ janus: {
33
+ 'primary' => primary_config,
34
+ 'replica' => replica_config,
35
+ },
36
+ }
37
+ end
38
+
39
+ describe 'Configuration' do
40
+ it 'creates primary connection as expected' do
41
+ config = primary_config.dup.freeze
42
+ expect(subject.config).to eq config.merge('database' => database,
43
+ 'flags' => ::Janus::Client::FOUND_ROWS).symbolize_keys
44
+ end
45
+
46
+ it 'creates replica connection as expected' do
47
+ config = replica_config.dup.freeze
48
+ expect(
49
+ subject.replica_connection.instance_variable_get(:@config)
50
+ ).to eq config.merge('database' => database, 'flags' => ::Janus::Client::FOUND_ROWS).symbolize_keys
51
+ end
52
+
53
+ context 'Rails sets empty database for server connection' do
54
+ let(:database) { nil }
55
+
56
+ it 'creates primary connection as expected' do
57
+ config = primary_config.dup.freeze
58
+ expect(subject.config).to eq config.merge(
59
+ 'database' => nil,
60
+ 'flags' => ::Janus::Client::FOUND_ROWS
61
+ ).symbolize_keys
62
+ end
63
+
64
+ it 'creates replica connection as expected' do
65
+ config = replica_config.dup.freeze
66
+ expect(
67
+ subject.replica_connection.instance_variable_get(:@config)
68
+ ).to eq config.merge('database' => nil, 'flags' => ::Janus::Client::FOUND_ROWS).symbolize_keys
69
+ end
70
+ end
71
+ end
72
+
73
+ describe 'Integration tests' do
74
+ let(:table_name) { 'table_name_mysql2' }
75
+
76
+ it_behaves_like 'a mysql like server'
77
+ end
78
+ end
@@ -1,82 +1,77 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe ActiveRecord::ConnectionAdapters::JanusTrilogyAdapter do
4
- subject { described_class.new(config) }
5
-
6
- it { expect(described_class::FOUND_ROWS).to eq 'FOUND_ROWS' }
7
-
8
- let(:database) { 'test' }
9
- let(:primary_config) do
10
- {
11
- 'username' => 'primary',
12
- 'password' => 'primary_password',
13
- 'host' => '127.0.0.1',
14
- 'ssl' => true,
15
- 'ssl_mode' => 'REQUIRED',
16
- 'tls_min_version' => Trilogy::TLS_VERSION_12,
17
- 'found_rows' => true,
18
- }
19
- end
20
- let(:replica_config) do
21
- {
22
- 'username' => 'replica',
23
- 'password' => 'replica_password',
24
- 'host' => '127.0.0.1',
25
- 'pool' => 500,
26
- 'ssl' => true,
27
- 'ssl_mode' => 'REQUIRED',
28
- 'tls_min_version' => Trilogy::TLS_VERSION_12,
29
- 'found_rows' => true,
30
- }
31
- end
32
- let(:config) do
33
- {
34
- database:,
35
- adapter: 'janus_trilogy',
36
- janus: {
37
- 'primary' => primary_config,
38
- 'replica' => replica_config,
39
- },
40
- }
41
- end
42
-
43
- describe 'Configuration' do
44
- it 'creates primary connection as expected' do
45
- config = primary_config.dup.freeze
46
- expect(subject.config).to eq config.merge('database' => database,
47
- 'flags' => ::Janus::Client::FOUND_ROWS).symbolize_keys
48
- end
49
-
50
- it 'creates replica connection as expected' do
51
- config = replica_config.dup.freeze
52
- expect(
53
- subject.replica_connection.instance_variable_get(:@config)
54
- ).to eq config.merge('database' => database).symbolize_keys
55
- end
56
-
57
- context 'Rails sets empty database for server connection' do
58
- let(:database) { nil }
59
-
60
- it 'creates primary connection as expected' do
61
- config = primary_config.dup.freeze
62
- expect(subject.config).to eq config.merge(
63
- 'database' => nil,
64
- 'flags' => ::Janus::Client::FOUND_ROWS
65
- ).symbolize_keys
66
- end
67
-
68
- it 'creates replica connection as expected' do
69
- config = replica_config.dup.freeze
70
- expect(
71
- subject.replica_connection.instance_variable_get(:@config)
72
- ).to eq config.merge('database' => nil).symbolize_keys
73
- end
74
- end
75
- end
76
-
77
- describe 'Integration tests' do
78
- let(:table_name) { 'table_name_trilogy' }
79
-
80
- it_behaves_like 'a mysql like server'
81
- end
82
- end
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe ActiveRecord::ConnectionAdapters::JanusTrilogyAdapter do
4
+ subject { described_class.new(config) }
5
+
6
+ let(:database) { 'test' }
7
+ let(:primary_config) do
8
+ {
9
+ 'username' => 'primary',
10
+ 'password' => 'primary_password',
11
+ 'host' => '127.0.0.1',
12
+ 'ssl' => true,
13
+ 'ssl_mode' => 'REQUIRED',
14
+ 'tls_min_version' => Trilogy::TLS_VERSION_12,
15
+ }
16
+ end
17
+ let(:replica_config) do
18
+ {
19
+ 'username' => 'replica',
20
+ 'password' => 'replica_password',
21
+ 'host' => '127.0.0.1',
22
+ 'pool' => 500,
23
+ 'ssl' => true,
24
+ 'ssl_mode' => 'REQUIRED',
25
+ 'tls_min_version' => Trilogy::TLS_VERSION_12,
26
+ }
27
+ end
28
+ let(:config) do
29
+ {
30
+ database:,
31
+ adapter: 'janus_trilogy',
32
+ janus: {
33
+ 'primary' => primary_config,
34
+ 'replica' => replica_config,
35
+ },
36
+ }
37
+ end
38
+
39
+ describe 'Configuration' do
40
+ # Trilogy enables FOUND_ROWS via the `found_rows` option (not mysql2-style
41
+ # flags), and ActiveRecord's TrilogyAdapter forces it on. We assert it is
42
+ # present even though the supplied config omits it.
43
+ it 'creates primary connection as expected' do
44
+ config = primary_config.dup.freeze
45
+ expect(subject.config).to eq config.merge('database' => database, 'found_rows' => true).symbolize_keys
46
+ end
47
+
48
+ it 'creates replica connection as expected' do
49
+ config = replica_config.dup.freeze
50
+ expect(
51
+ subject.replica_connection.instance_variable_get(:@config)
52
+ ).to eq config.merge('database' => database, 'found_rows' => true).symbolize_keys
53
+ end
54
+
55
+ context 'Rails sets empty database for server connection' do
56
+ let(:database) { nil }
57
+
58
+ it 'creates primary connection as expected' do
59
+ config = primary_config.dup.freeze
60
+ expect(subject.config).to eq config.merge('database' => nil, 'found_rows' => true).symbolize_keys
61
+ end
62
+
63
+ it 'creates replica connection as expected' do
64
+ config = replica_config.dup.freeze
65
+ expect(
66
+ subject.replica_connection.instance_variable_get(:@config)
67
+ ).to eq config.merge('database' => nil, 'found_rows' => true).symbolize_keys
68
+ end
69
+ end
70
+ end
71
+
72
+ describe 'Integration tests' do
73
+ let(:table_name) { 'table_name_trilogy' }
74
+
75
+ it_behaves_like 'a mysql like server'
76
+ end
77
+ end
@@ -1,46 +1,118 @@
1
- # frozen_string_literal: true
2
-
3
- require 'janus-ar/context'
4
-
5
- RSpec.describe Janus::Context do
6
- describe '#initialize' do
7
- it 'sets the primary flag and expiry' do
8
- context = described_class.new(primary: true, expiry: 60)
9
- expect(context.use_primary?).to be true
10
- expect(context.last_used_connection).to eq(:primary)
11
- end
12
- end
13
-
14
- describe '#stick_to_primary' do
15
- it 'sets the primary flag to true' do
16
- context = described_class.new
17
- context.stick_to_primary
18
- expect(context.use_primary?).to be true
19
- end
20
- end
21
-
22
- describe '#potential_write' do
23
- it 'calls stick_to_primary' do
24
- context = described_class.new
25
- expect(context).to receive(:stick_to_primary)
26
- context.potential_write
27
- end
28
- end
29
-
30
- describe '#release_all' do
31
- it 'resets the primary flag and expiry' do
32
- context = described_class.new(primary: true, expiry: 60)
33
- context.release_all
34
- expect(context.use_primary?).to be false
35
- expect(context.last_used_connection).to be_nil
36
- end
37
- end
38
-
39
- describe '#used_connection' do
40
- it 'sets the last used connection' do
41
- context = described_class.new
42
- context.used_connection(:secondary)
43
- expect(context.last_used_connection).to eq(:secondary)
44
- end
45
- end
46
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'janus-ar/context'
4
+ require 'active_support/executor'
5
+
6
+ RSpec.describe Janus::Context do
7
+ after(:each) { described_class.release_all }
8
+
9
+ describe '#initialize' do
10
+ it 'defaults to the replica and a primary last-used connection' do
11
+ context = described_class.new
12
+ expect(context.use_primary?).to be false
13
+ expect(context.last_used_connection).to eq(:primary)
14
+ end
15
+
16
+ it 'can be created pinned to the primary' do
17
+ expect(described_class.new(primary: true).use_primary?).to be true
18
+ end
19
+ end
20
+
21
+ describe '#stick_to_primary' do
22
+ it 'sets the primary flag to true' do
23
+ context = described_class.new
24
+ context.stick_to_primary
25
+ expect(context.use_primary?).to be true
26
+ end
27
+ end
28
+
29
+ describe '#release_all' do
30
+ it 'resets the primary flag and the last used connection' do
31
+ context = described_class.new(primary: true)
32
+ context.used_connection(:primary)
33
+ context.release_all
34
+ expect(context.use_primary?).to be false
35
+ expect(context.last_used_connection).to be_nil
36
+ end
37
+ end
38
+
39
+ describe '#used_connection' do
40
+ it 'sets the last used connection' do
41
+ context = described_class.new
42
+ context.used_connection(:replica)
43
+ expect(context.last_used_connection).to eq(:replica)
44
+ end
45
+ end
46
+
47
+ describe 'class-level access' do
48
+ it 'sticks and releases the current execution state' do
49
+ described_class.stick_to_primary
50
+ expect(described_class.use_primary?).to be true
51
+ described_class.release_all
52
+ expect(described_class.use_primary?).to be false
53
+ end
54
+
55
+ it 'isolates state between threads' do
56
+ described_class.stick_to_primary
57
+ other = Thread.new { described_class.use_primary? }.value
58
+ expect(other).to be false
59
+ expect(described_class.use_primary?).to be true
60
+ end
61
+
62
+ it 'isolates the last used connection between threads' do
63
+ described_class.used_connection(:replica)
64
+ other = Thread.new do
65
+ described_class.used_connection(:primary)
66
+ described_class.last_used_connection
67
+ end.value
68
+ expect(other).to eq(:primary)
69
+ expect(described_class.last_used_connection).to eq(:replica)
70
+ end
71
+
72
+ it 'is safe to release a context that was never touched' do
73
+ Thread.new do
74
+ expect { described_class.release_all }.not_to raise_error
75
+ expect(described_class.use_primary?).to be false
76
+ end.join
77
+ end
78
+ end
79
+
80
+ describe '.install_reset_hook' do
81
+ let(:executor) { Class.new(ActiveSupport::Executor) }
82
+
83
+ it 'releases the context at the start of every executor run' do
84
+ described_class.install_reset_hook(executor)
85
+ described_class.stick_to_primary
86
+
87
+ stuck_inside = nil
88
+ executor.wrap { stuck_inside = described_class.use_primary? }
89
+
90
+ expect(stuck_inside).to be false
91
+ end
92
+
93
+ it 'stops stickiness leaking from a previous run into the next' do
94
+ described_class.install_reset_hook(executor)
95
+
96
+ executor.wrap { described_class.stick_to_primary }
97
+
98
+ leaked = nil
99
+ executor.wrap { leaked = described_class.use_primary? }
100
+ expect(leaked).to be false
101
+ end
102
+
103
+ it 'still starts the next run clean after the previous run raised' do
104
+ described_class.install_reset_hook(executor)
105
+
106
+ expect do
107
+ executor.wrap do
108
+ described_class.stick_to_primary
109
+ raise 'boom'
110
+ end
111
+ end.to raise_error('boom')
112
+
113
+ leaked = nil
114
+ executor.wrap { leaked = described_class.use_primary? }
115
+ expect(leaked).to be false
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'janus-ar/db_console_config'
4
+
5
+ RSpec.describe Janus::DbConsoleConfig do
6
+ subject(:console_config) { described_class.new(db_config) }
7
+
8
+ let(:db_config) do
9
+ instance_double(
10
+ ActiveRecord::DatabaseConfigurations::HashConfig,
11
+ configuration_hash: {
12
+ database: 'my_database',
13
+ janus: {
14
+ 'primary' => { 'host' => 'primary.local', 'username' => 'primary' },
15
+ 'replica' => { 'host' => 'replica.local', 'username' => 'replica' },
16
+ },
17
+ }
18
+ )
19
+ end
20
+
21
+ it 'exposes the replica configuration with symbol keys so dbconsole connects to a replica' do
22
+ expect(console_config.configuration_hash).to eq(host: 'replica.local', username: 'replica')
23
+ end
24
+
25
+ it 'exposes the database name from the top-level config' do
26
+ expect(console_config.database).to eq('my_database')
27
+ end
28
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'janus-ar/context'
4
+ require 'janus-ar/logging/subscriber'
5
+
6
+ RSpec.describe Janus::Logging::Subscriber do
7
+ # A minimal stand-in for ActiveRecord::LogSubscriber: it records the event it
8
+ # is given so we can assert on the (possibly rewritten) payload name.
9
+ let(:base_class) do
10
+ Class.new do
11
+ attr_reader :received_event
12
+
13
+ def sql(event)
14
+ @received_event = event
15
+ end
16
+ end
17
+ end
18
+ let(:subscriber) { base_class.new.extend(described_class) }
19
+ let(:event) { instance_double(ActiveSupport::Notifications::Event, payload: { name: 'User Load' }) }
20
+
21
+ after(:each) { Janus::Context.release_all }
22
+
23
+ it 'tags the log name with the last used connection' do
24
+ Janus::Context.used_connection(:replica)
25
+
26
+ subscriber.sql(event)
27
+
28
+ expect(event.payload[:name]).to eq('[replica] User Load')
29
+ expect(subscriber.received_event).to eq(event)
30
+ end
31
+
32
+ it 'reflects the primary connection' do
33
+ Janus::Context.used_connection(:primary)
34
+
35
+ subscriber.sql(event)
36
+
37
+ expect(event.payload[:name]).to eq('[primary] User Load')
38
+ end
39
+
40
+ it 'leaves the name unchanged when no connection has been used yet' do
41
+ Janus::Context.release_all
42
+
43
+ subscriber.sql(event)
44
+
45
+ expect(event.payload[:name]).to eq('User Load')
46
+ end
47
+
48
+ Janus::Logging::Subscriber::IGNORE_PAYLOAD_NAMES.each do |ignored|
49
+ it "does not tag #{ignored} statements" do
50
+ Janus::Context.used_connection(:replica)
51
+ ignored_event = instance_double(ActiveSupport::Notifications::Event, payload: { name: ignored })
52
+
53
+ subscriber.sql(ignored_event)
54
+
55
+ expect(ignored_event.payload[:name]).to eq(ignored)
56
+ end
57
+ end
58
+ end