cql-rb 1.0.0.pre4 → 1.0.0.pre5

Sign up to get free protection for your applications and to get access to all the features.
@@ -177,47 +177,61 @@ module Cql
177
177
  Future.combine(*futures).get
178
178
  end
179
179
 
180
- it 'performs the request using the connection with the given ID' do
181
- future = Future.new
182
- request = Cql::Protocol::StartupRequest.new
183
- response = "\x81\x00\x00\x02\x00\x00\x00\x00"
184
-
185
- io_reactor.start.on_complete do
186
- io_reactor.add_connection(host, port).on_complete do |c1_id|
187
- io_reactor.add_connection(host, port).on_complete do |c2_id|
188
- q1_future = io_reactor.queue_request(request, c2_id)
189
- q2_future = io_reactor.queue_request(request, c1_id)
190
-
191
- Future.combine(q1_future, q2_future).on_complete do |(_, q1_id), (_, q2_id)|
192
- future.complete!([c1_id, c2_id, q1_id, q2_id])
180
+ context 'with a connection ID' do
181
+ it 'performs the request using the specified connection' do
182
+ future = Future.new
183
+ request = Cql::Protocol::StartupRequest.new
184
+ response = "\x81\x00\x00\x02\x00\x00\x00\x00"
185
+
186
+ io_reactor.start.on_complete do
187
+ io_reactor.add_connection(host, port).on_complete do |c1_id|
188
+ io_reactor.add_connection(host, port).on_complete do |c2_id|
189
+ q1_future = io_reactor.queue_request(request, c2_id)
190
+ q2_future = io_reactor.queue_request(request, c1_id)
191
+
192
+ Future.combine(q1_future, q2_future).on_complete do |(_, q1_id), (_, q2_id)|
193
+ future.complete!([c1_id, c2_id, q1_id, q2_id])
194
+ end
195
+
196
+ server.await_connects!(2)
197
+ server.broadcast!(response.dup)
193
198
  end
194
-
195
- server.await_connects!(2)
196
- server.broadcast!(response.dup)
197
199
  end
198
200
  end
199
- end
200
201
 
201
- connection1_id, connection2_id, query1_id, query2_id = future.value
202
+ connection1_id, connection2_id, query1_id, query2_id = future.value
202
203
 
203
- connection1_id.should_not be_nil
204
- connection2_id.should_not be_nil
205
- query1_id.should == connection2_id
206
- query2_id.should == connection1_id
207
- end
204
+ connection1_id.should_not be_nil
205
+ connection2_id.should_not be_nil
206
+ query1_id.should == connection2_id
207
+ query2_id.should == connection1_id
208
+ end
208
209
 
209
- it 'fails if the connection does not exist' do
210
- f = io_reactor.start.flat_map do
211
- io_reactor.add_connection(host, port).flat_map do
212
- io_reactor.queue_request(Cql::Protocol::StartupRequest.new, 1234)
210
+ it 'fails if the connection does not exist' do
211
+ f = io_reactor.start.flat_map do
212
+ io_reactor.add_connection(host, port).flat_map do
213
+ io_reactor.queue_request(Cql::Protocol::StartupRequest.new, 1234)
214
+ end
213
215
  end
216
+ expect { f.get }.to raise_error(ConnectionNotFoundError)
214
217
  end
215
- expect { f.get }.to raise_error(ConnectionNotFoundError)
216
- end
217
218
 
218
- it 'fails if the connection is busy' do
219
- f = io_reactor.start.flat_map do
220
- io_reactor.add_connection(host, port).flat_map do
219
+ it 'fails if the connection is busy' do
220
+ f = io_reactor.start.flat_map do
221
+ io_reactor.add_connection(host, port).flat_map do
222
+ io_reactor.add_connection(host, port).flat_map do |connection_id|
223
+ 200.times do
224
+ io_reactor.queue_request(Cql::Protocol::OptionsRequest.new, connection_id)
225
+ end
226
+ io_reactor.queue_request(Cql::Protocol::OptionsRequest.new, connection_id)
227
+ end
228
+ end
229
+ end
230
+ expect { f.get }.to raise_error(ConnectionBusyError)
231
+ end
232
+
233
+ it 'fails if the connection is busy, when there is only one connection' do
234
+ f = io_reactor.start.flat_map do
221
235
  io_reactor.add_connection(host, port).flat_map do |connection_id|
222
236
  200.times do
223
237
  io_reactor.queue_request(Cql::Protocol::OptionsRequest.new, connection_id)
@@ -225,12 +239,8 @@ module Cql
225
239
  io_reactor.queue_request(Cql::Protocol::OptionsRequest.new, connection_id)
226
240
  end
227
241
  end
242
+ expect { f.get }.to raise_error(ConnectionBusyError)
228
243
  end
229
- expect { f.get }.to raise_error(ConnectionBusyError)
230
- end
231
-
232
- it 'fails if the connection is busy, when there is only one connection' do
233
- pending 'as it is the reactor doesn\'t try to deliver requests when all connections are busy'
234
244
  end
235
245
 
236
246
  it 'fails if there is an error when encoding the request' do
@@ -73,6 +73,11 @@ module Cql
73
73
  buffer.should eql_bytes("\x00\x00")
74
74
  end
75
75
 
76
+ it 'encodes a non-string' do
77
+ Encoding.write_string(buffer, 42)
78
+ buffer.should eql_bytes("\x00\x0242")
79
+ end
80
+
76
81
  it 'appends to the buffer' do
77
82
  buffer << "\xab"
78
83
  buffer.force_encoding(::Encoding::BINARY)
@@ -6,6 +6,21 @@ require 'spec_helper'
6
6
  module Cql
7
7
  module Protocol
8
8
  describe RequestFrame do
9
+ context 'with CREDENTIALS requests' do
10
+ it 'encodes a CREDENTIALS request' do
11
+ bytes = RequestFrame.new(CredentialsRequest.new('username' => 'cassandra', 'password' => 'ardnassac')).write('')
12
+ bytes.should == (
13
+ "\x01\x00\x00\04" +
14
+ "\x00\x00\x00\x2c" +
15
+ "\x00\x02" +
16
+ "\x00\x08username" +
17
+ "\x00\x09cassandra" +
18
+ "\x00\x08password" +
19
+ "\x00\x09ardnassac"
20
+ )
21
+ end
22
+ end
23
+
9
24
  context 'with OPTIONS requests' do
10
25
  it 'encodes an OPTIONS request' do
11
26
  bytes = RequestFrame.new(OptionsRequest.new).write('')
@@ -150,6 +165,65 @@ module Cql
150
165
  end
151
166
  end
152
167
 
168
+ describe CredentialsRequest do
169
+ describe '#to_s' do
170
+ it 'returns a pretty string' do
171
+ request = CredentialsRequest.new('foo' => 'bar', 'hello' => 'world')
172
+ request.to_s.should == 'CREDENTIALS {"foo"=>"bar", "hello"=>"world"}'
173
+ end
174
+ end
175
+
176
+ describe '#eql?' do
177
+ it 'returns when the credentials are the same' do
178
+ c1 = CredentialsRequest.new('username' => 'foo', 'password' => 'bar')
179
+ c2 = CredentialsRequest.new('username' => 'foo', 'password' => 'bar')
180
+ c2.should eql(c2)
181
+ end
182
+
183
+ it 'returns when the credentials are equivalent' do
184
+ pending 'this would be nice, but is hardly necessary' do
185
+ c1 = CredentialsRequest.new(:username => 'foo', :password => 'bar')
186
+ c2 = CredentialsRequest.new('username' => 'foo', 'password' => 'bar')
187
+ c1.should eql(c2)
188
+ end
189
+ end
190
+
191
+ it 'returns false when the credentials are different' do
192
+ c1 = CredentialsRequest.new('username' => 'foo', 'password' => 'world')
193
+ c2 = CredentialsRequest.new('username' => 'foo', 'hello' => 'world')
194
+ c1.should_not eql(c2)
195
+ end
196
+
197
+ it 'is aliased as ==' do
198
+ c1 = CredentialsRequest.new('username' => 'foo', 'password' => 'bar')
199
+ c2 = CredentialsRequest.new('username' => 'foo', 'password' => 'bar')
200
+ c1.should == c2
201
+ end
202
+ end
203
+
204
+ describe '#hash' do
205
+ it 'has the same hash code as another identical object' do
206
+ c1 = CredentialsRequest.new('username' => 'foo', 'password' => 'bar')
207
+ c2 = CredentialsRequest.new('username' => 'foo', 'password' => 'bar')
208
+ c1.hash.should == c2.hash
209
+ end
210
+
211
+ it 'has the same hash code as another object with equivalent credentials' do
212
+ pending 'this would be nice, but is hardly necessary' do
213
+ c1 = CredentialsRequest.new(:username => 'foo', :password => 'bar')
214
+ c2 = CredentialsRequest.new('username' => 'foo', 'password' => 'bar')
215
+ c1.hash.should == c2.hash
216
+ end
217
+ end
218
+
219
+ it 'does not have the same hash code when the credentials are different' do
220
+ c1 = CredentialsRequest.new('username' => 'foo', 'password' => 'world')
221
+ c2 = CredentialsRequest.new('username' => 'foo', 'hello' => 'world')
222
+ c1.hash.should_not == c2.hash
223
+ end
224
+ end
225
+ end
226
+
153
227
  describe OptionsRequest do
154
228
  describe '#to_s' do
155
229
  it 'returns a pretty string' do
@@ -201,6 +201,24 @@ module Cql
201
201
  end
202
202
  end
203
203
 
204
+ context 'when fed a complete AUTHENTICATE frame' do
205
+ before do
206
+ frame << "\x81\x00\x00\x03\x00\x00\x001\x00/org.apache.cassandra.auth.PasswordAuthenticator"
207
+ end
208
+
209
+ it 'is complete' do
210
+ frame.should be_complete
211
+ end
212
+
213
+ it 'has an authentication class' do
214
+ frame.body.authentication_class.should == 'org.apache.cassandra.auth.PasswordAuthenticator'
215
+ end
216
+
217
+ it 'has a pretty #to_s representation' do
218
+ frame.body.to_s.should == 'AUTHENTICATE org.apache.cassandra.auth.PasswordAuthenticator'
219
+ end
220
+ end
221
+
204
222
  context 'when fed a complete SUPPORTED frame' do
205
223
  before do
206
224
  frame << "\x81\x00\x00\x06\x00\x00\x00\x27"
@@ -5,7 +5,7 @@ require 'spec_helper'
5
5
 
6
6
  describe 'A CQL client' do
7
7
  let :connection_options do
8
- {:host => ENV['CASSANDRA_HOST']}
8
+ {:host => ENV['CASSANDRA_HOST'], :credentials => {:username => 'cassandra', :password => 'cassandra'}}
9
9
  end
10
10
 
11
11
  let :client do
@@ -13,11 +13,11 @@ describe 'A CQL client' do
13
13
  end
14
14
 
15
15
  before do
16
- client.start!
16
+ client.connect
17
17
  end
18
18
 
19
19
  after do
20
- client.shutdown!
20
+ client.close
21
21
  end
22
22
 
23
23
  it 'executes a query and returns the result' do
@@ -38,12 +38,12 @@ describe 'A CQL client' do
38
38
 
39
39
  it 'can be initialized with a keyspace' do
40
40
  c = Cql::Client.new(connection_options.merge(:keyspace => 'system'))
41
- c.start!
41
+ c.connect
42
42
  begin
43
43
  c.keyspace.should == 'system'
44
44
  expect { c.execute('SELECT * FROM schema_keyspaces') }.to_not raise_error
45
45
  ensure
46
- c.shutdown!
46
+ c.close
47
47
  end
48
48
  end
49
49
 
@@ -68,12 +68,12 @@ describe 'A CQL client' do
68
68
  end
69
69
 
70
70
  before do
71
- client.shutdown!
72
- multi_client.start!
71
+ client.close
72
+ multi_client.connect
73
73
  end
74
74
 
75
75
  after do
76
- multi_client.shutdown!
76
+ multi_client.close
77
77
  end
78
78
 
79
79
  it 'handles keyspace changes with #use' do
@@ -101,6 +101,43 @@ describe 'A CQL client' do
101
101
  end
102
102
  end
103
103
 
104
+ context 'with authentication' do
105
+ let :client do
106
+ stub(:client, connect: nil, close: nil)
107
+ end
108
+
109
+ let :authentication_enabled do
110
+ begin
111
+ Cql::Client.connect(connection_options.merge(credentials: nil))
112
+ false
113
+ rescue Cql::AuthenticationError
114
+ true
115
+ end
116
+ end
117
+
118
+ it 'send credentials given in :credentials' do
119
+ client = Cql::Client.connect(connection_options.merge(credentials: {username: 'cassandra', password: 'cassandra'}))
120
+ client.execute('SELECT * FROM system.schema_keyspaces')
121
+ end
122
+
123
+ it 'raises an error when no credentials have been given' do
124
+ if authentication_enabled
125
+ expect { Cql::Client.connect(connection_options.merge(credentials: nil)) }.to raise_error(Cql::AuthenticationError)
126
+ else
127
+ pending 'authentication not configured'
128
+ end
129
+ end
130
+
131
+ it 'raises an error when the credentials are bad' do
132
+ if authentication_enabled
133
+ client = Cql::Client.new(connection_options.merge(credentials: {username: 'foo', password: 'bar'}))
134
+ expect { client.connect }.to raise_error(Cql::AuthenticationError)
135
+ else
136
+ pending 'authentication not configured'
137
+ end
138
+ end
139
+ end
140
+
104
141
  context 'with error conditions' do
105
142
  it 'raises an error for CQL syntax errors' do
106
143
  expect { client.execute('BAD cql') }.to raise_error(Cql::CqlError)
@@ -112,7 +149,7 @@ describe 'A CQL client' do
112
149
 
113
150
  it 'fails gracefully when connecting to the Thrift port' do
114
151
  client = Cql::Client.new(connection_options.merge(port: 9160))
115
- expect { client.start! }.to raise_error(Cql::IoError)
152
+ expect { client.connect }.to raise_error(Cql::IoError)
116
153
  end
117
154
  end
118
155
  end
@@ -19,10 +19,21 @@ describe 'Protocol parsing and communication' do
19
19
  io_reactor.stop.get if io_reactor.running?
20
20
  end
21
21
 
22
- def execute_request(request)
22
+ def raw_execute_request(request)
23
23
  io_reactor.queue_request(request).get.first
24
24
  end
25
25
 
26
+ def execute_request(request)
27
+ response = raw_execute_request(request)
28
+ if response.is_a?(Cql::Protocol::AuthenticateResponse)
29
+ unless response.authentication_class == 'org.apache.cassandra.auth.PasswordAuthenticator'
30
+ raise "Cassandra required an unsupported authenticator: #{response.authentication_class}"
31
+ end
32
+ response = execute_request(Cql::Protocol::CredentialsRequest.new('username' => 'cassandra', 'password' => 'cassandra'))
33
+ end
34
+ response
35
+ end
36
+
26
37
  def query(cql, consistency=:one)
27
38
  response = execute_request(Cql::Protocol::QueryRequest.new(cql, consistency))
28
39
  raise response.to_s if response.is_a?(Cql::Protocol::ErrorResponse)
@@ -83,15 +94,73 @@ describe 'Protocol parsing and communication' do
83
94
  response.options.should have_key('CQL_VERSION')
84
95
  end
85
96
 
86
- it 'sends STARTUP and receives READY' do
87
- response = execute_request(Cql::Protocol::StartupRequest.new)
88
- response.should be_a(Cql::Protocol::ReadyResponse)
97
+ context 'when authentication is not required' do
98
+ it 'sends STARTUP and receives READY' do
99
+ response = execute_request(Cql::Protocol::StartupRequest.new)
100
+ response.should be_a(Cql::Protocol::ReadyResponse)
101
+ end
102
+
103
+ it 'sends a bad STARTUP and receives ERROR' do
104
+ response = execute_request(Cql::Protocol::StartupRequest.new('9.9.9'))
105
+ response.code.should == 10
106
+ response.message.should include('not supported')
107
+ end
89
108
  end
90
109
 
91
- it 'sends a bad STARTUP and receives ERROR' do
92
- response = execute_request(Cql::Protocol::StartupRequest.new('9.9.9'))
93
- response.code.should == 10
94
- response.message.should include('not supported')
110
+ context 'when authentication is required' do
111
+ let :authentication_enabled do
112
+ ir = Cql::Io::IoReactor.new
113
+ ir.start
114
+ connected = ir.add_connection(ENV['CASSANDRA_HOST'], 9042)
115
+ started = connected.flat_map do
116
+ ir.queue_request(Cql::Protocol::StartupRequest.new)
117
+ end
118
+ response = started.get.first
119
+ required = response.is_a?(Cql::Protocol::AuthenticateResponse)
120
+ ir.stop.get
121
+ required
122
+ end
123
+
124
+ it 'sends STARTUP and receives AUTHENTICATE' do
125
+ if authentication_enabled
126
+ response = raw_execute_request(Cql::Protocol::StartupRequest.new)
127
+ response.should be_a(Cql::Protocol::AuthenticateResponse)
128
+ else
129
+ pending 'authentication not configured'
130
+ end
131
+ end
132
+
133
+ it 'ignores the AUTHENTICATE response and receives ERROR' do
134
+ if authentication_enabled
135
+ raw_execute_request(Cql::Protocol::StartupRequest.new)
136
+ response = raw_execute_request(Cql::Protocol::RegisterRequest.new('TOPOLOGY_CHANGE'))
137
+ response.code.should == 10
138
+ response.message.should include('needs authentication')
139
+ else
140
+ pending 'authentication not configured'
141
+ end
142
+ end
143
+
144
+ it 'sends STARTUP followed by CREDENTIALS and receives READY' do
145
+ if authentication_enabled
146
+ raw_execute_request(Cql::Protocol::StartupRequest.new)
147
+ response = raw_execute_request(Cql::Protocol::CredentialsRequest.new('username' => 'cassandra', 'password' => 'cassandra'))
148
+ response.should be_a(Cql::Protocol::ReadyResponse)
149
+ else
150
+ pending 'authentication not configured'
151
+ end
152
+ end
153
+
154
+ it 'sends bad username and password in CREDENTIALS and receives ERROR' do
155
+ if authentication_enabled
156
+ raw_execute_request(Cql::Protocol::StartupRequest.new)
157
+ response = raw_execute_request(Cql::Protocol::CredentialsRequest.new('username' => 'foo', 'password' => 'bar'))
158
+ response.code.should == 0x100
159
+ response.message.should include('Username and/or password are incorrect')
160
+ else
161
+ pending 'authentication not configured'
162
+ end
163
+ end
95
164
  end
96
165
  end
97
166
 
@@ -223,12 +292,12 @@ describe 'Protocol parsing and communication' do
223
292
  end
224
293
  end
225
294
 
226
- it 'sends a TRUNCATE command' do
227
- in_keyspace_with_table do
228
- response = query(%<TRUNCATE users>)
229
- response.should be_void
230
- end
231
- end
295
+ # it 'sends a TRUNCATE command' do
296
+ # in_keyspace_with_table do
297
+ # response = query(%<TRUNCATE users>)
298
+ # response.should be_void
299
+ # end
300
+ # end
232
301
 
233
302
  it 'sends a BATCH command' do
234
303
  in_keyspace_with_table do