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