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,80 @@
1
+ require 'redis'
2
+
3
+ module Motel
4
+ module Sources
5
+
6
+ class Redis
7
+
8
+ attr_accessor :host, :port, :password, :path, :prefix_tenant_alias
9
+
10
+ def initialize(config = {})
11
+ @host = config[:host]
12
+ @port = config[:port]
13
+ @password = config[:password]
14
+ @path = config[:path]
15
+ @prefix_tenant_alias = (config[:prefix_tenant_alias] || 'tenant:')
16
+ end
17
+
18
+ def tenants
19
+ redis.keys.inject({}) do |hash, tenant_als|
20
+ if tenant_als.match("^#{prefix_tenant_alias}")
21
+ hash[tenant_name(tenant_als)] = tenant(tenant_name(tenant_als))
22
+ end
23
+ hash
24
+ end
25
+ end
26
+
27
+ def tenant(name)
28
+ spec = redis.hgetall(tenant_alias(name))
29
+ spec if spec.any?
30
+ end
31
+
32
+ def tenant?(name)
33
+ !tenant(name).nil?
34
+ end
35
+
36
+ def add_tenant(name, spec)
37
+ raise ExistingTenantError if tenant?(name)
38
+
39
+ spec.each do |field, value|
40
+ redis.hset(tenant_alias(name), field, value)
41
+ end
42
+ end
43
+
44
+ def update_tenant(name, spec)
45
+ raise NonexistentTenantError unless tenant?(name)
46
+
47
+ spec.each do |field, value|
48
+ redis.hset(tenant_alias(name), field, value)
49
+ end
50
+ end
51
+
52
+ def delete_tenant(name)
53
+ if tenant?(name)
54
+ fields = redis.hkeys tenant_alias(name)
55
+ redis.hdel(tenant_alias(name), [*fields])
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def redis
62
+ @redis ||= begin
63
+ ::Redis.new(host: host, port: port, password: password, path: path)
64
+ end
65
+ end
66
+
67
+ def tenant_alias(name)
68
+ "#{prefix_tenant_alias}#{name}"
69
+ end
70
+
71
+ def tenant_name(tenant_alias)
72
+ name = tenant_alias.match("#{prefix_tenant_alias}(.*)")
73
+ name[1] if name
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+ end
80
+
@@ -0,0 +1,6 @@
1
+ module Motel
2
+
3
+ VERSION = File.read(File.expand_path('../../../VERSION', __FILE__)).strip
4
+
5
+ end
6
+
@@ -0,0 +1,184 @@
1
+ require 'spec_helper'
2
+
3
+ describe Motel::ConnectionAdapters::ConnectionHandler do
4
+
5
+ before(:each) do
6
+ @handler = Motel::ConnectionAdapters::ConnectionHandler.new
7
+ @handler.tenants_source.add_tenant('foo', FOO_SPEC)
8
+ @handler.tenants_source.add_tenant('bar', BAR_SPEC)
9
+ end
10
+
11
+ describe '#establish_connection' do
12
+
13
+ context 'specifying only the tenant name' do
14
+
15
+ context 'existing tenant' do
16
+
17
+ it 'returns an instance of ConnectionPool' do
18
+ expect(
19
+ @handler.establish_connection('foo')
20
+ ).to be_an_instance_of ActiveRecord::ConnectionAdapters::ConnectionPool
21
+ end
22
+
23
+ end
24
+
25
+ context 'nonexistent tenant' do
26
+
27
+ it 'raise an error' do
28
+ expect{
29
+ @handler.establish_connection('baz')
30
+ }.to raise_error Motel::NonexistentTenantError
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ context 'specifying tenant name and the spec' do
38
+
39
+ it 'returns an instance if ConnectionPool' do
40
+ resolver = Motel::ConnectionAdapters::ConnectionSpecification::Resolver.new
41
+ spec = resolver.spec(BAZ_SPEC)
42
+ expect(
43
+ @handler.establish_connection('baz', spec)
44
+ ).to be_an_instance_of ActiveRecord::ConnectionAdapters::ConnectionPool
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
51
+ describe '#retrieve_connection' do
52
+
53
+ context 'existing tenant' do
54
+
55
+ it 'initializes and returns a connection' do
56
+ expect(@handler.retrieve_connection('foo')).to be_true
57
+ end
58
+
59
+ it 'returns a connection' do
60
+ @pool = @handler.establish_connection('foo')
61
+ expect(@handler.retrieve_connection('foo')).to eq @pool.connection
62
+ end
63
+
64
+ end
65
+
66
+ context 'nonexistent tenant' do
67
+
68
+ it 'raise an error' do
69
+ expect{
70
+ @handler.retrieve_connection('baz')
71
+ }.to raise_error Motel::NonexistentTenantError
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+
78
+ describe '#retrieve_connection_pool' do
79
+
80
+ context 'existing tenant' do
81
+
82
+ it 'initializes and returns a connection pool' do
83
+ expect(@handler.retrieve_connection_pool('foo')).to be_true
84
+ end
85
+
86
+ it 'returns a connection pool' do
87
+ @pool = @handler.establish_connection('foo')
88
+ expect(@handler.retrieve_connection_pool('foo')).to eq @pool
89
+ end
90
+
91
+ end
92
+
93
+ context 'nonexistent tenant' do
94
+
95
+ it 'raise an error' do
96
+ expect{
97
+ @handler.retrieve_connection('baz')
98
+ }.to raise_error Motel::NonexistentTenantError
99
+ end
100
+
101
+ end
102
+
103
+ end
104
+
105
+ describe '#connected?' do
106
+
107
+ it 'returns true' do
108
+ @handler.retrieve_connection('foo')
109
+ expect(@handler.connected?('foo')).to be_true
110
+ end
111
+
112
+ it 'returns false' do
113
+ expect(@handler.connected?('foo')).to be_false
114
+ end
115
+
116
+ end
117
+
118
+ describe '#active_connections?' do
119
+
120
+ it 'has not active connections' do
121
+ expect(@handler.active_connections?).to be_false
122
+ end
123
+
124
+ it 'has active connections' do
125
+ @handler.retrieve_connection('foo')
126
+ expect(@handler.active_connections?).to be_true
127
+ end
128
+
129
+ end
130
+
131
+ describe '#remove_connection' do
132
+
133
+ it 'removes the connection' do
134
+ @handler.retrieve_connection('foo')
135
+ expect(@handler.active_connections?).to be_true
136
+
137
+ @handler.remove_connection('foo')
138
+ expect(@handler.active_connections?).to be_false
139
+ end
140
+
141
+ end
142
+
143
+ describe '#active_tenants' do
144
+
145
+ context 'no active tenants' do
146
+
147
+ it 'returns empty array' do
148
+ expect(@handler.active_tenants).to be_empty
149
+ end
150
+
151
+ end
152
+
153
+ context 'are active tenants' do
154
+
155
+ before(:each) do
156
+ @handler.establish_connection('foo')
157
+ @handler.establish_connection('bar')
158
+ end
159
+
160
+
161
+ it 'returns exactly two names' do
162
+ expect(@handler.active_tenants.count).to eq 2
163
+ end
164
+
165
+ it 'returns the names' do
166
+ expect(@handler.active_tenants).to include('foo', 'bar')
167
+ end
168
+
169
+ end
170
+
171
+ end
172
+
173
+ describe '#tenants_sources=' do
174
+
175
+ it 'sets a tenats source for teh resolver' do
176
+ @handler.tenants_source = Motel::Sources::Redis.new
177
+
178
+ expect(@handler.tenants_source).to be_an_instance_of Motel::Sources::Redis
179
+ end
180
+
181
+ end
182
+
183
+ end
184
+
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+
3
+ describe Motel::ConnectionAdapters::ConnectionSpecification::Resolver do
4
+
5
+ describe '#spec' do
6
+
7
+ context 'from a hash' do
8
+
9
+ before(:each) do
10
+ @resolver = Motel::ConnectionAdapters::ConnectionSpecification::Resolver.new
11
+ end
12
+
13
+ context 'specifying adapter' do
14
+
15
+ context 'existing adapter' do
16
+
17
+ it 'returns an instance of ConnectionSpecification' do
18
+ expect(
19
+ @resolver.spec(BAZ_SPEC)
20
+ ).to be_an_instance_of ActiveRecord::ConnectionAdapters::ConnectionSpecification
21
+ end
22
+
23
+ end
24
+
25
+ context 'nonexistent adapter' do
26
+
27
+ it 'rises an error' do
28
+ expect{@resolver.spec({adapter: 'nonexistent_adapter'})}.to raise_error LoadError
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
35
+ context 'adapter unspecified' do
36
+
37
+ it 'rises an error' do
38
+ expect{
39
+ @resolver.spec({database: BAZ_SPEC['database']})
40
+ }.to raise_error ActiveRecord::AdapterNotSpecified
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+
47
+ context 'from a string' do
48
+
49
+ before(:each) do
50
+ @resolver = Motel::ConnectionAdapters::ConnectionSpecification::Resolver.new(
51
+ {'foo' => FOO_SPEC}
52
+ )
53
+ end
54
+
55
+ context 'string as a key' do
56
+
57
+ context 'existing configuration' do
58
+
59
+ it 'returns an instance of ConnectionSpecification' do
60
+ expect(
61
+ @resolver.spec('foo')
62
+ ).to be_an_instance_of ActiveRecord::ConnectionAdapters::ConnectionSpecification
63
+ end
64
+
65
+ end
66
+
67
+ context 'nonexistent configuration' do
68
+
69
+ it 'rises an error' do
70
+ expect{@resolver.spec('baz')}.to raise_error ActiveRecord::AdapterNotSpecified
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+
77
+ context 'string as a url' do
78
+
79
+ before(:all) do
80
+ @url = 'mysql2://foo:foobar_password@localhost:3306/foobar'
81
+ end
82
+
83
+ it 'returns an instance of ConnectionSpecification' do
84
+ expect(
85
+ @resolver.spec(@url)
86
+ ).to be_an_instance_of ActiveRecord::ConnectionAdapters::ConnectionSpecification
87
+ end
88
+
89
+ it 'spec of connection specifications contains a correct adapter' do
90
+ expect(@resolver.spec(@url).config[:adapter]).to eq 'mysql2'
91
+ end
92
+
93
+ it 'spec of connection specifications contains a correct username' do
94
+ expect(@resolver.spec(@url).config[:username]).to eq 'foo'
95
+ end
96
+
97
+ it 'spec of connection specifications contains a correct password' do
98
+ expect(@resolver.spec(@url).config[:password]).to eq 'foobar_password'
99
+ end
100
+
101
+ it 'spec of connection specifications contains a correct host' do
102
+ expect(@resolver.spec(@url).config[:host]).to eq 'localhost'
103
+ end
104
+
105
+ it 'spec of connection specifications contains a correct port' do
106
+ expect(@resolver.spec(@url).config[:port]).to eq 3306
107
+ end
108
+
109
+ it 'spec of connection specifications contains a correct database' do
110
+ expect(@resolver.spec(@url).config[:database]).to eq 'foobar'
111
+ end
112
+
113
+ end
114
+
115
+ end
116
+
117
+ end
118
+
119
+ end
120
+
@@ -0,0 +1,155 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ require 'spec_helper'
4
+ require 'rack/test'
5
+ require 'tempfile'
6
+
7
+ describe Motel::Lobby do
8
+ include Rack::Test::Methods
9
+
10
+ def app
11
+ app = lambda{ |env| [200, {"Content-Type" => "text/html"}, "Test"] }
12
+ Motel::Lobby.new(app)
13
+ end
14
+
15
+ before(:all) do
16
+ ActiveRecord::Base.motel.add_tenant('foo', FOO_SPEC)
17
+ end
18
+
19
+ after(:all) do
20
+ ActiveRecord::Base.motel.delete_tenant('foo')
21
+ ActiveRecord::Base.motel.admission_criteria = nil #sets default
22
+ ActiveRecord::Base.motel.nonexistent_tenant_page = nil #sets default
23
+ end
24
+
25
+ describe '#call' do
26
+
27
+ context 'default admission criteria' do
28
+
29
+ before(:all) do
30
+ ActiveRecord::Base.motel.admission_criteria = nil
31
+ end
32
+
33
+ context 'url match' do
34
+
35
+ context 'existing tenant' do
36
+
37
+ before(:each) do
38
+ @url = 'http://foo.test.com'
39
+ end
40
+
41
+ it 'sets the current tenant' do
42
+ request @url
43
+
44
+ expect(ActiveRecord::Base.motel.current_tenant).to eq 'foo'
45
+ end
46
+
47
+ it 'response is ok' do
48
+ request @url
49
+
50
+ expect(last_response).to be_ok
51
+ end
52
+
53
+ end
54
+
55
+ context 'nonexistent tenant' do
56
+
57
+ before(:each) do
58
+ @url = 'http://bar.test.com'
59
+ end
60
+
61
+ it 'returns 404 code' do
62
+ request @url
63
+
64
+ expect(last_response.status).to eq 404
65
+ end
66
+
67
+ context 'with default nonexistent tenant message' do
68
+
69
+ it 'returns default message on body' do
70
+ ActiveRecord::Base.motel.nonexistent_tenant_page = nil
71
+ request @url
72
+
73
+ expect(last_response.body).to eq "Nonexistent bar tenant"
74
+ end
75
+
76
+ end
77
+
78
+ context 'specifying the path of the page with the nonexistent tenant message' do
79
+
80
+ it 'returns default message on body' do
81
+ message = '<h1>Noexistent tenant<h1>'
82
+ file = Tempfile.new(['status_404', '.html'], TEMP_DIR)
83
+ file.write(message)
84
+ file.close
85
+
86
+ ActiveRecord::Base.motel.nonexistent_tenant_page = file.path
87
+ request @url
88
+
89
+ expect(last_response.body).to eq message
90
+ end
91
+
92
+ end
93
+
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+
102
+ context 'specifying admission criteria' do
103
+
104
+ before(:all) do
105
+ ActiveRecord::Base.motel.admission_criteria = 'tenants\/(\w*)'
106
+ end
107
+
108
+ context 'url match' do
109
+
110
+ context 'existing tenant' do
111
+
112
+ before(:each) do
113
+ @url = 'http://www.example.com/tenants/foo'
114
+ end
115
+
116
+ it 'sets the current tenant' do
117
+ request @url
118
+
119
+ expect(ActiveRecord::Base.motel.current_tenant).to eq 'foo'
120
+ end
121
+
122
+ it 'response is ok' do
123
+ request @url
124
+
125
+ expect(last_response).to be_ok
126
+ end
127
+
128
+ end
129
+
130
+ end
131
+
132
+ context 'url does not match' do
133
+
134
+ before(:each) do
135
+ @url = 'http://example.com'
136
+ end
137
+
138
+ it 'sets null the current tenant' do
139
+ request @url
140
+
141
+ expect(ActiveRecord::Base.motel.current_tenant).to be_nil
142
+ end
143
+
144
+ it 'response is ok' do
145
+ request @url
146
+
147
+ expect(last_response).to be_ok
148
+ end
149
+
150
+ end
151
+
152
+ end
153
+
154
+ end
155
+