motel-activerecord 1.0.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.
@@ -0,0 +1,238 @@
1
+ require 'spec_helper'
2
+
3
+ describe Motel::Manager do
4
+
5
+ before(:all) do
6
+ ActiveRecord::Base.connection_handler = begin
7
+ Motel::ConnectionAdapters::ConnectionHandler.new
8
+ end
9
+ @manager = Motel::Manager
10
+ end
11
+
12
+ before(:each) do
13
+ ActiveRecord::Base.connection_handler.tenants_source = begin
14
+ Motel::Sources::Default.new
15
+ end
16
+ @tenants_source = ActiveRecord::Base.connection_handler.tenants_source
17
+ @tenants_source.add_tenant('foo', FOO_SPEC)
18
+ @tenants_source.add_tenant('bar', BAR_SPEC)
19
+ end
20
+
21
+ after(:each) do
22
+ ENV['TENANT'] = nil
23
+ @manager.current_tenant = nil
24
+ @manager.default_tenant = nil
25
+
26
+ # Remove all connections tenant
27
+ ActiveRecord::Base.connection_handler.active_tenants do |tenant|
28
+ ActiveRecord::Base.connection_handler.remove_connection(tenant)
29
+ end
30
+ end
31
+
32
+ describe '#tenants_source_configurations' do
33
+
34
+ context 'redis source' do
35
+
36
+ before(:each) do
37
+ @manager.tenants_source_configurations({
38
+ source: :redis,
39
+ host: 'localhost',
40
+ port: 6380,
41
+ password: 'none',
42
+ path: '/tmp/redis.sock',
43
+ prefix_tenant_alias: 'test-tenant'
44
+ })
45
+ end
46
+
47
+ it 'places a redis instance on the source' do
48
+ expect(@manager.tenants_source).to be_an_instance_of Motel::Sources::Redis
49
+ end
50
+
51
+ it 'source attributes has a correct values' do
52
+ expect(@manager.tenants_source.host).to eq 'localhost'
53
+ expect(@manager.tenants_source.port).to eq 6380
54
+ expect(@manager.tenants_source.password).to eq 'none'
55
+ expect(@manager.tenants_source.path).to eq '/tmp/redis.sock'
56
+ expect(@manager.tenants_source.prefix_tenant_alias).to eq 'test-tenant'
57
+ end
58
+
59
+ end
60
+
61
+ context 'database source' do
62
+
63
+ before(:each) do
64
+ @manager.tenants_source_configurations({
65
+ source: :database,
66
+ source_spec: TENANTS_SPEC,
67
+ table_name: 'tenant'
68
+ })
69
+ end
70
+
71
+ it 'places a database instance on the source' do
72
+ expect(@manager.tenants_source).to be_an_instance_of Motel::Sources::Database
73
+ end
74
+
75
+ it 'source attributes has a correct values' do
76
+ expect(@manager.tenants_source.source_spec).to eq TENANTS_SPEC
77
+ expect(@manager.tenants_source.table_name).to eq 'tenant'
78
+ end
79
+
80
+ end
81
+
82
+ context 'default source' do
83
+
84
+ before(:each) do
85
+ @manager.tenants_source_configurations({source: :default})
86
+ end
87
+
88
+ it 'places a default instance on the source' do
89
+ expect(@manager.tenants_source).to be_an_instance_of Motel::Sources::Default
90
+ end
91
+
92
+ end
93
+
94
+ end
95
+
96
+ describe '#tenants' do
97
+
98
+ it 'returns tenant foo' do
99
+ expect(@manager.tenants.key?('foo')).to be_true
100
+ end
101
+
102
+ it 'returns tenant bar' do
103
+ expect(@manager.tenants.key?('bar')).to be_true
104
+ end
105
+
106
+ end
107
+
108
+ describe '#tenant' do
109
+
110
+ it 'returns tenant foo spec' do
111
+ expect(@manager.tenant('foo')['adapter']).to eq FOO_SPEC['adapter']
112
+ expect(@manager.tenant('foo')['database']).to eq FOO_SPEC['database']
113
+ end
114
+
115
+ end
116
+
117
+ describe '#tenant?' do
118
+
119
+ it 'returns true if tenant foo does exist' do
120
+ expect(@manager.tenant?('foo')).to be_true
121
+ end
122
+
123
+ it 'returns true if tenant baz does exist' do
124
+ resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new(
125
+ BAZ_SPEC, nil
126
+ )
127
+ handler = ActiveRecord::Base.connection_handler
128
+ handler.establish_connection('baz', resolver.spec)
129
+ expect(@manager.tenant?('baz')).to be_true
130
+ end
131
+
132
+ it 'returns false if tenant does not exist' do
133
+ expect(@manager.tenant?('foobar')).to be_false
134
+ end
135
+
136
+ end
137
+
138
+ describe '#add_tenant' do
139
+
140
+ it 'adds new tenant' do
141
+ @manager.add_tenant('baz', BAZ_SPEC)
142
+
143
+ expect(@tenants_source.tenant?('baz')).to be_true
144
+ end
145
+
146
+ end
147
+
148
+ describe '#update_tenant' do
149
+
150
+ it 'updates tenant' do
151
+ @manager.update_tenant('foo', {adapter: 'mysql2', database: 'foo'})
152
+
153
+ expect(@tenants_source.tenant('foo')['adapter']).to eq 'mysql2'
154
+ expect(@tenants_source.tenant('foo')['database']).to eq 'foo'
155
+ end
156
+
157
+ it 'returns the spec unpdated' do
158
+ spec_updated = @manager.update_tenant('foo', {adapter: 'mysql2', database: 'foo'})
159
+
160
+
161
+ expect(spec_updated['adapter']).to eq 'mysql2'
162
+ expect(spec_updated['database']).to eq 'foo'
163
+ end
164
+
165
+ end
166
+
167
+ describe '#delete_tenant' do
168
+
169
+ it 'returns true' do
170
+ expect(@manager.delete_tenant('foo')).to be_true
171
+ end
172
+
173
+ it 'deletes tenant' do
174
+ @manager.delete_tenant('foo')
175
+ expect(@tenants_source.tenant?('foo')).to be_false
176
+ end
177
+
178
+ it 'removes connection of tenant' do
179
+
180
+ ActiveRecord::Base.connection_handler.establish_connection('foo')
181
+ @manager.delete_tenant('foo')
182
+
183
+ expect(ActiveRecord::Base.connection_handler.active_tenants).not_to include('foo')
184
+ end
185
+
186
+ end
187
+
188
+ describe '#active_tenants' do
189
+
190
+ it 'returns active tenans' do
191
+ ActiveRecord::Base.connection_handler.establish_connection('foo')
192
+ expect(@manager.active_tenants).to include('foo')
193
+ end
194
+
195
+ end
196
+
197
+ describe '#determines_tenant' do
198
+
199
+ context 'tenant environment variable, current tenant and default tenant are set' do
200
+
201
+ it 'returns tenant enviroment variable' do
202
+ ENV['TENANT'] = 'foo'
203
+ @manager.current_tenant = 'bar'
204
+ @manager.default_tenant = 'baz'
205
+
206
+ expect(@manager.determines_tenant).to eq ENV['TENANT']
207
+ end
208
+
209
+ end
210
+
211
+ context 'only current tenant and default tenant are set' do
212
+
213
+ it 'returns tenant enviroment variable' do
214
+ ENV['TENANT'] = nil
215
+ @manager.current_tenant = 'bar'
216
+ @manager.default_tenant = 'baz'
217
+
218
+ expect(@manager.determines_tenant).to eq @manager.current_tenant
219
+ end
220
+
221
+ end
222
+
223
+ context 'only default tenant is set' do
224
+
225
+ it 'returns tenant enviroment variable' do
226
+ ENV['TENANT'] = nil
227
+ @manager.current_tenant = nil
228
+ @manager.default_tenant = 'baz'
229
+
230
+ expect(@manager.determines_tenant).to eq @manager.default_tenant
231
+ end
232
+
233
+ end
234
+
235
+ end
236
+
237
+ end
238
+
@@ -0,0 +1,169 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRecord::Base do
4
+
5
+ before(:all) do
6
+ ActiveRecord::Base.motel.tenants_source_configurations({source: :default})
7
+ ActiveRecord::Base.motel.add_tenant('foo', FOO_SPEC)
8
+ ActiveRecord::Base.motel.add_tenant('bar', BAR_SPEC)
9
+ end
10
+
11
+ after(:all) do
12
+ ActiveRecord::Base.motel.delete_tenant('foo')
13
+ ActiveRecord::Base.motel.delete_tenant('bar')
14
+ end
15
+
16
+ after(:each) do
17
+ ActiveRecord::Base.connection_handler.active_tenants do |tenant|
18
+ ActiveRecord::Base.connection_handler.remove_connection(tenant)
19
+ end
20
+ ActiveRecord::Base.motel.current_tenant = nil
21
+ end
22
+
23
+ describe '.establish_connection' do
24
+
25
+ it 'establishes a connection keyed by the class name' do
26
+ ActiveRecord::Base.establish_connection(BAZ_SPEC)
27
+
28
+ expect(ActiveRecord::Base.connection_handler.active_tenants).to include('ActiveRecord::Base')
29
+ end
30
+
31
+ end
32
+
33
+ describe '.connection_pool' do
34
+
35
+ context 'current tenant established' do
36
+
37
+ it 'returns a connection pool of current tenant' do
38
+ ActiveRecord::Base.motel.current_tenant = 'foo'
39
+ pool = ActiveRecord::Base.connection_handler.establish_connection('foo')
40
+
41
+ expect(ActiveRecord::Base.connection_pool).to eq pool
42
+ end
43
+
44
+ end
45
+
46
+ context 'current tenant not established' do
47
+
48
+ it 'rises an error' do
49
+ ActiveRecord::Base.motel.current_tenant = nil
50
+ expect{ActiveRecord::Base.connection_pool}.to raise_error Motel::NoCurrentTenantError
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+
57
+ describe '.retrieve_connection' do
58
+
59
+ context 'current tenant established' do
60
+
61
+ it 'returns a connection of current tenant' do
62
+ ActiveRecord::Base.motel.current_tenant = 'foo'
63
+ pool = ActiveRecord::Base.connection_handler.establish_connection('foo')
64
+
65
+ expect(ActiveRecord::Base.retrieve_connection).to eq pool.connection
66
+ end
67
+
68
+ end
69
+
70
+ context 'current tenant not established' do
71
+
72
+ it 'rises an error' do
73
+ ActiveRecord::Base.motel.current_tenant = nil
74
+ expect{ActiveRecord::Base.retrieve_connection}.to raise_error Motel::NoCurrentTenantError
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+
81
+ describe '.connected?' do
82
+
83
+ before(:each) do
84
+ ActiveRecord::Base.connection_handler.retrieve_connection('foo')
85
+ end
86
+
87
+ context 'current tenant established' do
88
+
89
+ it 'returns true' do
90
+ ActiveRecord::Base.motel.current_tenant = 'foo'
91
+ expect(ActiveRecord::Base.connected?).to be_true
92
+ end
93
+
94
+ it 'returns false' do
95
+ ActiveRecord::Base.motel.current_tenant = 'bar'
96
+ expect(ActiveRecord::Base.connected?).to be_false
97
+ end
98
+
99
+ end
100
+
101
+ context 'current tenant not established' do
102
+
103
+ it 'rises an error' do
104
+ ActiveRecord::Base.motel.current_tenant = nil
105
+ expect{ActiveRecord::Base.connected?}.to raise_error Motel::NoCurrentTenantError
106
+ end
107
+
108
+ end
109
+
110
+ end
111
+
112
+ describe '.remove_connection' do
113
+
114
+ context 'current tenant established' do
115
+
116
+ it 'removes connection' do
117
+ ActiveRecord::Base.connection_handler.establish_connection('foo')
118
+ ActiveRecord::Base.motel.current_tenant = 'foo'
119
+ ActiveRecord::Base.remove_connection
120
+ expect(ActiveRecord::Base.connection_handler.active_tenants).not_to include('foo')
121
+ end
122
+
123
+ end
124
+
125
+ context 'current tenant not established' do
126
+
127
+ it 'rises an error' do
128
+ ActiveRecord::Base.motel.current_tenant = nil
129
+ expect{ActiveRecord::Base.remove_connection}.to raise_error Motel::NoCurrentTenantError
130
+ end
131
+
132
+ end
133
+
134
+ end
135
+
136
+ describe '.arel_engine' do
137
+
138
+ it 'returns a ActiveRecord::Base class' do
139
+ expect(ActiveRecord::Base.arel_engine).to eq ActiveRecord::Base
140
+ end
141
+
142
+ end
143
+
144
+ describe '.current_tenant' do
145
+
146
+ context 'tenant enviroment variable or current tenant or default tenant are set' do
147
+
148
+ it 'returns the current tenant' do
149
+ ActiveRecord::Base.motel.current_tenant = 'foo'
150
+
151
+ expect(ActiveRecord::Base.current_tenant).to eq 'foo'
152
+ end
153
+
154
+ end
155
+
156
+ context 'no tenant has been established' do
157
+
158
+ it 'rises an error' do
159
+ ActiveRecord::Base.motel.current_tenant = nil
160
+
161
+ expect{ActiveRecord::Base.current_tenant}.to raise_error Motel::NoCurrentTenantError
162
+ end
163
+
164
+ end
165
+
166
+ end
167
+
168
+ end
169
+
@@ -0,0 +1,246 @@
1
+ require 'spec_helper'
2
+
3
+ describe Motel::Sources::Database do
4
+
5
+ before(:all) do
6
+ @klass = Class.new(ActiveRecord::Base) { def self.name; 'klass'; end }
7
+
8
+ resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new(TENANTS_SPEC, nil)
9
+ @handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
10
+ @handler.establish_connection(@klass, resolver.spec)
11
+
12
+ @table_name = 'tenant'
13
+ @tenants_source = Motel::Sources::Database.new(
14
+ source_spec: TENANTS_SPEC, table_name: @table_name
15
+ )
16
+
17
+ @tenant_table_sql = <<-SQL
18
+ CREATE TABLE #{@table_name}(
19
+ `name` VARCHAR PRIMARY KEY,
20
+ `adapter` VARCHAR,
21
+ `socket` VARCHAR,
22
+ `port` INTEGER,
23
+ `pool` INTEGER,
24
+ `host` VARCHAR,
25
+ `username` VARCHAR,
26
+ `password` VARCHAR,
27
+ `database` VARCHAR,
28
+ `url` VARCHAR
29
+ )
30
+ SQL
31
+ end
32
+
33
+ before(:all) do
34
+ @handler.retrieve_connection_pool(@klass).with_connection do |conn|
35
+ conn.execute(@tenant_table_sql)
36
+ end
37
+ end
38
+
39
+ before (:each) do
40
+ @foo_tenant_sql = <<-SQL
41
+ INSERT INTO #{@table_name}(`name`, `adapter`, `database`)
42
+ VALUES ("foo", "#{FOO_SPEC['adapter']}", "#{FOO_SPEC['database']}")
43
+ SQL
44
+
45
+ @bar_tenant_sql = <<-SQL
46
+ INSERT INTO #{@table_name}(`name`, `adapter`, `database`)
47
+ VALUES ("bar", "#{BAR_SPEC['adapter']}", "#{BAR_SPEC['database']}")
48
+ SQL
49
+
50
+ @handler.retrieve_connection_pool(@klass).with_connection do |conn|
51
+ conn.execute(@foo_tenant_sql)
52
+ conn.execute(@bar_tenant_sql)
53
+ end
54
+ end
55
+
56
+ after(:each) do
57
+ @handler.retrieve_connection_pool(@klass).with_connection do |conn|
58
+ conn.execute("DELETE FROM #{@table_name}")
59
+ end
60
+ end
61
+
62
+ after(:all) do
63
+ @handler.retrieve_connection_pool(@klass).with_connection do |conn|
64
+ conn.execute("DROP TABLE #{@table_name}")
65
+ end
66
+ end
67
+
68
+ describe '#tenants' do
69
+
70
+ it 'there are only two tenants' do
71
+ expect(@tenants_source.tenants.count).to eq 2
72
+ end
73
+
74
+ it 'exist foo key' do
75
+ expect(@tenants_source.tenants.key?('foo')).to be_true
76
+ end
77
+
78
+ it 'tenant foo has a correct spec' do
79
+ expect(@tenants_source.tenants['foo']['adapter']).to eq FOO_SPEC['adapter']
80
+ expect(@tenants_source.tenants['foo']['database']).to eq FOO_SPEC['database']
81
+ end
82
+
83
+ it 'exist bar key' do
84
+ expect(@tenants_source.tenants.key?('bar')).to be_true
85
+ end
86
+
87
+ it 'tenant bar has a correct spec' do
88
+ expect(@tenants_source.tenants['bar']['adapter']).to eq BAR_SPEC['adapter']
89
+ expect(@tenants_source.tenants['bar']['database']).to eq BAR_SPEC['database']
90
+ end
91
+
92
+ end
93
+
94
+ describe '#tenant' do
95
+
96
+ context 'existing tenant' do
97
+
98
+ it 'tenant foo has a correct spec' do
99
+ expect(@tenants_source.tenant('foo')['adapter']).to eq FOO_SPEC['adapter']
100
+ expect(@tenants_source.tenant('foo')['database']).to eq FOO_SPEC['database']
101
+ end
102
+
103
+ end
104
+
105
+ context 'nonexistent tenant' do
106
+
107
+ it 'returns null' do
108
+ expect(@tenants_source.tenant('baz')).to be_nil
109
+ end
110
+
111
+ end
112
+
113
+ end
114
+
115
+ describe '#tenant?' do
116
+
117
+ it 'returns true if tenant does exist' do
118
+ expect(@tenants_source.tenant?('foo')).to be_true
119
+ end
120
+
121
+ it 'returns false if tenant does not exist' do
122
+ expect(@tenants_source.tenant?('baz')).to be_false
123
+ end
124
+
125
+ end
126
+
127
+ describe '#add_tenant' do
128
+
129
+ context 'existing tenant' do
130
+
131
+ it 'raise an error' do
132
+ expect{
133
+ @tenants_source.add_tenant('foo', FOO_SPEC)
134
+ }.to raise_error Motel::ExistingTenantError
135
+ end
136
+
137
+ end
138
+
139
+ context 'nonexistent tenant' do
140
+
141
+ context 'spec has keys as strings' do
142
+
143
+ it 'add new tenant to database' do
144
+ @tenants_source.add_tenant(
145
+ 'baz', {'adapter' => BAZ_SPEC['adapter'], 'database' => BAZ_SPEC['database']}
146
+ )
147
+
148
+ result = @handler.retrieve_connection_pool(@klass).with_connection do |conn|
149
+ conn.select_all("SELECT * FROM #{@table_name} WHERE `name` = 'baz'")
150
+ end
151
+
152
+ expect(result.first['adapter']).to eq BAZ_SPEC['adapter']
153
+ expect(result.first['database']).to eq BAZ_SPEC['database']
154
+ end
155
+
156
+ end
157
+
158
+ context 'spec has keys as symbols' do
159
+
160
+ it 'add new tenant to database' do
161
+ @tenants_source.add_tenant(
162
+ 'baz', {adapter: BAZ_SPEC['adapter'] , database: BAZ_SPEC['database']}
163
+ )
164
+
165
+ result = @handler.retrieve_connection_pool(@klass).with_connection do |conn|
166
+ conn.select_all("SELECT * FROM #{@table_name} WHERE `name` = 'baz'")
167
+ end
168
+
169
+ expect(result.first['adapter']).to eq BAZ_SPEC['adapter']
170
+ expect(result.first['database']).to eq BAZ_SPEC['database']
171
+ end
172
+
173
+ end
174
+
175
+ end
176
+
177
+ end
178
+
179
+ describe '#update_tenant' do
180
+
181
+ context 'existing tenant' do
182
+
183
+ context 'full update' do
184
+
185
+ it 'update tenant in the database' do
186
+ @tenants_source.update_tenant(
187
+ 'foo', {adapter: 'mysql2', database: 'foo'}
188
+ )
189
+
190
+ result = @handler.retrieve_connection_pool(@klass).with_connection do |conn|
191
+ conn.select_all("SELECT * FROM #{@table_name} WHERE `name` = 'foo'")
192
+ end
193
+
194
+ expect(result.first['adapter']).to eq 'mysql2'
195
+ expect(result.first['database']).to eq 'foo'
196
+ end
197
+
198
+ end
199
+
200
+ context 'partial update' do
201
+
202
+ it 'update tenant in the database' do
203
+ @tenants_source.update_tenant(
204
+ 'foo', {adapter: 'mysql2'}
205
+ )
206
+
207
+ result = @handler.retrieve_connection_pool(@klass).with_connection do |conn|
208
+ conn.select_all("SELECT * FROM #{@table_name} WHERE `name` = 'foo'")
209
+ end
210
+
211
+ expect(result.first['adapter']).to eq 'mysql2'
212
+ expect(result.first['database']).to eq FOO_SPEC['database']
213
+ end
214
+
215
+ end
216
+
217
+ end
218
+
219
+ context 'nonexistent tenant' do
220
+
221
+ it 'raise an error' do
222
+ expect{
223
+ @tenants_source.update_tenant('baz', {})
224
+ }.to raise_error Motel::NonexistentTenantError
225
+ end
226
+
227
+ end
228
+
229
+ end
230
+
231
+ describe '#delete_tenant' do
232
+
233
+ it 'remove tenant from redis server' do
234
+ @tenants_source.delete_tenant('foo')
235
+
236
+ result = @handler.retrieve_connection_pool(@klass).with_connection do |conn|
237
+ conn.select_all("SELECT * FROM #{@table_name} WHERE `name` = 'foo'")
238
+ end
239
+
240
+ expect(result.count).to eq 0
241
+ end
242
+
243
+ end
244
+
245
+ end
246
+