pod4 0.7.2 → 0.8.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,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
+