pod4 0.7.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,525 @@
1
+ #
2
+ # Supplemental test for Sequel -- tests jRuby / Pod4 / Sequel / JDBC / MS SQL SERVER
3
+ #
4
+
5
+ require 'pod4/sequel_interface'
6
+
7
+ require 'sequel'
8
+ require 'date'
9
+ require 'time'
10
+ require 'bigdecimal'
11
+
12
+ # Note that Sequel insists on *THIS* JDBC driver for MSSQL access.
13
+ require 'sqljdbc4.jar' # from jdbc-mssqlserver
14
+
15
+
16
+ class TestSequelInterfaceMs < SequelInterface
17
+ set_table :customer
18
+ set_id_fld :id
19
+ end
20
+
21
+ class SchemaSequelInterfaceMs < SequelInterface
22
+ set_schema :public
23
+ set_table :customer
24
+ set_id_fld :id
25
+ end
26
+
27
+ class ProdSequelInterfaceMs < SequelInterface
28
+ set_table :product
29
+ set_id_fld :code
30
+ end
31
+
32
+
33
+ describe TestSequelInterfaceMs do
34
+
35
+ let(:data) do
36
+ d = []
37
+ d << { name: 'Barney',
38
+ level: 1.23,
39
+ day: Date.parse("2016-01-01"),
40
+ timestamp: Time.parse('2015-01-01 12:11'),
41
+ qty: BigDecimal.new("1.24"),
42
+ price: nil }
43
+
44
+ d << { name: 'Fred',
45
+ level: 2.34,
46
+ day: Date.parse("2016-02-02"),
47
+ timestamp: Time.parse('2015-01-02 12:22'),
48
+ qty: BigDecimal.new("2.35"),
49
+ price: nil }
50
+
51
+ d << { name: 'Betty',
52
+ level: 3.45,
53
+ day: Date.parse("2016-03-03"),
54
+ timestamp: Time.parse('2015-01-03 12:33'),
55
+ qty: BigDecimal.new("3.46"),
56
+ price: nil }
57
+
58
+ d
59
+ end
60
+
61
+ def fill_data(ifce)
62
+ data.each{|r| ifce.create(r) }
63
+ end
64
+
65
+ def fill_product_data(ifce)
66
+ ifce.create( {code: "foo", name: "bar"} )
67
+ end
68
+
69
+
70
+ let (:db) do
71
+ uri = 'sqlexpress.jhallpr.com\sqleswingshiftdv'
72
+ db = 'pod4_test'
73
+ user = 'pod4test'
74
+ pass = 'pod4test'
75
+ db = Sequel.connect("jdbc:sqlserver://#{uri};database=#{db};user=#{user};password=#{pass}")
76
+ db.extension(:connection_validator)
77
+
78
+ db.run %Q|if exists (select * from INFORMATION_SCHEMA.TABLES
79
+ where TABLE_NAME = 'customer'
80
+ AND TABLE_SCHEMA = 'dbo' )
81
+ drop table dbo.customer;|
82
+
83
+ db.run %Q|if exists (select * from INFORMATION_SCHEMA.TABLES
84
+ where TABLE_NAME = 'product'
85
+ AND TABLE_SCHEMA = 'dbo' )
86
+ drop table dbo.product;|
87
+
88
+ db.run %Q|create table dbo.customer (
89
+ id int identity(1,1) not null,
90
+ name nvarchar(max),
91
+ level real null,
92
+ day date null,
93
+ timestamp datetime null,
94
+ price money null,
95
+ qty numeric(5,2) null );|
96
+
97
+ db.run %Q|create table dbo.product (
98
+ code nvarchar(20),
99
+ name nvarchar(max) );|
100
+
101
+ db
102
+ end
103
+
104
+ let(:interface) { TestSequelInterfaceMs.new(db) }
105
+ let(:prod_interface) { ProdSequelInterfaceMs.new(db) }
106
+
107
+ before do
108
+ # TRUNCATE TABLE also resets the identity counter
109
+ interface.execute %Q|
110
+ truncate table customer;
111
+ truncate table product;|
112
+
113
+ fill_data(interface)
114
+ end
115
+
116
+ ##
117
+
118
+
119
+ describe '#quoted_table' do
120
+
121
+ it 'returns just the table when the schema is not set' do
122
+ expect( interface.quoted_table.downcase ).to eq( %Q|[customer]| )
123
+ end
124
+
125
+ it 'returns the schema plus table when the schema is set' do
126
+ ifce = SchemaSequelInterfaceMs.new(db)
127
+ expect( ifce.quoted_table.downcase ).to eq( %|[public].[customer]| )
128
+ end
129
+
130
+ end
131
+ ##
132
+
133
+
134
+ describe '#create' do
135
+
136
+ let(:hash) { {name: 'Bam-Bam', qty: 4.44} }
137
+ let(:ot) { Octothorpe.new(name: 'Wilma', qty: 5.55) }
138
+
139
+ it 'raises a Pod4::DatabaseError if anything goes wrong' do
140
+ expect{ interface.create(one: 'two') }.to raise_exception DatabaseError
141
+ end
142
+
143
+ it 'creates the record when given a hash' do
144
+ # kinda impossible to seperate these two tests
145
+ id = interface.create(hash)
146
+
147
+ expect{ interface.read(id) }.not_to raise_exception
148
+ expect( interface.read(id).to_h ).to include hash
149
+ end
150
+
151
+ it 'creates the record when given an Octothorpe' do
152
+ id = interface.create(ot)
153
+
154
+ expect{ interface.read(id) }.not_to raise_exception
155
+ expect( interface.read(id).to_h ).to include ot.to_h
156
+ end
157
+
158
+ it 'does not freak out if the hash has symbol values' do
159
+ # Which, Sequel does
160
+ expect{ interface.create(name: :Booboo) }.not_to raise_exception
161
+ end
162
+
163
+ it 'shouldn\'t have a problem with record values of nil' do
164
+ record = {name: 'Ranger', price: nil}
165
+ expect{ interface.create(record) }.not_to raise_exception
166
+ id = interface.create(record)
167
+ expect( interface.read(id).to_h ).to include(record)
168
+ end
169
+
170
+ it 'shouldn\'t have a problem with strings containing special characters' do
171
+ record = {name: "T'Challa[]", price: nil}
172
+ expect{ interface.create(record) }.not_to raise_exception
173
+ id = interface.create(record)
174
+ expect( interface.read(id).to_h ).to include(record)
175
+ end
176
+
177
+ it 'shouldn\'t have a problem with non-integer keys' do
178
+ hash = {code: "foo", name: "bar"}
179
+ id = prod_interface.create( Octothorpe.new(hash) )
180
+
181
+ expect( id ).to eq "foo"
182
+ expect{ prod_interface.read("foo") }.not_to raise_exception
183
+ expect( prod_interface.read("foo").to_h ).to include hash
184
+ end
185
+
186
+ end
187
+ ##
188
+
189
+
190
+ describe '#read' do
191
+
192
+ it 'returns the record for the id as an Octothorpe' do
193
+ expect( interface.read(2).to_h ).to include(name: 'Fred', qty: 2.35)
194
+ end
195
+
196
+ it 'raises a Pod4::CantContinue if the ID is bad' do
197
+ expect{ interface.read(:foo) }.to raise_exception CantContinue
198
+ end
199
+
200
+ it 'returns an empty Octothorpe if no record matches the ID' do
201
+ expect{ interface.read(99) }.not_to raise_exception
202
+ expect( interface.read(99) ).to be_a_kind_of Octothorpe
203
+ expect( interface.read(99) ).to be_empty
204
+ end
205
+
206
+ it 'returns real fields as Float' do
207
+ level = interface.read(1).>>.level
208
+
209
+ expect( level ).to be_a_kind_of Float
210
+ expect( level ).to be_within(0.001).of( data.first[:level] )
211
+ end
212
+
213
+ it 'returns date fields as Date' do
214
+ date = interface.read(1).>>.day
215
+
216
+ expect( date ).to be_a_kind_of Date
217
+ expect( date ).to eq data.first[:day]
218
+ end
219
+
220
+ it 'returns datetime fields as Time' do
221
+ timestamp = interface.read(1).>>.timestamp
222
+
223
+ expect( timestamp ).to be_a_kind_of Time
224
+ expect( timestamp ).to eq data.first[:timestamp]
225
+ end
226
+
227
+ it 'returns numeric fields as BigDecimal' do
228
+ qty = interface.read(1).>>.qty
229
+
230
+ expect( qty ).to be_a_kind_of BigDecimal
231
+ expect( qty ).to eq data.first[:qty]
232
+ end
233
+
234
+ it 'returns money fields as bigdecimal' do
235
+ dibble = { name: 'Dibble',
236
+ level: 4.56,
237
+ day: Date.parse("2016-03-03"),
238
+ timestamp: Time.parse('2015-01-03 12:44'),
239
+ qty: BigDecimal.new("4.57"),
240
+ price: BigDecimal.new("4.58") }
241
+
242
+ interface.create(dibble)
243
+ price = interface.read(4).>>.price
244
+
245
+ expect( price ).to be_a_kind_of BigDecimal
246
+ expect( price ).to eq dibble[:price]
247
+ end
248
+
249
+ it 'shouldn\'t have a problem with non-integer keys' do
250
+ # this is a 100% overlap with the create test above...
251
+ fill_product_data(prod_interface)
252
+
253
+ expect{ prod_interface.read("foo") }.not_to raise_exception
254
+ expect( prod_interface.read("foo").to_h ).to include(code: "foo", name: "bar")
255
+ end
256
+
257
+ end
258
+ ##
259
+
260
+
261
+ describe '#list' do
262
+
263
+ it 'returns an array of Octothorpes that match the records' do
264
+ arr = interface.list.map {|ot| x = ot.to_h}
265
+
266
+ expect( arr.size ).to eq(data.size)
267
+
268
+ data.each do |d|
269
+ r = arr.find{|x| x[:name] == d[:name] }
270
+ expect( r ).not_to be_nil
271
+ expect( r[:level] ).to be_within(0.001).of( d[:level] )
272
+ expect( r[:day] ).to eq d[:day]
273
+ expect( r[:timestamp] ).to eq d[:timestamp]
274
+ expect( r[:qty] ).to eq d[:qty]
275
+ end
276
+
277
+ end
278
+
279
+ it 'returns a subset of records based on the selection parameter' do
280
+ expect( interface.list(name: 'Fred').size ).to eq 1
281
+
282
+ expect( interface.list(name: 'Betty').first.to_h ).
283
+ to include(name: 'Betty', qty: 3.46)
284
+
285
+ end
286
+
287
+ it 'returns an empty Array if nothing matches' do
288
+ expect( interface.list(name: 'Yogi') ).to eq([])
289
+ end
290
+
291
+ it 'raises DatabaseError if the selection criteria is nonsensical' do
292
+ expect{ interface.list('foo') }.to raise_exception Pod4::DatabaseError
293
+ end
294
+
295
+ it 'returns an empty array if there is no data' do
296
+ interface.list.each {|x| interface.delete(x[interface.id_fld]) }
297
+ expect( interface.list ).to eq([])
298
+ end
299
+
300
+ it 'does not freak out if the hash has symbol values' do
301
+ # Which, Sequel does
302
+ expect{ interface.list(name: :Barney) }.not_to raise_exception
303
+ end
304
+
305
+
306
+ end
307
+ ##
308
+
309
+
310
+ describe '#update' do
311
+
312
+ let(:id) { interface.list.first[:id] }
313
+
314
+ it 'updates the record at ID with record parameter' do
315
+ record = {name: 'Booboo', qty: 99.99}
316
+ interface.update(id, record)
317
+
318
+ booboo = interface.read(id)
319
+ expect( booboo.>>.name ).to eq( record[:name] )
320
+ expect( booboo.>>.qty.to_f ).to eq( record[:qty] )
321
+ end
322
+
323
+ it 'raises a CantContinue if anything weird happens with the ID' do
324
+ expect{ interface.update(99, name: 'Booboo') }.
325
+ to raise_exception CantContinue
326
+
327
+ end
328
+
329
+ it 'raises a DatabaseError if anything weird happensi with the record' do
330
+ expect{ interface.update(id, smarts: 'more') }.
331
+ to raise_exception DatabaseError
332
+
333
+ end
334
+
335
+ it 'does not freak out if the hash has symbol values' do
336
+ # Which, Sequel does
337
+ expect{ interface.update(id, name: :Booboo) }.not_to raise_exception
338
+ end
339
+
340
+ it 'shouldnt have a problem with record values of nil' do
341
+ record = {name: 'Ranger', qty: nil}
342
+ expect{ interface.update(id, record) }.not_to raise_exception
343
+ expect( interface.read(id).to_h ).to include(record)
344
+ end
345
+
346
+ it 'shouldnt have a problem with strings containing special characters' do
347
+ record = {name: "T'Challa[]", qty: nil}
348
+ expect{ interface.update(id, record) }.not_to raise_exception
349
+ expect( interface.read(id).to_h ).to include(record)
350
+ end
351
+
352
+ it 'shouldn\'t have a problem with non-integer keys' do
353
+ fill_product_data(prod_interface)
354
+ expect{ prod_interface.update("foo", name: "baz") }.not_to raise_error
355
+ expect( prod_interface.read("foo").to_h[:name] ).to eq "baz"
356
+ end
357
+
358
+ end
359
+ ##
360
+
361
+
362
+ describe '#delete' do
363
+
364
+ def list_contains(ifce, id)
365
+ ifce.list.find {|x| x[ifce.id_fld] == id }
366
+ end
367
+
368
+ let(:id) { interface.list.first[:id] }
369
+
370
+ it 'raises CantContinue if anything hinky happens with the ID' do
371
+ expect{ interface.delete(:foo) }.to raise_exception CantContinue
372
+ expect{ interface.delete(99) }.to raise_exception CantContinue
373
+ end
374
+
375
+ it 'makes the record at ID go away' do
376
+ expect( list_contains(interface, id) ).to be_truthy
377
+ interface.delete(id)
378
+ expect( list_contains(interface, id) ).to be_falsy
379
+ end
380
+
381
+ it 'shouldn\'t have a problem with non-integer keys' do
382
+ fill_product_data(prod_interface)
383
+ expect( list_contains(prod_interface, "foo") ).to be_truthy
384
+ prod_interface.delete("foo")
385
+ expect( list_contains(prod_interface, "foo") ).to be_falsy
386
+ end
387
+
388
+ end
389
+ ##
390
+
391
+
392
+ describe '#execute' do
393
+
394
+ let(:sql) { 'delete from customer where qty < 2.0;' }
395
+
396
+ it 'requires an SQL string' do
397
+ expect{ interface.execute }.to raise_exception ArgumentError
398
+ expect{ interface.execute(nil) }.to raise_exception ArgumentError
399
+ expect{ interface.execute(14) }.to raise_exception ArgumentError
400
+ end
401
+
402
+ it 'raises some sort of Pod4 error if it runs into problems' do
403
+ expect{ interface.execute('delete from not_a_table') }.
404
+ to raise_exception Pod4Error
405
+
406
+ end
407
+
408
+ it 'executes the string' do
409
+ expect{ interface.execute(sql) }.not_to raise_exception
410
+ expect( interface.list.size ).to eq(data.size - 1)
411
+ expect( interface.list.map{|r| r[:name] } ).not_to include 'Barney'
412
+ end
413
+
414
+ end
415
+ ##
416
+
417
+
418
+ describe '#select' do
419
+
420
+ it 'requires an SQL string' do
421
+ expect{ interface.select }.to raise_exception ArgumentError
422
+ expect{ interface.select(nil) }.to raise_exception ArgumentError
423
+ expect{ interface.select(14) }.to raise_exception ArgumentError
424
+ end
425
+
426
+ it 'raises some sort of Pod4 error if it runs into problems' do
427
+ expect{ interface.select('select * from not_a_table') }.
428
+ to raise_exception Pod4Error
429
+
430
+ end
431
+
432
+ it 'returns the result of the sql' do
433
+ sql1 = 'select name from customer where qty < 2.0;'
434
+ sql2 = 'select name from customer where qty < 0.0;'
435
+
436
+ expect{ interface.select(sql1) }.not_to raise_exception
437
+ expect( interface.select(sql1) ).to eq( [{name: 'Barney'}] )
438
+ expect( interface.select(sql2) ).to eq( [] )
439
+ end
440
+
441
+ it 'works if you pass a non-select' do
442
+ # By which I mean: still executes the SQL; returns []
443
+ pending "JDBC Driver freaks out at this, raises an exception"
444
+ sql = 'delete from customer where qty < 2.0;'
445
+ ret = interface.select(sql)
446
+
447
+ expect( interface.list.size ).to eq(data.size - 1)
448
+ expect( interface.list.map{|r| r[:name] } ).not_to include 'Barney'
449
+ expect( ret ).to eq( [] )
450
+ end
451
+
452
+ end
453
+ ##
454
+
455
+
456
+ describe "#executep" do
457
+ # For the time being lets assume that Sequel does its job and the three modes we are calling
458
+ # actually work
459
+
460
+ let(:sql) { 'delete from customer where qty < ?;' }
461
+
462
+ it 'requires an SQL string and a mode' do
463
+ expect{ interface.executep }.to raise_exception ArgumentError
464
+ expect{ interface.executep(nil) }.to raise_exception ArgumentError
465
+ expect{ interface.executep(14, :update) }.to raise_exception ArgumentError
466
+ expect{ interface.executep(14, :update, 2) }.to raise_exception ArgumentError
467
+ end
468
+
469
+ it 'requires the mode to be valid' do
470
+ expect{ interface.executep(sql, :foo, 2) }.to raise_exception ArgumentError
471
+ end
472
+
473
+ it 'raises some sort of Pod4 error if it runs into problems' do
474
+ expect{ interface.executep('delete from not_a_table where thingy = ?', :delete, 14) }.
475
+ to raise_exception Pod4Error
476
+
477
+ end
478
+
479
+ it 'executes the string' do
480
+ expect{ interface.executep(sql, :delete, 2.0) }.not_to raise_exception
481
+ expect( interface.list.size ).to eq(data.size - 1)
482
+ expect( interface.list.map{|r| r[:name] } ).not_to include 'Barney'
483
+ end
484
+
485
+ end
486
+
487
+
488
+ describe "#selectp" do
489
+
490
+ it 'requires an SQL string' do
491
+ expect{ interface.selectp }.to raise_exception ArgumentError
492
+ expect{ interface.selectp(nil) }.to raise_exception ArgumentError
493
+ expect{ interface.selectp(14) }.to raise_exception ArgumentError
494
+ end
495
+
496
+ it 'raises some sort of Pod4 error if it runs into problems' do
497
+ expect{ interface.selectp('select * from not_a_table where thingy = ?', 14) }.
498
+ to raise_exception Pod4Error
499
+
500
+ end
501
+
502
+ it 'returns the result of the sql' do
503
+ sql = 'select name from customer where qty < ?;'
504
+
505
+ expect{ interface.selectp(sql, 2.0) }.not_to raise_exception
506
+ expect( interface.selectp(sql, 2.0) ).to eq( [{name: 'Barney'}] )
507
+ expect( interface.selectp(sql, 0.0) ).to eq( [] )
508
+ end
509
+
510
+ it 'works if you pass a non-select' do
511
+ # By which I mean: still executes the SQL; returns []
512
+ pending "JDBC Driver freaks out at this, raises an exception"
513
+ sql = 'delete from customer where qty < ?;'
514
+ ret = interface.selectp(sql, 2.0)
515
+
516
+ expect( interface.list.size ).to eq(data.size - 1)
517
+ expect( interface.list.map{|r| r[:name] } ).not_to include 'Barney'
518
+ expect( ret ).to eq( [] )
519
+ end
520
+
521
+ end
522
+
523
+
524
+ end
525
+