pod4 0.10.5 → 0.10.6

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.
@@ -1,611 +0,0 @@
1
- require 'pod4/pg_interface'
2
- require 'pg'
3
-
4
- require_relative '../common/shared_examples_for_interface'
5
- require_relative '../fixtures/database'
6
-
7
-
8
- describe "PgInterface" do
9
-
10
- let(:pg_interface_class) do
11
- Class.new PgInterface do
12
- set_table :customer
13
- set_id_fld :id
14
-
15
- def stop; close; end # We open a lot of connections, unusually
16
- end
17
- end
18
-
19
- let(:schema_interface_class) do
20
- Class.new PgInterface do
21
- set_schema :public
22
- set_table :customer
23
- set_id_fld :id
24
- end
25
- end
26
-
27
- let(:prod_interface_class) do
28
- Class.new PgInterface do
29
- set_table :product
30
- set_id_fld :code
31
- end
32
- end
33
-
34
- let(:bad_interface_class1) do
35
- Class.new PgInterface do
36
- set_table :customer
37
- end
38
- end
39
-
40
- let(:bad_interface_class2) do
41
- Class.new PgInterface do
42
- set_id_fld :id
43
- end
44
- end
45
-
46
-
47
- def db_setup(connect)
48
- client = PG.connect(connect)
49
-
50
- client.exec(%Q|
51
- drop table if exists customer;
52
- drop table if exists product;
53
-
54
- create table customer (
55
- id serial primary key,
56
- medical_model_class.name text,
57
- level real null,
58
- day date null,
59
- timestamp timestamp null,
60
- price money null,
61
- flag boolean null,
62
- qty numeric null );
63
-
64
- create table product (
65
- code text,
66
- name text );| )
67
-
68
- ensure
69
- client.finish if client
70
- end
71
-
72
-
73
- def fill_data(ifce)
74
- @data.each{|r| ifce.create(r) }
75
- end
76
-
77
-
78
- def fill_product_data(ifce)
79
- ifce.create( {code: "foo", name: "bar"} )
80
- end
81
-
82
-
83
- before(:all) do
84
- @connect_hash = DB[:pg]
85
- db_setup(@connect_hash)
86
-
87
- @data = []
88
- @data << { name: 'Barney',
89
- level: 1.23,
90
- day: Date.parse("2016-01-01"),
91
- timestamp: Time.parse('2015-01-01 12:11'),
92
- price: BigDecimal.new("1.24"),
93
- flag: true,
94
- qty: BigDecimal.new("1.25") }
95
-
96
- @data << { name: 'Fred',
97
- level: 2.34,
98
- day: Date.parse("2016-02-02"),
99
- timestamp: Time.parse('2015-01-02 12:22'),
100
- price: BigDecimal.new("2.35"),
101
- flag: false,
102
- qty: BigDecimal.new("2.36") }
103
-
104
- @data << { name: 'Betty',
105
- level: 3.45,
106
- day: Date.parse("2016-03-03"),
107
- timestamp: Time.parse('2015-01-03 12:33'),
108
- price: BigDecimal.new("3.46"),
109
- flag: nil,
110
- qty: BigDecimal.new("3.47") }
111
-
112
- end
113
-
114
-
115
- before do
116
- interface.execute(%Q|
117
- truncate table customer restart identity;
118
- truncate table product;|)
119
-
120
- end
121
-
122
-
123
- after do
124
- # We open a lot of connections, unusually
125
- interface.stop if interface
126
- end
127
-
128
-
129
- let(:interface) do
130
- pg_interface_class.new(@connect_hash)
131
- end
132
-
133
- let(:prod_interface) do
134
- prod_interface_class.new(@connect_hash)
135
- end
136
-
137
- #####
138
-
139
-
140
- it_behaves_like 'an interface' do
141
-
142
- let(:interface) do
143
- pg_interface_class.new(@connect_hash)
144
- end
145
-
146
- let(:record) { {name: 'Barney'} }
147
-
148
- end
149
- ##
150
-
151
-
152
- describe 'PgInterface.set_schema' do
153
- it 'takes one argument' do
154
- expect( pg_interface_class ).to respond_to(:set_schema).with(1).argument
155
- end
156
- end
157
- ##
158
-
159
-
160
- describe 'PgInterface.schema' do
161
- it 'returns the schema' do
162
- expect( schema_interface_class.schema ).to eq :public
163
- end
164
-
165
- it 'is optional' do
166
- expect{ pg_interface_class.schema }.not_to raise_exception
167
- expect( pg_interface_class.schema ).to eq nil
168
- end
169
- end
170
- ##
171
-
172
-
173
- describe 'PgInterface.set_table' do
174
- it 'takes one argument' do
175
- expect( pg_interface_class ).to respond_to(:set_table).with(1).argument
176
- end
177
- end
178
- ##
179
-
180
-
181
- describe 'PgInterface.table' do
182
- it 'returns the table' do
183
- expect( pg_interface_class.table ).to eq :customer
184
- end
185
- end
186
- ##
187
-
188
-
189
- describe 'PgInterface.set_id_fld' do
190
- it 'takes one argument' do
191
- expect( pg_interface_class ).to respond_to(:set_id_fld).with(1).argument
192
- end
193
- end
194
- ##
195
-
196
-
197
- describe 'PgInterface.id_fld' do
198
- it 'returns the ID field name' do
199
- expect( pg_interface_class.id_fld ).to eq :id
200
- end
201
- end
202
- ##
203
-
204
-
205
- describe '#new' do
206
-
207
- it 'requires a TinyTds connection string' do
208
- expect{ pg_interface_class.new }.to raise_exception ArgumentError
209
- expect{ pg_interface_class.new(nil) }.to raise_exception ArgumentError
210
- expect{ pg_interface_class.new('foo') }.to raise_exception ArgumentError
211
-
212
- expect{ pg_interface_class.new(@connect_hash) }.not_to raise_exception
213
- end
214
-
215
- end
216
- ##
217
-
218
-
219
- describe '#quoted_table' do
220
-
221
- it 'returns just the table when the schema is not set' do
222
- expect( interface.quoted_table ).to eq( %Q|"customer"| )
223
- end
224
-
225
- it 'returns the schema plus table when the schema is set' do
226
- ifce = schema_interface_class.new(@connect_hash)
227
- expect( ifce.quoted_table ).to eq( %|"public"."customer"| )
228
- end
229
-
230
- end
231
- ##
232
-
233
-
234
- describe '#create' do
235
-
236
- let(:hash) { {name: 'Bam-Bam', price: 4.44} }
237
- let(:ot) { Octothorpe.new(name: 'Wilma', price: 5.55) }
238
-
239
- it 'raises a Pod4::DatabaseError if anything goes wrong' do
240
- expect{ interface.create(one: 'two') }.to raise_exception DatabaseError
241
- end
242
-
243
- it 'creates the record when given a hash' do
244
- # kinda impossible to seperate these two tests
245
- id = interface.create(hash)
246
-
247
- expect{ interface.read(id) }.not_to raise_exception
248
- expect( interface.read(id).to_h ).to include hash
249
- end
250
-
251
- it 'creates the record when given an Octothorpe' do
252
- id = interface.create(ot)
253
-
254
- expect{ interface.read(id) }.not_to raise_exception
255
- expect( interface.read(id).to_h ).to include ot.to_h
256
- end
257
-
258
- it 'shouldn\'t have a problem with record values of nil' do
259
- record = {name: 'Ranger', price: nil}
260
- expect{ interface.create(record) }.not_to raise_exception
261
- id = interface.create(record)
262
- expect( interface.read(id).to_h ).to include(record)
263
- end
264
-
265
- it 'shouldn\'t have a problem with strings containing special characters' do
266
- record = {name: %Q|T'Challa""|, price: nil}
267
- expect{ interface.create(record) }.not_to raise_exception
268
- id = interface.create(record)
269
- expect( interface.read(id).to_h ).to include(record)
270
- end
271
-
272
- it 'shouldn\'t have a problem with non-integer keys' do
273
- hash = {code: "foo", name: "bar"}
274
- id = prod_interface.create( Octothorpe.new(hash) )
275
-
276
- expect( id ).to eq "foo"
277
- expect{ prod_interface.read("foo") }.not_to raise_exception
278
- expect( prod_interface.read("foo").to_h ).to include hash
279
- end
280
-
281
- end
282
- ##
283
-
284
-
285
- describe '#read' do
286
- before { fill_data(interface) }
287
-
288
- it 'returns the record for the id as an Octothorpe' do
289
- rec = interface.read(2)
290
- expect( rec ).to be_a_kind_of Octothorpe
291
- expect( rec.>>.name ).to eq 'Fred'
292
- end
293
-
294
- it 'raises a Pod4::CantContinue if the ID is bad' do
295
- expect{ interface.read(:foo) }.to raise_exception CantContinue
296
- end
297
-
298
- it 'returns an empty Octothorpe if no record matches the ID' do
299
- expect{ interface.read(99) }.not_to raise_exception
300
- expect( interface.read(99) ).to be_a_kind_of Octothorpe
301
- expect( interface.read(99) ).to be_empty
302
- end
303
-
304
- it 'returns real fields as Float' do
305
- level = interface.read(1).>>.level
306
-
307
- expect( level ).to be_a_kind_of Float
308
- expect( level ).to be_within(0.001).of( @data.first[:level] )
309
- end
310
-
311
- it 'returns date fields as Date' do
312
- date = interface.read(1).>>.day
313
-
314
- expect( date ).to be_a_kind_of Date
315
- expect( date ).to eq @data.first[:day]
316
- end
317
-
318
- it 'returns datetime fields as Time' do
319
- timestamp = interface.read(1).>>.timestamp
320
-
321
- expect( timestamp ).to be_a_kind_of Time
322
- expect( timestamp ).to eq @data.first[:timestamp]
323
- end
324
-
325
- it 'returns numeric fields as BigDecimal' do
326
- qty = interface.read(1).>>.qty
327
-
328
- expect( qty ).to be_a_kind_of BigDecimal
329
- expect( qty ).to eq @data.first[:qty]
330
- end
331
-
332
- it 'returns money fields as BigDecimal' do
333
- price = interface.read(1).>>.price
334
-
335
- expect( price ).to be_a_kind_of BigDecimal
336
- expect( price ).to eq @data.first[:price]
337
- end
338
-
339
- it 'returns boolean fields as boolean' do
340
- [1,2,3].each do |i|
341
- flag = interface.read(i).>>.flag
342
- expect( [true, false, nil].include? flag ).to be true
343
- expect( flag ).to be @data[i - 1][:flag]
344
- end
345
- end
346
-
347
- it 'shouldn\'t have a problem with non-integer keys' do
348
- # this is a 100% overlap with the create test above...
349
- fill_product_data(prod_interface)
350
-
351
- expect{ prod_interface.read("foo") }.not_to raise_exception
352
- expect( prod_interface.read("foo").to_h ).to include(code: "foo", name: "bar")
353
- end
354
-
355
- end
356
- ##
357
-
358
-
359
- describe '#list' do
360
- before { fill_data(interface) }
361
-
362
- it 'has an optional selection parameter, a hash' do
363
- # Actually it does not have to be a hash, but FTTB we only support that.
364
- expect{ interface.list(name: 'Barney') }.not_to raise_exception
365
- end
366
-
367
- it 'returns an array of Octothorpes that match the records' do
368
- # convert each OT to a hash and remove the ID key
369
- arr = interface.list.map {|ot| x = ot.to_h; x.delete(:id); x }
370
-
371
- expect( arr ).to match_array @data
372
- end
373
-
374
- it 'returns a subset of records based on the selection parameter' do
375
- expect( interface.list(name: 'Fred').size ).to eq 1
376
-
377
- expect( interface.list(name: 'Betty').first.to_h ).
378
- to include(name: 'Betty')
379
-
380
- end
381
-
382
- it 'returns an empty Array if nothing matches' do
383
- expect( interface.list(name: 'Yogi') ).to eq([])
384
- end
385
-
386
- it 'raises ArgumentError if the selection criteria is nonsensical' do
387
- expect{ interface.list('foo') }.to raise_exception ArgumentError
388
- end
389
-
390
- end
391
- ##
392
-
393
-
394
- describe '#update' do
395
- before { fill_data(interface) }
396
-
397
- let(:id) { interface.list.first[:id] }
398
-
399
- def float_price(row)
400
- row[:price] = row[:price].to_f
401
- row
402
- end
403
-
404
- it 'updates the record at ID with record parameter' do
405
- record = {name: 'Booboo', price: 99.99}
406
- interface.update(id, record)
407
-
408
- expect( float_price( interface.read(id).to_h ) ).to include(record)
409
- end
410
-
411
- it 'raises a CantContinue if anything weird happens with the ID' do
412
- expect{ interface.update(99, name: 'Booboo') }.
413
- to raise_exception CantContinue
414
-
415
- end
416
-
417
- it 'raises a DatabaseError if anything weird happens with the record' do
418
- expect{ interface.update(id, smarts: 'more') }.
419
- to raise_exception DatabaseError
420
-
421
- end
422
-
423
- it 'shouldn\'t have a problem with record values of nil' do
424
- record = {name: 'Ranger', price: nil}
425
- expect{ interface.update(id, record) }.not_to raise_exception
426
- expect( interface.read(id).to_h ).to include(record)
427
- end
428
-
429
- it 'shouldn\'t have a problem with strings containing special characters' do
430
- record = {name: %Q|T'Challa""|, price: nil}
431
- expect{ interface.update(id, record) }.not_to raise_exception
432
- expect( interface.read(id).to_h ).to include(record)
433
- end
434
-
435
- it 'shouldn\'t have a problem with non-integer keys' do
436
- fill_product_data(prod_interface)
437
- expect{ prod_interface.update("foo", name: "baz") }.not_to raise_error
438
- expect( prod_interface.read("foo").to_h[:name] ).to eq "baz"
439
- end
440
-
441
- end
442
- ##
443
-
444
-
445
- describe '#delete' do
446
-
447
- def list_contains(ifce, id)
448
- ifce.list.find {|x| x[ifce.id_fld] == id }
449
- end
450
-
451
- let(:id) { interface.list.first[:id] }
452
-
453
- before { fill_data(interface) }
454
-
455
- it 'raises CantContinue if anything hinky happens with the id' do
456
- expect{ interface.delete(:foo) }.to raise_exception CantContinue
457
- expect{ interface.delete(99) }.to raise_exception CantContinue
458
- end
459
-
460
- it 'makes the record at ID go away' do
461
- expect( list_contains(interface, id) ).to be_truthy
462
- interface.delete(id)
463
- expect( list_contains(interface, id) ).to be_falsy
464
- end
465
-
466
- it 'shouldn\'t have a problem with non-integer keys' do
467
- fill_product_data(prod_interface)
468
- expect( list_contains(prod_interface, "foo") ).to be_truthy
469
- prod_interface.delete("foo")
470
- expect( list_contains(prod_interface, "foo") ).to be_falsy
471
- end
472
-
473
- end
474
- ##
475
-
476
-
477
- describe '#execute' do
478
-
479
- let(:sql) { 'delete from customer where cast(price as numeric) < 2.0;' }
480
-
481
- before { fill_data(interface) }
482
-
483
- it 'requires an SQL string' do
484
- expect{ interface.execute }.to raise_exception ArgumentError
485
- expect{ interface.execute(nil) }.to raise_exception ArgumentError
486
- expect{ interface.execute(14) }.to raise_exception ArgumentError
487
- end
488
-
489
- it 'raises some sort of Pod4 error if it runs into problems' do
490
- expect{ interface.execute('delete from not_a_table') }.
491
- to raise_exception Pod4Error
492
-
493
- end
494
-
495
- it 'executes the string' do
496
- expect{ interface.execute(sql) }.not_to raise_exception
497
- expect( interface.list.size ).to eq(@data.size - 1)
498
- expect( interface.list.map{|r| r[:name] } ).not_to include 'Barney'
499
- end
500
-
501
- end
502
- ##
503
-
504
-
505
- describe '#executep' do
506
-
507
- let(:sql) { 'delete from customer where cast(price as numeric) < %s and name = %s;' }
508
-
509
- before { fill_data(interface) }
510
-
511
- it 'requires an SQL string' do
512
- expect{ interface.executep }.to raise_exception ArgumentError
513
- expect{ interface.executep(nil) }.to raise_exception ArgumentError
514
- expect{ interface.executep(14) }.to raise_exception ArgumentError
515
- end
516
-
517
- it 'raises some sort of Pod4 error if it runs into problems' do
518
- expect{ interface.executep('delete from not_a_table where foo = %s', 12) }.
519
- to raise_exception Pod4Error
520
-
521
- end
522
-
523
- it 'executes the string with the given parameters' do
524
- expect{ interface.executep(sql, 12.0, 'Barney') }.not_to raise_exception
525
- expect( interface.list.size ).to eq(@data.size - 1)
526
- expect( interface.list.map{|r| r[:name] } ).not_to include 'Barney'
527
- end
528
-
529
- end
530
- ##
531
-
532
-
533
- describe '#select' do
534
-
535
- before { fill_data(interface) }
536
-
537
- it 'requires an SQL string' do
538
- expect{ interface.select }.to raise_exception ArgumentError
539
- expect{ interface.select(nil) }.to raise_exception ArgumentError
540
- expect{ interface.select(14) }.to raise_exception ArgumentError
541
- end
542
-
543
- it 'raises some sort of Pod4 error if it runs into problems' do
544
- expect{ interface.select('select * from not_a_table') }.
545
- to raise_exception Pod4Error
546
-
547
- end
548
-
549
- it 'returns the result of the sql' do
550
- sql1 = 'select name from customer where cast(price as numeric) < 2.0;'
551
- sql2 = 'select name from customer where cast(price as numeric) < 0.0;'
552
-
553
- expect{ interface.select(sql1) }.not_to raise_exception
554
- expect( interface.select(sql1) ).to eq( [{name: 'Barney'}] )
555
- expect( interface.select(sql2) ).to eq( [] )
556
- end
557
-
558
- it 'works if you pass a non-select' do
559
- # By which I mean: still executes the SQL; returns []
560
- sql = 'delete from customer where cast(price as numeric) < 2.0;'
561
- ret = interface.select(sql)
562
-
563
- expect( interface.list.size ).to eq(@data.size - 1)
564
- expect( interface.list.map{|r| r[:name] } ).not_to include 'Barney'
565
- expect( ret ).to eq( [] )
566
- end
567
-
568
- end
569
- ##
570
-
571
-
572
- describe '#selectp' do
573
-
574
- before { fill_data(interface) }
575
-
576
- it 'requires an SQL string' do
577
- expect{ interface.selectp }.to raise_exception ArgumentError
578
- expect{ interface.selectp(nil) }.to raise_exception ArgumentError
579
- expect{ interface.selectp(14) }.to raise_exception ArgumentError
580
- end
581
-
582
- it 'raises some sort of Pod4 error if it runs into problems' do
583
- expect{ interface.selectp('select * from not_a_table where thingy = %s', 12) }.
584
- to raise_exception Pod4Error
585
-
586
- end
587
-
588
- it 'returns the result of the sql' do
589
- sql = 'select name from customer where cast(price as numeric) < %s;'
590
-
591
- expect{ interface.selectp(sql, 2.0) }.not_to raise_exception
592
- expect( interface.selectp(sql, 2.0) ).to eq( [{name: 'Barney'}] )
593
- expect( interface.selectp(sql, 0.0) ).to eq( [] )
594
- end
595
-
596
- it 'works if you pass a non-select' do
597
- sql = 'delete from customer where cast(price as numeric) < %s;'
598
- ret = interface.selectp(sql, 2.0)
599
-
600
- expect( interface.list.size ).to eq(@data.size - 1)
601
- expect( interface.list.map{|r| r[:name] } ).not_to include 'Barney'
602
- expect( ret ).to eq( [] )
603
- end
604
-
605
-
606
- end
607
- ##
608
-
609
-
610
- end
611
-