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