cql-rb 1.0.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,746 @@
1
+ # encoding: ascii-8bit
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module Cql
7
+ module Protocol
8
+ describe ResponseFrame do
9
+ let :frame do
10
+ described_class.new
11
+ end
12
+
13
+ context 'when fed no data' do
14
+ it 'has a header length' do
15
+ frame.header_length.should == 8
16
+ end
17
+
18
+ it 'has no body length' do
19
+ frame.body_length.should be_nil
20
+ end
21
+
22
+ it 'is not complete' do
23
+ frame.should_not be_complete
24
+ end
25
+ end
26
+
27
+ context 'when fed a header' do
28
+ before do
29
+ frame << "\x81\x00\x00\x02\x00\x00\x00\x16"
30
+ end
31
+
32
+ it 'knows the frame body length' do
33
+ frame.body_length.should == 22
34
+ end
35
+ end
36
+
37
+ context 'when fed a header in pieces' do
38
+ before do
39
+ frame << "\x81\x00"
40
+ frame << "\x00\x02\x00\x00\x00"
41
+ frame << "\x16"
42
+ end
43
+
44
+ it 'knows the body length' do
45
+ frame.body_length.should == 22
46
+ end
47
+
48
+ it 'knows the stream ID' do
49
+ frame.stream_id.should == 0
50
+ end
51
+ end
52
+
53
+ context 'when fed a header with a non-zero stream ID' do
54
+ before do
55
+ frame << "\x81\x00\x20\x02\x00\x00\x00\x16"
56
+ end
57
+
58
+ it 'knows the stream ID' do
59
+ frame.stream_id.should == 0x20
60
+ end
61
+ end
62
+
63
+ context 'when fed a header with the stream ID -1' do
64
+ before do
65
+ frame << "\x81\x00\xff\x02\x00\x00\x00\x16"
66
+ end
67
+
68
+ it 'knows the stream ID' do
69
+ frame.stream_id.should == -1
70
+ end
71
+ end
72
+
73
+ context 'when fed a request frame header' do
74
+ it 'raises an UnsupportedFrameTypeError' do
75
+ expect { frame << "\x01\x00\x00\x00\x00\x00\x00\x00" }.to raise_error(UnsupportedFrameTypeError)
76
+ end
77
+ end
78
+
79
+ context 'when fed a request frame header' do
80
+ it 'raises an UnsupportedFrameTypeError' do
81
+ expect { frame << "\x01\x00\x00\x00\x00\x00\x00\x00" }.to raise_error(UnsupportedFrameTypeError)
82
+ end
83
+ end
84
+
85
+ context 'when fed a header and a partial body' do
86
+ before do
87
+ frame << "\x81\x00"
88
+ frame << "\x00\x06"
89
+ frame << "\x00\x00\x00\x16"
90
+ frame << [rand(255), rand(255), rand(255), rand(255), rand(255), rand(255), rand(255), rand(255)].pack('C')
91
+ end
92
+
93
+ it 'knows the body length' do
94
+ frame.body_length.should == 22
95
+ end
96
+
97
+ it 'is not complete' do
98
+ frame.should_not be_complete
99
+ end
100
+ end
101
+
102
+ context 'when fed a complete ERROR frame' do
103
+ before do
104
+ frame << "\x81\x00\x00\x00\x00\x00\x00V\x00\x00\x00\n\x00PProvided version 4.0.0 is not supported by this server (supported: 2.0.0, 3.0.0)"
105
+ end
106
+
107
+ it 'is complete' do
108
+ frame.should be_complete
109
+ end
110
+
111
+ it 'has an error code' do
112
+ frame.body.code.should == 10
113
+ end
114
+
115
+ it 'has an error message' do
116
+ frame.body.message.should == 'Provided version 4.0.0 is not supported by this server (supported: 2.0.0, 3.0.0)'
117
+ end
118
+
119
+ it 'has a pretty #to_s representation' do
120
+ frame.body.to_s.should == 'ERROR 10 "Provided version 4.0.0 is not supported by this server (supported: 2.0.0, 3.0.0)"'
121
+ end
122
+ end
123
+
124
+ context 'when fed an ERROR frame with details' do
125
+ it 'has more details for a unavailable error' do
126
+ frame = described_class.new("\x81\x00\x00\x00\x00\x00\x00\x1c\x00\x00\x10\x00\x00\x0cUnavailable!\x00\x05\x00\x00\x00\x03\x00\x00\x00\x02")
127
+ frame.body.details.should == {
128
+ :cl => :all,
129
+ :required => 3,
130
+ :alive => 2
131
+ }
132
+ end
133
+
134
+ it 'has more details for a write_timeout error' do
135
+ frame = described_class.new("\x81\x00\x00\x00\x00\x00\x00K\x00\x00\x11\x00\x000Operation timed out - received only 0 responses.\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\tBATCH_LOG")
136
+ frame.body.details.should == {
137
+ :cl => :one,
138
+ :received => 0,
139
+ :blockfor => 1,
140
+ :write_type => 'BATCH_LOG'
141
+ }
142
+ end
143
+
144
+ it 'has more details for a read_timeout error' do
145
+ frame = described_class.new("\x81\x00\x00\x00\x00\x00\x00\x25\x00\x00\x12\x00\x00\x14Operation timed out.\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x01")
146
+ frame.body.details.should == {
147
+ :cl => :one,
148
+ :received => 0,
149
+ :blockfor => 1,
150
+ :data_present => true
151
+ }
152
+ frame = described_class.new("\x81\x00\x00\x00\x00\x00\x00\x25\x00\x00\x12\x00\x00\x14Operation timed out.\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00")
153
+ frame.body.details.should == {
154
+ :cl => :one,
155
+ :received => 0,
156
+ :blockfor => 1,
157
+ :data_present => false
158
+ }
159
+ end
160
+
161
+ it 'has more details for an already_exists error with a keyspace' do
162
+ frame = described_class.new("\x81\x00\x00\x00\x00\x00\x00\x1e\x00\x00\x24\x00\x00\x10Keyspace exists!\x00\x05stuff\x00\x00")
163
+ frame.body.details.should == {
164
+ :ks => 'stuff',
165
+ :table => '',
166
+ }
167
+ end
168
+
169
+ it 'has more details for an already_exists error with a keyspace and table' do
170
+ frame = described_class.new("\x81\x00\x00\x00\x00\x00\x00\x24\x00\x00\x24\x00\x00\x10Keyspace exists!\x00\x05stuff\x00\x06things")
171
+ frame.body.details.should == {
172
+ :ks => 'stuff',
173
+ :table => 'things',
174
+ }
175
+ end
176
+
177
+ it 'has more details for an unprepared error' do
178
+ frame = described_class.new("\x81\x00\x00\x00\x00\x00\x00\x33\x00\x00\x25\x00\x00\x1bUnknown prepared statement!\x00\x10\xCAH\x7F\x1Ez\x82\xD2<N\x8A\xF35Qq\xA5/")
179
+ frame.body.details.should == {
180
+ :id => "\xCAH\x7F\x1Ez\x82\xD2<N\x8A\xF35Qq\xA5/"
181
+ }
182
+ end
183
+
184
+ it 'has a pretty #to_s representation' do
185
+ frame = described_class.new("\x81\x00\x00\x00\x00\x00\x00\x33\x00\x00\x25\x00\x00\x1bUnknown prepared statement!\x00\x10\xCAH\x7F\x1Ez\x82\xD2<N\x8A\xF35Qq\xA5/")
186
+ frame.body.to_s.should match(/^ERROR 9472 "Unknown prepared statement!" \{:id=>".+?"\}$/)
187
+ end
188
+ end
189
+
190
+ context 'when fed a complete READY frame' do
191
+ before do
192
+ frame << [0x81, 0, 0, 0x02, 0].pack('C4N')
193
+ end
194
+
195
+ it 'is complete' do
196
+ frame.should be_complete
197
+ end
198
+
199
+ it 'has a pretty #to_s representation' do
200
+ frame.body.to_s.should == 'READY'
201
+ end
202
+ end
203
+
204
+ context 'when fed a complete SUPPORTED frame' do
205
+ before do
206
+ frame << "\x81\x00\x00\x06\x00\x00\x00\x27"
207
+ frame << "\x00\x02\x00\x0bCQL_VERSION\x00\x01\x00\x053.0.0\x00\x0bCOMPRESSION\x00\x00"
208
+ end
209
+
210
+ it 'is complete' do
211
+ frame.should be_complete
212
+ end
213
+
214
+ it 'has options' do
215
+ frame.body.options.should == {'CQL_VERSION' => ['3.0.0'], 'COMPRESSION' => []}
216
+ end
217
+
218
+ it 'has a pretty #to_s representation' do
219
+ frame.body.to_s.should == 'SUPPORTED {"CQL_VERSION"=>["3.0.0"], "COMPRESSION"=>[]}'
220
+ end
221
+ end
222
+
223
+ context 'when fed a complete RESULT frame' do
224
+ context 'when it\'s a set_keyspace' do
225
+ before do
226
+ frame << "\x81\x00\x00\b\x00\x00\x00\f"
227
+ frame << "\x00\x00\x00\x03\x00\x06system"
228
+ end
229
+
230
+ it 'has a keyspace' do
231
+ frame.body.keyspace.should == 'system'
232
+ end
233
+
234
+ it 'is not void' do
235
+ frame.body.should_not be_void
236
+ end
237
+
238
+ it 'has a pretty #to_s representation' do
239
+ frame.body.to_s.should == 'RESULT SET_KEYSPACE "system"'
240
+ end
241
+ end
242
+
243
+ context 'when it\'s a schema_change' do
244
+ context 'when it\'s a keyspace change' do
245
+ before do
246
+ frame << "\x81\x00\x00\b\x00\x00\x00\e\x00\x00\x00\x05\x00\aCREATED\x00\ncql_rb_477\x00\x00"
247
+ end
248
+
249
+ it 'has a change description' do
250
+ frame.body.change.should == 'CREATED'
251
+ end
252
+
253
+ it 'has a keyspace' do
254
+ frame.body.keyspace.should == 'cql_rb_477'
255
+ end
256
+
257
+ it 'has no table' do
258
+ frame.body.table.should be_empty
259
+ end
260
+
261
+ it 'is not void' do
262
+ frame.body.should_not be_void
263
+ end
264
+
265
+ it 'has a pretty #to_s representation' do
266
+ frame.body.to_s.should == 'RESULT SCHEMA_CHANGE CREATED "cql_rb_477" ""'
267
+ end
268
+ end
269
+
270
+ context 'when it\'s a table change' do
271
+ before do
272
+ frame << "\x81\x00\x00\b\x00\x00\x00 \x00\x00\x00\x05\x00\aUPDATED\x00\ncql_rb_973\x00\x05users"
273
+ end
274
+
275
+ it 'has a change description' do
276
+ frame.body.change.should == 'UPDATED'
277
+ end
278
+
279
+ it 'has a keyspace' do
280
+ frame.body.keyspace.should == 'cql_rb_973'
281
+ end
282
+
283
+ it 'has a table' do
284
+ frame.body.table.should == 'users'
285
+ end
286
+
287
+ it 'is not void' do
288
+ frame.body.should_not be_void
289
+ end
290
+
291
+ it 'has a pretty #to_s representation' do
292
+ frame.body.to_s.should == 'RESULT SCHEMA_CHANGE UPDATED "cql_rb_973" "users"'
293
+ end
294
+ end
295
+ end
296
+
297
+ context 'when it\'s a void' do
298
+ before do
299
+ frame << "\x81\x00\x00\b\x00\x00\x00\x04\x00\x00\x00\x01"
300
+ end
301
+
302
+ it 'is has a body' do
303
+ frame.body.should_not be_nil
304
+ end
305
+
306
+ it 'is void' do
307
+ frame.body.should be_void
308
+ end
309
+
310
+ it 'has a pretty #to_s representation' do
311
+ frame.body.to_s.should == 'RESULT VOID'
312
+ end
313
+ end
314
+
315
+ context 'when it\'s rows' do
316
+ before do
317
+ frame << "\x81\x00\x00\b\x00\x00\x00~\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\x00\ncql_rb_126\x00\x05users\x00\tuser_name\x00\r\x00\x05email\x00\r\x00\bpassword\x00\r\x00\x00\x00\x02\x00\x00\x00\x04phil\x00\x00\x00\rphil@heck.com\xFF\xFF\xFF\xFF\x00\x00\x00\x03sue\x00\x00\x00\rsue@inter.net\xFF\xFF\xFF\xFF"
318
+ end
319
+
320
+ it 'has rows that are hashes of column name => column value' do
321
+ frame.body.rows.should == [
322
+ {'user_name' => 'phil', 'email' => 'phil@heck.com', 'password' => nil},
323
+ {'user_name' => 'sue', 'email' => 'sue@inter.net', 'password' => nil}
324
+ ]
325
+ end
326
+
327
+ it 'has column metadata' do
328
+ frame.body.metadata.should == [
329
+ ['cql_rb_126', 'users', 'user_name', :varchar],
330
+ ['cql_rb_126', 'users', 'email', :varchar],
331
+ ['cql_rb_126', 'users', 'password', :varchar]
332
+ ]
333
+ end
334
+
335
+ it 'is not void' do
336
+ frame.body.should_not be_void
337
+ end
338
+
339
+ it 'has a pretty #to_s representation' do
340
+ frame.body.to_s.should == 'RESULT ROWS [["cql_rb_126", "users", "user_name", :varchar], ["cql_rb_126", "users", "email", :varchar], ["cql_rb_126", "users", "password", :varchar]] [{"user_name"=>"phil", "email"=>"phil@heck.com", "password"=>nil}, {"user_name"=>"sue", "email"=>"sue@inter.net", "password"=>nil}]'
341
+ end
342
+ end
343
+
344
+ context 'when it\'s rows from different keyspaces' do
345
+ before do
346
+ # TODO: not sure if this is really how it would be for real
347
+ # this frame was constructed from the spec not from an actual result
348
+ frame << "\x81\x00\x00\b\x00\x00\x00\xa7"
349
+ frame << "\x00\x00\x00\x02"
350
+ frame << "\x00\x00\x00\x00"
351
+ frame << "\x00\x00\x00\x03"
352
+ frame << "\x00\ncql_rb_126\x00\x06users1\x00\tuser_name\x00\r"
353
+ frame << "\x00\ncql_rb_127\x00\x06users2\x00\x05email\x00\r"
354
+ frame << "\x00\ncql_rb_128\x00\x06users3\x00\bpassword\x00\r"
355
+ frame << "\x00\x00\x00\x02\x00\x00\x00\x04phil\x00\x00\x00\rphil@heck.com\xFF\xFF\xFF\xFF\x00\x00\x00\x03sue\x00\x00\x00\rsue@inter.net\xFF\xFF\xFF\xFF"
356
+ end
357
+
358
+ it 'has rows that are hashes of column name => column value' do
359
+ frame.body.rows.should == [
360
+ {'user_name' => 'phil', 'email' => 'phil@heck.com', 'password' => nil},
361
+ {'user_name' => 'sue', 'email' => 'sue@inter.net', 'password' => nil}
362
+ ]
363
+ end
364
+
365
+ it 'has column metadata' do
366
+ frame.body.metadata.should == [
367
+ ['cql_rb_126', 'users1', 'user_name', :varchar],
368
+ ['cql_rb_127', 'users2', 'email', :varchar],
369
+ ['cql_rb_128', 'users3', 'password', :varchar]
370
+ ]
371
+ end
372
+ end
373
+
374
+ context 'when it\'s a prepared' do
375
+ before do
376
+ frame << "\x81\x00\x00\b\x00\x00\x00>"
377
+ frame << "\x00\x00\x00\x04\x00\x10\xCAH\x7F\x1Ez\x82\xD2<N\x8A\xF35Qq\xA5/\x00\x00\x00\x01\x00\x00\x00\x01\x00\ncql_rb_911\x00\x05users\x00\tuser_name\x00\r"
378
+ end
379
+
380
+ it 'has an id' do
381
+ frame.body.id.should == "\xCAH\x7F\x1Ez\x82\xD2<N\x8A\xF35Qq\xA5/"
382
+ end
383
+
384
+ it 'has column metadata' do
385
+ frame.body.metadata.should == [['cql_rb_911', 'users', 'user_name', :varchar]]
386
+ end
387
+
388
+ it 'has a pretty #to_s representation' do
389
+ frame.body.to_s.should match(/^RESULT PREPARED [0-9a-f]{32} \[\["cql_rb_911", "users", "user_name", :varchar\]\]$/)
390
+ end
391
+ end
392
+
393
+ context 'with different column types' do
394
+ before do
395
+ # The following test was created by intercepting the frame for the
396
+ # SELECT statement in this CQL exchange
397
+ #
398
+ # CREATE TABLE lots_of_types (
399
+ # ascii_column ASCII,
400
+ # bigint_column BIGINT,
401
+ # blob_column BLOB,
402
+ # boolean_column BOOLEAN,
403
+ # decimal_column DECIMAL,
404
+ # double_column DOUBLE,
405
+ # float_column FLOAT,
406
+ # int_column INT,
407
+ # text_column TEXT,
408
+ # timestamp_column TIMESTAMP,
409
+ # uuid_column UUID,
410
+ # varchar_column VARCHAR,
411
+ # varint_column VARINT,
412
+ # timeuuid_column TIMEUUID,
413
+ # inet_column INET,
414
+ # list_column LIST<ASCII>,
415
+ # map_column MAP<TEXT, BOOLEAN>,
416
+ # set_column SET<BLOB>,
417
+ #
418
+ # PRIMARY KEY (ascii_column)
419
+ # );
420
+ #
421
+ # INSERT INTO lots_of_types (ascii_column, bigint_column, blob_column, boolean_column, decimal_column, double_column, float_column, int_column, text_column, timestamp_column, uuid_column, varchar_column, varint_column, timeuuid_column, inet_column, list_column, map_column, set_column)
422
+ # VALUES (
423
+ # 'hello',
424
+ # 1012312312414123,
425
+ # 'fab45e3456',
426
+ # true,
427
+ # 1042342234234.123423435647768234,
428
+ # 10000.123123123,
429
+ # 12.13,
430
+ # 12348098,
431
+ # 'hello world',
432
+ # 1358013521.123,
433
+ # cfd66ccc-d857-4e90-b1e5-df98a3d40cd6,
434
+ # 'foo',
435
+ # 1231312312331283012830129382342342412123,
436
+ # a4a70900-24e1-11df-8924-001ff3591711,
437
+ # 167772418,
438
+ # ['foo', 'foo', 'bar'],
439
+ # {'foo': true, 'hello': false},
440
+ # {'ab4321', 'afd87ecd'}
441
+ # );
442
+ #
443
+ # SELECT * FROM lots_of_types WHERE ascii_column = 'hello';
444
+
445
+ frame << "\x81\x00\x00\b\x00\x00\x02K"
446
+ frame << "\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x12\x00\x04test\x00\rlots_of_types\x00\fascii_column\x00\x01\x00\rbigint_column\x00\x02\x00\vblob_column\x00\x03\x00\x0Eboolean_column\x00\x04\x00\x0Edecimal_column\x00\x06\x00\rdouble_column\x00\a\x00\ffloat_column\x00\b\x00\vinet_column\x00\x10\x00\nint_column\x00\t\x00\vlist_column\x00 \x00\x01\x00\nmap_column\x00!\x00\r\x00\x04\x00\nset_column\x00\"\x00\x03\x00\vtext_column\x00\r\x00\x10timestamp_column\x00\v\x00\x0Ftimeuuid_column\x00\x0F\x00\vuuid_column\x00\f\x00\x0Evarchar_column\x00\r\x00\rvarint_column\x00\x0E\x00\x00\x00\x01\x00\x00\x00\x05hello\x00\x00\x00\b\x00\x03\x98\xB1S\xC8\x7F\xAB\x00\x00\x00\x05\xFA\xB4^4V\x00\x00\x00\x01\x01\x00\x00\x00\x11\x00\x00\x00\x12\r'\xFDI\xAD\x80f\x11g\xDCfV\xAA\x00\x00\x00\b@\xC3\x88\x0F\xC2\x7F\x9DU\x00\x00\x00\x04AB\x14{\x00\x00\x00\x04\n\x00\x01\x02\x00\x00\x00\x04\x00\xBCj\xC2\x00\x00\x00\x11\x00\x03\x00\x03foo\x00\x03foo\x00\x03bar\x00\x00\x00\x14\x00\x02\x00\x03foo\x00\x01\x01\x00\x05hello\x00\x01\x00\x00\x00\x00\r\x00\x02\x00\x03\xABC!\x00\x04\xAF\xD8~\xCD\x00\x00\x00\vhello world\x00\x00\x00\b\x00\x00\x01</\xE9\xDC\xE3\x00\x00\x00\x10\xA4\xA7\t\x00$\xE1\x11\xDF\x89$\x00\x1F\xF3Y\x17\x11\x00\x00\x00\x10\xCF\xD6l\xCC\xD8WN\x90\xB1\xE5\xDF\x98\xA3\xD4\f\xD6\x00\x00\x00\x03foo\x00\x00\x00\x11\x03\x9EV \x15\f\x03\x9DK\x18\xCDI\\$?\a["
447
+ end
448
+
449
+ it 'decodes ASCII as an ASCII encoded string' do
450
+ frame.body.rows.first['ascii_column'].should == 'hello'
451
+ frame.body.rows.first['ascii_column'].encoding.should == ::Encoding::ASCII
452
+ end
453
+
454
+ it 'decodes BIGINT as a number' do
455
+ frame.body.rows.first['bigint_column'].should == 1012312312414123
456
+ end
457
+
458
+ it 'decodes BLOB as a ASCII-8BIT string' do
459
+ frame.body.rows.first['blob_column'].should == "\xfa\xb4\x5e\x34\x56"
460
+ frame.body.rows.first['blob_column'].encoding.should == ::Encoding::BINARY
461
+ end
462
+
463
+ it 'decodes BOOLEAN as a boolean' do
464
+ frame.body.rows.first['boolean_column'].should equal(true)
465
+ end
466
+
467
+ it 'decodes DECIMAL as a number' do
468
+ frame.body.rows.first['decimal_column'].should == BigDecimal.new('1042342234234.123423435647768234')
469
+ end
470
+
471
+ it 'decodes DOUBLE as a number' do
472
+ frame.body.rows.first['double_column'].should == 10000.123123123
473
+ end
474
+
475
+ it 'decodes FLOAT as a number' do
476
+ frame.body.rows.first['float_column'].should be_within(0.001).of(12.13)
477
+ end
478
+
479
+ it 'decodes INT as a number' do
480
+ frame.body.rows.first['int_column'].should == 12348098
481
+ end
482
+
483
+ it 'decodes TEXT as a UTF-8 encoded string' do
484
+ frame.body.rows.first['text_column'].should == 'hello world'
485
+ frame.body.rows.first['text_column'].encoding.should == ::Encoding::UTF_8
486
+ end
487
+
488
+ it 'decodes TIMESTAMP as a Time' do
489
+ frame.body.rows.first['timestamp_column'].should == Time.at(1358013521.123)
490
+ end
491
+
492
+ it 'decodes UUID as a Uuid' do
493
+ frame.body.rows.first['uuid_column'].should == Uuid.new('cfd66ccc-d857-4e90-b1e5-df98a3d40cd6')
494
+ end
495
+
496
+ it 'decodes VARCHAR as a UTF-8 encoded string' do
497
+ frame.body.rows.first['varchar_column'].should == 'foo'
498
+ frame.body.rows.first['varchar_column'].encoding.should == ::Encoding::UTF_8
499
+ end
500
+
501
+ it 'decodes VARINT as a number' do
502
+ frame.body.rows.first['varint_column'].should == 1231312312331283012830129382342342412123
503
+ end
504
+
505
+ it 'decodes TIMEUUID as a Uuid' do
506
+ frame.body.rows.first['timeuuid_column'].should == Uuid.new('a4a70900-24e1-11df-8924-001ff3591711')
507
+ end
508
+
509
+ it 'decodes INET as a IPAddr' do
510
+ frame.body.rows.first['inet_column'].should == IPAddr.new('10.0.1.2')
511
+ end
512
+
513
+ it 'decodes LIST<ASCII> as an array of ASCII strings' do
514
+ frame.body.rows.first['list_column'].should == ['foo', 'foo', 'bar'].map { |s| s.force_encoding(::Encoding::ASCII) }
515
+ end
516
+
517
+ it 'decodes MAP<TEXT, BOOLEAN> as a hash of UTF-8 strings to booleans' do
518
+ frame.body.rows.first['map_column'].should == {'foo' => true, 'hello' => false}
519
+ end
520
+
521
+ it 'decodes SET<BLOB> as a set of binary strings' do
522
+ frame.body.rows.first['set_column'].should == Set.new(["\xab\x43\x21", "\xaf\xd8\x7e\xcd"].map { |s| s.force_encoding(::Encoding::BINARY) })
523
+ end
524
+
525
+ it 'decodes COUNTER as a number' do
526
+ frame = described_class.new("\x81\x00\x00\b\x00\x00\x00N\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\x00\x04test\x00\x04cnts\x00\x02id\x00\r\x00\x02c1\x00\x05\x00\x02c2\x00\x05\x00\x00\x00\x01\x00\x00\x00\x04theo\x00\x00\x00\b\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\b\x00\x00\x00\x00\x00\x00\x00\x01")
527
+ frame.body.rows.first['c1'].should == 3
528
+ end
529
+
530
+ it 'raises an error when encountering an unknown column type' do
531
+ frame = described_class.new
532
+ frame << "\x81\x00\x00\b\x00\x00\x00E"
533
+ frame << "\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\x00\ncql_rb_328\x00\x05users"
534
+ expect { frame << "\x00\tuser_name\x00\xff\x00\x05email\x00\r\x00\bpassword\x00\r\x00\x00\x00\x00" }.to raise_error(UnsupportedColumnTypeError)
535
+ end
536
+ end
537
+
538
+ context 'when it\'s an unknown type' do
539
+ it 'raises an error' do
540
+ expect { frame << "\x81\x00\x00\b\x00\x00\x00\x05\x00\x00\x00\xffhello" }.to raise_error(UnsupportedResultKindError)
541
+ end
542
+ end
543
+ end
544
+
545
+ context 'when fed a SCHEMA_CHANGE EVENT frame' do
546
+ before do
547
+ frame << "\x81\x00\xFF\f\x00\x00\x00+\x00\rSCHEMA_CHANGE\x00\aDROPPED\x00\ncql_rb_609\x00\x05users"
548
+ end
549
+
550
+ it 'has the stream ID -1' do
551
+ frame.stream_id.should == -1
552
+ end
553
+
554
+ it 'has an event type' do
555
+ frame.body.type.should == 'SCHEMA_CHANGE'
556
+ end
557
+
558
+ it 'has a change' do
559
+ frame.body.change.should == 'DROPPED'
560
+ end
561
+
562
+ it 'has a keyspace' do
563
+ frame.body.keyspace.should == 'cql_rb_609'
564
+ end
565
+
566
+ it 'has a table' do
567
+ frame.body.table.should == 'users'
568
+ end
569
+
570
+ it 'has a pretty #to_s representation' do
571
+ frame.body.to_s.should == 'EVENT SCHEMA_CHANGE DROPPED "cql_rb_609" "users"'
572
+ end
573
+ end
574
+
575
+ context 'when fed a STATUS_CHANGE EVENT frame' do
576
+ before do
577
+ frame << "\x81\x00\xFF\f\x00\x00\x00\x1E\x00\rSTATUS_CHANGE\x00\x04DOWN\x04\x00\x00\x00\x00\x00\x00#R"
578
+ end
579
+
580
+ it 'has the stream ID -1' do
581
+ frame.stream_id.should == -1
582
+ end
583
+
584
+ it 'has an event type' do
585
+ frame.body.type.should == 'STATUS_CHANGE'
586
+ end
587
+
588
+ it 'has a change' do
589
+ frame.body.change.should == 'DOWN'
590
+ end
591
+
592
+ it 'has an address' do
593
+ frame.body.address.should == IPAddr.new('0.0.0.0')
594
+ end
595
+
596
+ it 'has a port' do
597
+ frame.body.port.should == 9042
598
+ end
599
+
600
+ it 'has a pretty #to_s representation' do
601
+ frame.body.to_s.should == 'EVENT STATUS_CHANGE DOWN 0.0.0.0:9042'
602
+ end
603
+ end
604
+
605
+ context 'when fed a TOPOLOGY_CHANGE EVENT frame' do
606
+ before do
607
+ frame << "\x81\x00\xFF\f\x00\x00\x00(\x00\x0FTOPOLOGY_CHANGE\x00\fREMOVED_NODE\x04\x00\x00\x00\x00\x00\x00#R"
608
+ end
609
+
610
+ it 'has the stream ID -1' do
611
+ frame.stream_id.should == -1
612
+ end
613
+
614
+ it 'has an event type' do
615
+ frame.body.type.should == 'TOPOLOGY_CHANGE'
616
+ end
617
+
618
+ it 'has a change' do
619
+ frame.body.change.should == 'REMOVED_NODE'
620
+ end
621
+
622
+ it 'has an address' do
623
+ frame.body.address.should == IPAddr.new('0.0.0.0')
624
+ end
625
+
626
+ it 'has a port' do
627
+ frame.body.port.should == 9042
628
+ end
629
+
630
+ it 'has a pretty #to_s representation' do
631
+ frame.body.to_s.should == 'EVENT TOPOLOGY_CHANGE REMOVED_NODE 0.0.0.0:9042'
632
+ end
633
+ end
634
+
635
+ context 'when fed an unsupported event type' do
636
+ it 'raises an exception' do
637
+ frame = "\x81\x00\xFF\f\x00\x00\x00\x06\x00\x04PING"
638
+ expect { described_class.new(frame) }.to raise_error(UnsupportedEventTypeError, /PING/)
639
+ end
640
+ end
641
+
642
+ context 'when fed an non-existent opcode' do
643
+ it 'raises an UnsupportedOperationError' do
644
+ expect { frame << "\x81\x00\x00\x99\x00\x00\x00\x39" }.to raise_error(UnsupportedOperationError)
645
+ end
646
+ end
647
+
648
+ context 'when fed more bytes than needed' do
649
+ it 'it consumes its bytes, leaving the rest' do
650
+ buffer = "\x81\x00\x00\x06\x00\x00\x00\x27\x00\x02\x00\x0bCQL_VERSION\x00\x01\x00\x053.0.0\x00\x0bCOMPRESSION\x00\x00"
651
+ buffer << "\x81\x00\x00\x00"
652
+ described_class.new(buffer)
653
+ buffer.should == "\x81\x00\x00\x00"
654
+ end
655
+ end
656
+
657
+ context 'when fed a frame that is longer than the specification specifies' do
658
+ it 'it consumes the body length, leaving the rest' do
659
+ buffer = "\x81\x00\x00\x06\x00\x00\x00\x2a"
660
+ buffer << "\x00\x02\x00\x0bCQL_VERSION\x00\x01\x00\x053.0.0\x00\x0bCOMPRESSION\x00\x00"
661
+ buffer << "\xab\xcd\xef\x99"
662
+ described_class.new(buffer)
663
+ buffer.should == "\x99"
664
+ end
665
+ end
666
+
667
+ describe ReadyResponse do
668
+ describe '#eql?' do
669
+ it 'is equal to all other ready responses' do
670
+ ReadyResponse.new.should eql(ReadyResponse.new)
671
+ end
672
+
673
+ it 'aliased as ==' do
674
+ ReadyResponse.new.should == ReadyResponse.new
675
+ end
676
+ end
677
+
678
+ describe '#hash' do
679
+ it 'has the same hash code as all other ready responses' do
680
+ ReadyResponse.new.hash.should == ReadyResponse.new.hash
681
+ end
682
+ end
683
+ end
684
+
685
+ describe SchemaChangeEventResponse do
686
+ describe '#eql?' do
687
+ it 'is equal to an identical response' do
688
+ r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
689
+ r2 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
690
+ r1.should eql(r2)
691
+ end
692
+
693
+ it 'is not equal when the change is different' do
694
+ r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
695
+ r2 = SchemaChangeEventResponse.new('CREATED', 'keyspace_name', 'table_name')
696
+ r1.should_not eql(r2)
697
+ end
698
+
699
+ it 'is not equal when the keyspace is different' do
700
+ r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
701
+ r2 = SchemaChangeEventResponse.new('DELETED', 'eman_ecapsyek', 'table_name')
702
+ r1.should_not eql(r2)
703
+ end
704
+
705
+ it 'is not equal when the table is different' do
706
+ r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
707
+ r2 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'eman_elbat')
708
+ r1.should_not eql(r2)
709
+ end
710
+
711
+ it 'is aliased as ==' do
712
+ r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
713
+ r2 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
714
+ r1.should == r2
715
+ end
716
+ end
717
+
718
+ describe '#hash' do
719
+ it 'is the same for an identical response' do
720
+ r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
721
+ r2 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
722
+ r1.hash.should == r2.hash
723
+ end
724
+
725
+ it 'is not the same when the change is different' do
726
+ r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
727
+ r2 = SchemaChangeEventResponse.new('CREATED', 'keyspace_name', 'table_name')
728
+ r1.hash.should_not == r2.hash
729
+ end
730
+
731
+ it 'is not the same when the keyspace is different' do
732
+ r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
733
+ r2 = SchemaChangeEventResponse.new('DELETED', 'eman_ecapsyek', 'table_name')
734
+ r1.hash.should_not == r2.hash
735
+ end
736
+
737
+ it 'is not the same when the table is different' do
738
+ r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
739
+ r2 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'eman_elbat')
740
+ r1.hash.should_not == r2.hash
741
+ end
742
+ end
743
+ end
744
+ end
745
+ end
746
+ end