UNPKG

31.5 kBJavaScriptView Raw
1var oreo = require('..')
2var ok = require('assert').ok
3var fs = require('fs')
4var bluebird = require('bluebird')
5var async = require('../lib/async')
6
7var models = {
8 books: require('./Book')
9}
10
11var db
12var platforms = [
13 {
14 driver: 'pg',
15 user: 'postgres',
16 pass: '',
17 hosts: ['localhost:5432', 'localhost:5433', 'localhost:5430'],
18 name: 'oreo_test',
19 debug: false,
20 silent: true,
21 Promise: global.Promise || bluebird,
22 models: models
23 },
24 {
25 driver: 'pg',
26 user: 'postgres',
27 pass: '',
28 hosts: ['localhost:5432', 'localhost:5433', 'localhost:5430'],
29 name: 'oreo_test',
30 debug: false,
31 silent: true,
32 memoize: 150,
33 Promise: global.Promise || bluebird,
34 models: models
35 },
36 {
37 driver: 'mysql',
38 user: 'root',
39 pass: '',
40 hosts: ['localhost'],
41 name: 'oreo_test',
42 debug: false,
43 silent: true,
44 Promise: global.Promise || bluebird,
45 models: models
46 }
47]
48
49var showError = function (err) {
50 console.log(err.stack)
51}
52
53var no = function (err) {
54 ok(!err, err + '')
55}
56
57var mockRedis = function() {
58 var cache = {}
59 return {
60 get: function(key, cb) {
61 var val = cache[key]
62 if (val) {
63 val = JSON.parse(val)
64 val.fromCache = true
65 val = JSON.stringify(val)
66 }
67 cb(null, val)
68 },
69 set: function(key, val, cb) {
70 cache[key] = val
71 cb(null)
72 }
73 }
74}
75
76describe('oreo', function() {
77
78 it('should fail with unknown driver', function(done) {
79 db = oreo({
80 driver: 'mssql'
81 }, function(err) {
82 ok(!!err, 'did not fail')
83 done()
84 })
85 })
86
87 platforms.forEach(function(config) {
88
89 it('should connect and discover - cb', function(done) {
90 console.log('\n', config.driver)
91 db = oreo(config, function(err) {
92 no(err)
93 done()
94 })
95 })
96
97 it('should create tables', function(done) {
98 var sql = fs.readFileSync(__dirname + '/schema/' + config.driver + '.sql', 'utf8')
99 db.executeWrite(sql, function(err, rs) {
100 no(err)
101 done()
102 })
103 })
104
105 it('should rediscover and end - promise', function(done) {
106 db.discover().then(function(db) {
107 ok(!!db.authors, 'authors not discovered')
108 config.schema = JSON.parse(JSON.stringify(db))
109 db.authors.find().then(function () {
110 var isDone = false
111 db.end(function () {
112 if (isDone) return
113 isDone = true
114 done()
115 })
116 })
117 }).catch(showError)
118 })
119
120 it('should connect and discover - schema and onReady', function(done) {
121 var count = 0
122 var isDone = function () {
123 count++
124 if (count === 2) done()
125 }
126 db = oreo(config, function (err) {
127 no(err)
128 }).onReady(function() {
129 isDone()
130 })
131 db.onReady(function () {
132 ok(!!db.authors, 'authors not discovered')
133 isDone()
134 })
135 })
136
137 it('should rediscover - cb', function(done) {
138 db.discover(function(err) {
139 no(err)
140 ok(!!db.authors, 'authors not discovered')
141 done()
142 })
143 })
144
145 it('should insert - cb', function(done) {
146 db.authors.insert({
147 name: 'Jack Kerouac'
148 }, function(err, author) {
149 no(err)
150 ok(author.id === 1, 'did not insert author - should insert')
151 db.books.insert({
152 title: 'On the Road',
153 author_id: author.id
154 }, function(err, book) {
155 no(err)
156 ok(book.id === 1, 'did not insert book')
157 db.ratings.insert({
158 author_id: author.id,
159 book_id: book.id,
160 stars: 10
161 }, function(err, rating) {
162 no(err)
163 ok(rating.stars === 10, 'did not insert rating')
164 done()
165 })
166 })
167 })
168 })
169
170 it('should insert - promise', function(done) {
171 db.authors.insert({
172 name: 'Tom Wolfe'
173 }).then(function(author) {
174 ok(author.id === 2, 'did not insert author - should insert')
175 db.books.insert({
176 title: 'The Electric Kool-Aid Acid Test',
177 author_id: author.id
178 }).then(function(book) {
179 ok(book.id === 2, 'did not insert book')
180 db.ratings.insert({
181 author_id: author.id,
182 book_id: book.id,
183 stars: 9
184 }).then(function(rating) {
185 ok(rating.stars === 9, 'did not insert rating')
186 done()
187 })
188 })
189 }).catch(showError)
190 })
191
192 it('should save field with same name as 1-to-m fk - cb', function(done) {
193 db.authors.save({
194 id: 2,
195 books: [2]
196 }, function(err, author) {
197 no(err)
198 db.authors.get(2, function(err, author) {
199 ok(author.books.toString() === '2', 'did not save books value')
200 author.hydrate('books', function (err) {
201 ok(!!err, 'books should not be hydratable')
202 done()
203 })
204 })
205 })
206 })
207
208 it('should not save 1-to-m row with same name as field - cb', function(done) {
209 db.authors.save({
210 id: 2,
211 books: [
212 {id: 2, author_id: 2, title: 'Working Title'}
213 ]
214 }, function(err, author) {
215 ok(!!err, 'books should not save')
216 done()
217 })
218 })
219
220 it('should save field with same name as 1-to-1 fk - cb', function(done) {
221 db.authors.save({
222 id: 2,
223 Country: {
224 Code: 'US',
225 name: 'United States'
226 }
227 }, function(err, author) {
228 no(err)
229 ok(author.Country === 'US', 'author.Country')
230 db.authors.get(2, function(err, author) {
231 no(err)
232 ok(author.Country === 'US', 'did not save author.Country')
233 author.hydrate('Country', function (err) {
234 no(err)
235 ok(author.Country.name === 'United States', 'author.Country.name')
236 author.Country.update({
237 name: 'USA'
238 }, function (err, Country) {
239 no(err)
240 ok(Country.name === 'USA', 'Country.name not USA')
241 author.update({
242 Country: {
243 Code: 'CA',
244 name: 'Canada'
245 }
246 }, function (err, author) {
247 no(err)
248 ok(author.Country === 'CA', 'author.Country not CA')
249 author.update({
250 Country: 'MX'
251 }, function (err, author) {
252 ok(!!err, 'should violate fk constraint')
253 db.authors.get(2, function (err, author) {
254 no(err)
255 ok(author.Country === 'CA', 'author.Country should still be CA')
256 done()
257 })
258 })
259 })
260 })
261 })
262 })
263 })
264 })
265
266 it('should static save - cb', function(done) {
267 var data = {
268 id: 1408,
269 name: 'Stephen King'
270 }
271 db.authors.save(data, function(err, author) {
272 no(err)
273 ok(author.id === 1408, 'did not insert author')
274 db.authors.save(data, function(err, author) {
275 no(err)
276 ok(author.id === 1408, 'did not update author')
277 done()
278 })
279 })
280 })
281
282 it('should static save - promise', function(done) {
283 var data = {
284 id: 1984,
285 name: 'George Orwell'
286 }
287 db.authors.save(data).then(function(author) {
288 ok(author.id === 1984, 'did not insert author')
289 db.authors.save(data).then(function(author) {
290 ok(author.id === 1984, 'did not update author')
291 done()
292 })
293 }).catch(showError)
294 })
295
296 it('should get - cb', function(done) {
297 db.authors.get(1, function(err, author) {
298 no(err)
299 ok(author.id === 1, 'did not get author')
300 done()
301 })
302 })
303
304 it('should get - promise', function(done) {
305 db.authors.get(1).then(function(author) {
306 ok(author.id === 1, 'did not get author')
307 done()
308 }).catch(showError)
309 })
310
311 it('should mget - cb', function(done) {
312 db.authors.mget([1, 1984], function(err, authors) {
313 no(err)
314 ok(authors[0].id === 1, 'did not get authors')
315 ok(authors[1].id === 1984, 'did not get second author')
316 done()
317 })
318 })
319
320 it('should mget with null value - promise', function(done) {
321 db.authors.mget([1984, 999999, 1]).then(function(authors) {
322 ok(authors[0].id === 1984, 'did not get first author')
323 ok(authors[1] === null, 'second author is not null')
324 ok(authors[2].id === 1, 'did not get third author')
325 done()
326 }).catch(showError)
327 })
328
329 it('should get (composite primary key object)', function(done) {
330 db.ratings.get({
331 author_id: 1,
332 book_id: 1
333 }, function(err, rating) {
334 no(err)
335 ok(rating.stars === 10, 'did not get rating')
336 done()
337 })
338 })
339
340 it('should get (composite primary key array)', function(done) {
341 db.ratings.get([1, 1], function(err, rating) {
342 no(err)
343 ok(rating.stars === 10, 'did not get rating')
344 done()
345 })
346 })
347
348 it('should find all - cb', function(done) {
349 db.authors.find(function(err, authors) {
350 no(err)
351 ok(authors.length === 4, 'authors.length')
352 done()
353 })
354 })
355
356 it('should find all - promise', function(done) {
357 db.authors.find().then(function(authors) {
358 ok(authors.length === 4, 'authors.length')
359 done()
360 }).catch(showError)
361 })
362
363 it('should find (where string)', function(done) {
364 db.authors.find({
365 where: "name = 'Jack Kerouac'"
366 }, function(err, authors) {
367 no(err)
368 ok(authors[0].id === 1, 'did not find author')
369 done()
370 })
371 })
372
373 it('should find (case-sensitive)', function(done) {
374 db.authors.find({
375 where: {
376 Country: 'CA'
377 }
378 }, function(err, authors) {
379 no(err)
380 ok(authors[0].id === 2, 'did not find author')
381 done()
382 })
383 })
384
385 it('should find (where array)', function(done) {
386 db.authors.find({
387 where: ["name = 'Jack Kerouac'"]
388 }, function(err, authors) {
389 no(err)
390 ok(authors[0].id === 1, 'did not find author')
391 done()
392 })
393 })
394
395 it('should find (where parameterized array)', function(done) {
396 var opts = {
397 where: ['name = :name'],
398 params: {
399 name: 'Jack Kerouac'
400 }
401 }
402 db.authors.find(opts, function(err, authors) {
403 no(err)
404 ok(authors[0].id === 1, 'did not find author')
405 ok(authors[0].name === opts.params.name, 'did not find author name')
406 done()
407 })
408 })
409
410 it('should find (where object)', function(done) {
411 db.authors.find({
412 where: {
413 name: 'Jack Kerouac'
414 }
415 }, function(err, authors) {
416 no(err)
417 ok(authors[0].id === 1, 'did not find author')
418 done()
419 })
420 })
421
422 it('should find (composite primary key)', function(done) {
423 db.ratings.find({
424 where: {
425 stars: 10
426 }
427 }, function(err, ratings) {
428 no(err)
429 ok(ratings[0].author_id === 1, 'did not find rating')
430 done()
431 })
432 })
433
434 it('should order by', function(done) {
435 db.authors.find({
436 order: 'id desc'
437 }).then(function(authors) {
438 ok(authors[0].id === 1984, 'order first')
439 ok(authors[1].id === 1408, 'order second')
440 done()
441 }).catch(showError)
442 })
443
444 it('should limit', function(done) {
445 db.authors.find({
446 limit: 2
447 }).then(function(authors) {
448 ok(authors.length === 2, 'limit')
449 done()
450 }).catch(showError)
451 })
452
453 it('should offset', function(done) {
454 db.authors.find({
455 order: 'id desc',
456 limit: 1,
457 offset: 1
458 }).then(function(authors) {
459 ok(authors[0].id === 1408, 'offset')
460 done()
461 }).catch(showError)
462 })
463
464 it('should findOne - cb', function(done) {
465 db.authors.findOne({
466 where: "name = 'Jack Kerouac'"
467 }, function(err, author) {
468 no(err)
469 ok(author.id === 1, 'did not findOne author')
470 done()
471 })
472 })
473
474 it('should findOne - promise', function(done) {
475 db.authors.findOne({
476 where: "name = 'Jack Kerouac'"
477 }).then(function(author) {
478 ok(author.id === 1, 'did not findOne author')
479 done()
480 }).catch(showError)
481 })
482
483 it('should update - cb', function(done) {
484 db.authors.get(1, function(err, author) {
485 no(err)
486 var new_name = 'Jim Kerouac'
487 author.update({
488 name: new_name
489 }, function(err, author) {
490 no(err)
491 ok(author.id === 1, 'did not get correct author')
492 ok(author.name === new_name, 'did not update author')
493 done()
494 })
495 })
496 })
497
498 it('should update - promise', function(done) {
499 db.authors.get(1).then(function(author) {
500 var new_name = 'Jeff Kerouac'
501 author.update({
502 name: new_name
503 }).then(function(author) {
504 ok(author.id === 1, 'did not get correct author')
505 ok(author.name === new_name, 'did not update author')
506 done()
507 })
508 }).catch(showError)
509 })
510
511 it('should hydrate - cb', function(done) {
512 db.books.get(1, function(err, book) {
513 no(err)
514 book.hydrate('author', function(err) {
515 ok(book.author.id === book.author_id, 'did not hydrate author')
516 ok(book.id === 1, 'weird')
517 done()
518 })
519 })
520 })
521
522 it('should hydrate - promise', function(done) {
523 db.books.get(1).then(function(book) {
524 book.hydrate('author').then(function() {
525 ok(book.author.id === book.author_id, 'did not hydrate author')
526 ok(book.id === 1, 'weird')
527 done()
528 })
529 }).catch(showError)
530 })
531
532 it('should hydrate composite foreign key', function(done) {
533 db.samples.insert({
534 author_id: 1,
535 book_id: 1,
536 description: 'this is an example'
537 }, function(err, data) {
538 no(err)
539 db.samples.get(data.id, function(err, sample) {
540 sample.hydrate('rating', function(err) {
541 ok(sample.rating.stars === 10, 'did not hydrate rating')
542 done()
543 })
544 })
545 })
546 })
547
548 it('should hydrate 1-to-m - promise', function(done) {
549 db.authors.get(1).then(function(author) {
550 author.hydrate('author:books').then(function() {
551 ok(author['author:books'].length === 1, 'did not hydrate author:books')
552 ok(!!author['author:books'][0].title, 'author:books[0].title')
553 done()
554 })
555 }).catch(showError)
556 })
557
558 it('should hydrate 1-to-m shorthand - promise', function(done) {
559 db.books.get(1).then(function(book) {
560 return book.hydrate('samples').then(function() {
561 ok(!!book.samples[0].description, 'book.samples[0].description')
562 done()
563 })
564 }).catch(showError)
565 })
566
567 it('should not hydrate ambiguous 1-to-m - promise', function(done) {
568 db.battles.insert({
569 author1_id: 1,
570 author2_id: 2
571 }).then(function (battle) {
572 return battle.hydrate('a1').then(function () {
573 var author = battle.a1
574 return author.hydrate('battles')
575 })
576 }).catch(function (err) {
577 ok(!!err, 'should have ambiguous hydration error')
578 done()
579 }).catch(showError)
580 })
581
582 it('should hydrate non-ambiguous 1-to-m - promise', function(done) {
583 db.battles.insert({
584 author1_id: 2,
585 author2_id: 1
586 }).then(function (battle) {
587 db.authors.get(1).then(function (author) {
588 author.hydrate(['a1:battles', 'a2:battles']).then(function () {
589 ok(!!author['a1:battles'][0].id, 'a1.battles.id')
590 ok(!!author['a2:battles'][0].id, 'a2.battles.id')
591 done()
592 })
593 })
594 }).catch(showError)
595 })
596
597 it('should not hydrate wrong 1-to-m - promise', function(done) {
598 db.books.get(1).then(function(book) {
599 return book.hydrate('author:books')
600 }).catch(function (err) {
601 ok(!!err, 'should have error')
602 done()
603 }).catch(showError)
604 })
605
606 it('should not hydrate shorthand 1-to-m conflicting column name - promise', function(done) {
607 db.authors.get(1).then(function(author) {
608 return author.hydrate('books')
609 }).catch(function (err) {
610 ok(!!err, 'should have error')
611 done()
612 }).catch(showError)
613 })
614
615 it('should hydrate in parallel - cb', function(done) {
616 db.samples.get(1, function(err, sample) {
617 ok(!err, err)
618 async.each([
619 function(next) {
620 sample.hydrate('book', next)
621 },
622 function(next) {
623 sample.hydrate('rating', next)
624 }
625 ], function (fn, end) {
626 fn(end)
627 }, function (err) {
628 no(err)
629 ok(sample.book.id === sample.book_id, 'did not hydrate book')
630 ok(sample.rating.author_id === sample.author_id, 'did not hydrate rating')
631 done()
632 })
633 })
634 })
635
636 it('should hydrate multiple in parallel - promise', function(done) {
637 db.samples.get(1).then(function(sample) {
638 sample.hydrate(['book', 'rating']).then(function () {
639 ok(sample.book.id === sample.book_id, 'did not hydrate book')
640 ok(sample.rating.author_id === sample.author_id, 'did not hydrate rating')
641 done()
642 })
643 }).catch(showError)
644 })
645
646 it('should get and hydrate - promise', function(done) {
647 db.samples.get([1, 1], {
648 hydrate: ['book', 'rating']
649 }).then(function(sample) {
650 ok(!!sample.id, 'sample.id')
651 ok(!!sample.book.id, 'sample.book.id')
652 ok(!!sample.rating.stars, 'sample.rating')
653 done()
654 }).catch(showError)
655 })
656
657 it('should find and hydrate - promise', function(done) {
658 db.books.find({
659 hydrate: 'author'
660 }).then(function(books) {
661 ok(!!books[0].id, 'books[0].id')
662 ok(!!books[0].author.id, 'books[0].author.id')
663 ok(!!books[1].id, 'books[1].id')
664 ok(!!books[1].author.id, 'books[1].author.id')
665 done()
666 }).catch(showError)
667 })
668
669 it('should findOne and hydrate - cb', function(done) {
670 db.books.findOne({
671 hydrate: ['author']
672 }, function(err, book) {
673 no(err)
674 ok(!!book.id, 'book.id')
675 ok(!!book.author.id, 'book.author.id')
676 done()
677 })
678 })
679
680 it('should set', function(done) {
681 db.authors.get(1, function(err, author) {
682 no(err)
683 var old_name = author.name
684 var new_name = 'Jack Kerouac'
685 author.set({
686 name: new_name
687 })
688 ok(author._data.name === old_name, 'did not set old name')
689 ok(author.name === new_name, 'did not set new name')
690 done()
691 })
692 })
693
694 it('should save a row instance - cb', function(done) {
695 db.authors.get(1, function(err, author) {
696 no(err)
697 var new_name = 'Jack2'
698 author.set({
699 name: new_name
700 })
701 author.save(function(err, author) {
702 ok(!err, err)
703 ok(author.id === 1, 'did not get correct author')
704 ok(author.name === new_name, 'did not save author')
705 db.authors.get(1, function(err, author) {
706 ok(!err, err)
707 ok(author.name === new_name, 'did not save new name')
708 done()
709 })
710 })
711 })
712 })
713
714 it('should save a row instance - promise', function(done) {
715 db.authors.get(1).then(function(author) {
716 var new_name = 'Jack3'
717 author.set({
718 name: new_name
719 })
720 author.save().then(function(author) {
721 ok(author.id === 1, 'did not get correct author')
722 ok(author.name === new_name, 'did not save author')
723 db.authors.get(1).then(function(author) {
724 ok(author.name === new_name, 'did not save new name')
725 done()
726 })
727 })
728 }).catch(showError)
729 })
730
731 it('should instantiate model and use constructor - cb', function(done) {
732 db.books.get(1, function(err, book) {
733 no(err)
734 ok(book instanceof db.books.Row, 'incorrect type')
735 ok(book.getTitle() === book.title, 'did not get title')
736 ok(book.getTitle2() === book.title, 'did not run model constructor')
737 var desc = Object.getOwnPropertyDescriptor(book, 'something')
738 ok(!desc.enumerable, 'did not modify data in constructor')
739 done()
740 })
741 })
742
743 it('should execute parameterized query - cb', function(done) {
744 db.execute([
745 'select id',
746 'from authors',
747 'where name = :name'
748 ], {
749 name: 'Jack3',
750 }, function(err, rs) {
751 no(err)
752 ok(rs[0].id === 1, 'wrong record')
753 done()
754 })
755 })
756
757 it('should execute parameterized query - promise', function(done) {
758 db.execute([
759 'select id',
760 'from authors',
761 'where name = :name'
762 ], {
763 name: 'Jack3',
764 }).then(function(rs) {
765 ok(rs[0].id === 1, 'wrong record')
766 done()
767 }).catch(showError)
768 })
769
770 it('should prevent semicolon sqli', function(done) {
771 db.books.find({
772 where: {
773 id: "1; update books set title = 'sqli' where id = '1"
774 }
775 }, function(err, books) {
776 if (err) return done() // postgres errors and that is cool
777 // mysql doesn't error, so let's make sure the injected sql didn't run
778 db.books.get(1, function(err, book) {
779 no(err)
780 ok(book.title !== 'sqli', 'injected update ran')
781 done()
782 })
783 })
784 })
785
786 it('should cache - cb', function(done) {
787 db.books.db._opts.cache = mockRedis()
788 db.books.get(1, function(err, book) {
789 no(err)
790 var new_title = 'New Title'
791 book.update({
792 title: new_title
793 }, function(err) {
794 no(err)
795 db.books.get(1, function(err, book) {
796 no(err)
797 ok(book.title === new_title, 'did not save new title')
798 ok(book.fromCache, 'did not get value from cache')
799 done()
800 })
801 })
802 })
803 })
804
805 it('should cache - promise', function(done) {
806 db.books.get(1).then(function(book) {
807 var new_title = 'New Title2'
808 book.update({
809 title: new_title
810 }).then(function() {
811 db.books.get(1).then(function(book) {
812 ok(book.title === new_title, 'did not save new title')
813 ok(book.fromCache, 'did not get value from cache')
814 db.books.db._opts.cache = null
815 done()
816 })
817 })
818 }).catch(showError)
819 })
820
821 it('should cache mget using composite keys - promise', function(done) {
822 db.authors.db._opts.cache = mockRedis()
823 db.authors.get(1).then(function (author) {
824 var list = [
825 { id: 1984 },
826 { id: 1 },
827 { id: 1408 }
828 ]
829 db.authors.mget(list).then(function(authors) {
830 ok(authors[0].id === list[0].id, 'did not get author 1')
831 ok(authors[1].id === list[1].id, 'did not get author 2')
832 ok(authors[2].id === list[2].id, 'did not get author 3')
833 db.authors.get([1408]).then(function (author) {
834 ok(author.id === 1408, 'did not get author')
835 done()
836 })
837 })
838 }).catch(showError)
839 })
840
841 it('should delete', function(done) {
842 var newBook = {
843 title: 'XYZ Book',
844 author: {
845 name: 'XYZ Author'
846 }
847 }
848 db.books.insert(newBook, function(err, book) {
849 no(err)
850 var bookId = book.id
851 var authorId = book.author_id
852 ok(!!book.id, 'did not insert book')
853 book.delete(function(err) {
854 no(err)
855 ok(!book.id, 'book should be deleted')
856 db.books.get(bookId, function (err, book) {
857 ok(!!err && !!err.notFound, 'should not find deleted book')
858 db.authors.get(authorId, function (err, author) {
859 no(err)
860 ok(!!author.id, 'should not delete author')
861 done()
862 })
863 })
864 })
865 })
866 })
867
868 it('should not delete without cascade', function(done) {
869 var newBook = {
870 title: 'XYZ Book',
871 author: {
872 name: 'XYZ Author'
873 }
874 }
875 db.books.insert(newBook, function(err, book) {
876 no(err)
877 var bookId = book.id
878 var authorId = book.author_id
879 ok(!!book.id, 'did not insert book')
880 book.hydrate('author', function (err) {
881 no(err)
882 book.author.delete(function(err) {
883 ok(!!err, 'should not delete without cascade')
884 done()
885 })
886 })
887 })
888 })
889
890 it('should save 1-to-1 nested object (insert + insert)', function(done) {
891 var newBook = {
892 id: 11,
893 title: 'Book #1',
894 author: {
895 name: 'Author #1'
896 }
897 }
898 db.books.save(newBook, function(err, book) {
899 no(err)
900 ok(book.id === 11, 'did not insert book')
901 book.hydrate('author', function(err) {
902 no(err)
903 ok(book.author_id === book.author.id, 'did not insert author')
904 done()
905 })
906 })
907 })
908
909 it('should save 1-to-1 nested object (update + insert) - promise', function(done) {
910 db.books.get(2).then(function(book) {
911 // replace the book's author with a newly inserted author
912 book.author = {
913 name: 'Author #2'
914 }
915 book.save().then(function(book) {
916 ok(book.id === 2, 'did not get book')
917 book.hydrate('author').then(function() {
918 ok(book.author_id === book.author.id, 'did not insert author')
919 done()
920 })
921 })
922 }).catch(showError)
923 })
924
925 it('should save 1-to-1-to-1 nested objects (insert + insert + insert)', function(done) {
926 var newBookData = {
927 title: 'my title',
928 author: {
929 name: 'Author #3',
930 Country: {
931 Code: 'US',
932 name: 'United States'
933 }
934 }
935 }
936 db.books.save(newBookData, function(err, book) {
937 no(err)
938 book.hydrate('author', function(err) {
939 no(err)
940 book.author.hydrate('Country', function(err) {
941 no(err)
942 ok(book.author.Country.name === 'United States', 'did not save')
943 done()
944 })
945 })
946 })
947 })
948
949 it('should not save shorthand 1-to-m w/ column name conflict', function(done) {
950 var newAuthor = {
951 name: 'Jimbo Jimson',
952 books: [
953 { title: 'My First Book' }
954 ]
955 }
956 db.authors.save(newAuthor).catch(function (err) {
957 ok(!!err, 'should have error')
958 done()
959 })
960 })
961
962 it('should not save a 1-to-m field that is not an array', function(done) {
963 var newAuthor = {
964 name: 'Jimbo Jimson',
965 'author:books': { title: 'My First Book' }
966 }
967 db.authors.save(newAuthor).catch(function (err) {
968 ok(!!err, 'should have error')
969 done()
970 })
971 })
972
973 it('should save 1-to-m (insert + insert)', function(done) {
974 var newAuthor = {
975 name: 'Jimbo Jimson',
976 'author:books': [
977 { title: 'My First Book' },
978 { title: 'My Second Book' }
979 ]
980 }
981 db.authors.save(newAuthor, function(err, author) {
982 no(err)
983 ok(!!author.id, 'did not insert author')
984 ok(author.name === newAuthor.name, 'wrong author.name')
985 var property = 'author:books'
986 author.hydrate([property], function(err) {
987 no(err)
988 ok(!!author[property], 'did not hydrate books')
989 ok(author[property].length === newAuthor[property].length, 'wrong number of books')
990 var book = author[property][0]
991 ok(book.author_id === author.id, 'did not insert book')
992 ok(book.title === newAuthor[property][0].title, 'wrong title')
993 done()
994 })
995 })
996 })
997
998 it('should save shorthand 1-to-m (insert + insert)', function(done) {
999 var newBook = {
1000 title: 'A Great Book',
1001 samples: [
1002 { description: 'Something!' },
1003 { description: 'Something else!' }
1004 ]
1005 }
1006 db.books.save(newBook, function(err, book) {
1007 no(err)
1008 ok(!!book.id, 'did not insert book')
1009 ok(book.title === newBook.title, 'wrong book.title')
1010 var property = 'samples'
1011 book.hydrate(property, function(err) {
1012 no(err)
1013 ok(!!book[property], 'did not hydrate samples')
1014 ok(book[property].length === newBook[property].length, 'wrong number of samples')
1015 var sample = book[property][0]
1016 ok(sample.book_id === book.id, 'did not insert sample')
1017 ok(sample.description === newBook[property][0].description, 'wrong description')
1018 done()
1019 })
1020 })
1021 })
1022
1023 xit('TODO should populate linking table keys', function(done) {
1024 var newAuthor = {
1025 name: 'Chuck Palahniuk',
1026 ratings: [
1027 {
1028 stars: 5,
1029 book: { title: 'Fight Club' }
1030 },
1031 {
1032 stars: 4,
1033 book: { title: 'Choke' }
1034 }
1035 ]
1036 }
1037 db.authors.save(newAuthor, function(err, author) {
1038 no(err)
1039 ok(!!author.id, 'did not insert author')
1040 ok(author.name === newAuthor.name, 'wrong author.name')
1041 var property = 'ratings'
1042 author.hydrate([property], function(err) {
1043 no(err)
1044 ok(!!author[property], 'did not hydrate')
1045 ok(author[property].length === newAuthor[property].length, 'wrong qty')
1046 var rating = author[property][0]
1047 console.log('rating:', rating)
1048 rating.hydrate('book', function(err) {
1049 var book = rating.book
1050 console.log('book:', book)
1051 ok(book.author_id === author.id, 'did not save book.author_id')
1052 ok(book.title === newAuthor.ratings[0].book.title, 'wrong title')
1053 done()
1054 })
1055 })
1056 })
1057 })
1058
1059 // TODO:
1060 // save rows of the same table in parallel
1061 // should fail saving a 1-to-m row that attempts to modify a foreign key column
1062 // should not allow updating foreign key value(s) in a 1-to-1 nested save
1063 // should not allow updating a 1-to-m row if primary key is specified and fk doesn't match this.pk
1064 // should not allow updating a 1-to-1 row if primary key is specified and pk not match this.ftbl_id
1065 // composite primary key insert / update
1066 // primary key id is specified for insert
1067 // table has no primary key
1068 // update table with javascript Date value
1069 // test sql-injection during save - not just select
1070 // onReady
1071 // failed transactions rollback as expected saving 1-to-1 and 1-to-m
1072 // 1-to-1 and 1-to-m unmodified values should not be updated
1073 // uncaught error when trying to save to a 1-to-m that exists but linked to a different table
1074
1075 it('should kill the connection pool', function (done) {
1076 var isDone = false
1077 db.end(function () {
1078 if (isDone) return
1079 isDone = true
1080 done()
1081 })
1082 })
1083
1084 })
1085
1086})