cql-rb 1.0.0.pre0

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,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