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