toasty_driver_integration_suite/tests/
has_many_crud_basic.rs

1//! Test basic has_many associations without any preloading of associations
2//! during query time. All associations are accessed via queries on demand.
3
4use crate::prelude::*;
5use std::collections::HashMap;
6
7#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
8pub async fn crud_user_todos(test: &mut Test) -> Result<()> {
9    let mut db = setup(test).await;
10
11    // Create a user
12    let user = User::create().name("User 1").exec(&mut db).await?;
13
14    // No TODOs
15    assert_eq!(0, user.todos().exec(&mut db).await?.len());
16
17    // Create a Todo associated with the user
18    let todo = user
19        .todos()
20        .create()
21        .title("hello world")
22        .exec(&mut db)
23        .await?;
24
25    // Find the todo by ID
26    let list = Todo::filter_by_id(todo.id).exec(&mut db).await?;
27
28    assert_eq!(1, list.len());
29    assert_eq!(todo.id, list[0].id);
30
31    // Find the TODO by user ID
32    let list = Todo::filter_by_user_id(user.id).exec(&mut db).await?;
33
34    assert_eq!(1, list.len());
35    assert_eq!(todo.id, list[0].id);
36
37    // Find the User using the Todo
38    let user_reload = User::get_by_id(&mut db, &todo.user_id).await?;
39    assert_eq!(user.id, user_reload.id);
40
41    let mut created = HashMap::new();
42    let mut ids = vec![todo.id];
43    created.insert(todo.id, todo);
44
45    // Create a few more TODOs
46    for i in 0..5 {
47        let title = format!("hello world {i}");
48
49        let todo = if i.is_even() {
50            // Create via user
51            user.todos().create().title(title).exec(&mut db).await?
52        } else {
53            // Create via todo builder
54            Todo::create()
55                .user(&user)
56                .title(title)
57                .exec(&mut db)
58                .await?
59        };
60
61        ids.push(todo.id);
62        assert_none!(created.insert(todo.id, todo));
63    }
64
65    // Load all TODOs
66    let list = user.todos().exec(&mut db).await?;
67
68    assert_eq!(6, list.len());
69
70    let loaded: HashMap<_, _> = list.into_iter().map(|todo| (todo.id, todo)).collect();
71    assert_eq!(6, loaded.len());
72
73    for (id, expect) in &created {
74        assert_eq!(expect.title, loaded[id].title);
75    }
76
77    // Find all TODOs by user (using the belongs_to queries)
78    let list = Todo::filter_by_user_id(user.id).exec(&mut db).await?;
79    assert_eq!(6, list.len());
80
81    let by_id: HashMap<_, _> = list.into_iter().map(|todo| (todo.id, todo)).collect();
82
83    assert_eq!(6, by_id.len());
84
85    for (id, expect) in by_id {
86        assert_eq!(expect.title, loaded[&id].title);
87    }
88
89    // Create a second user
90    let user2 = User::create().name("User 2").exec(&mut db).await?;
91
92    // No TODOs associated with `user2`
93    assert_eq!(0, user2.todos().exec(&mut db).await?.len());
94
95    // Create a TODO for user2
96    let u2_todo = user2
97        .todos()
98        .create()
99        .title("user 2 todo")
100        .exec(&mut db)
101        .await?;
102
103    {
104        let u1_todos = user.todos().exec(&mut db).await?;
105
106        for todo in u1_todos {
107            assert_ne!(u2_todo.id, todo.id);
108        }
109    }
110
111    // Delete a TODO by value
112    let todo = Todo::get_by_id(&mut db, &ids[0]).await?;
113    todo.delete().exec(&mut db).await?;
114
115    // Can no longer get the todo via id
116    assert_err!(Todo::get_by_id(&mut db, &ids[0]).await);
117
118    // Can no longer get the todo scoped
119    assert_err!(user.todos().get_by_id(&mut db, &ids[0]).await);
120
121    // Delete a TODO by scope
122    user.todos()
123        .filter_by_id(ids[1])
124        .delete()
125        .exec(&mut db)
126        .await?;
127
128    // Can no longer get the todo via id
129    assert_err!(Todo::get_by_id(&mut db, &ids[1]).await);
130
131    // Can no longer get the todo scoped
132    assert_err!(user.todos().get_by_id(&mut db, &ids[1]).await);
133
134    // Successfuly a todo by scope
135    user.todos()
136        .filter_by_id(ids[2])
137        .update()
138        .title("batch update 1")
139        .exec(&mut db)
140        .await?;
141
142    let todo = Todo::get_by_id(&mut db, &ids[2]).await?;
143    assert_eq!(todo.title, "batch update 1");
144
145    // Now fail to update it by scoping by other user
146    user2
147        .todos()
148        .filter_by_id(ids[2])
149        .update()
150        .title("batch update 2")
151        .exec(&mut db)
152        .await?;
153
154    let todo = Todo::get_by_id(&mut db, &ids[2]).await?;
155    assert_eq!(todo.title, "batch update 1");
156
157    let id = user.id;
158
159    // Delete the user and associated TODOs are deleted
160    user.delete().exec(&mut db).await?;
161    assert_err!(User::get_by_id(&mut db, &id).await);
162    assert_err!(Todo::get_by_id(&mut db, &ids[2]).await);
163    Ok(())
164}
165
166#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
167pub async fn has_many_insert_on_update(test: &mut Test) -> Result<()> {
168    let mut db = setup(test).await;
169
170    // Create a user, no TODOs
171    let mut user = User::create().name("Alice").exec(&mut db).await?;
172    assert!(user.todos().exec(&mut db).await?.is_empty());
173
174    // Update the user and create a todo in a batch
175    user.update()
176        .name("Bob")
177        .todo(Todo::create().title("change name"))
178        .exec(&mut db)
179        .await?;
180
181    assert_eq!("Bob", user.name);
182    let todos: Vec<_> = user.todos().exec(&mut db).await?;
183    assert_eq!(1, todos.len());
184    assert_eq!(todos[0].title, "change name");
185    Ok(())
186}
187
188#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
189pub async fn scoped_find_by_id(test: &mut Test) -> Result<()> {
190    let mut db = setup(test).await;
191
192    // Create a couple of users
193    let user1 = User::create().name("User 1").exec(&mut db).await?;
194    let user2 = User::create().name("User 2").exec(&mut db).await?;
195
196    // Create a todo
197    let todo = user1
198        .todos()
199        .create()
200        .title("hello world")
201        .exec(&mut db)
202        .await?;
203
204    // Find it scoped by user1
205    let reloaded = user1.todos().get_by_id(&mut db, &todo.id).await?;
206    assert_eq!(reloaded.id, todo.id);
207    assert_eq!(reloaded.title, todo.title);
208
209    // Trying to find the same todo scoped by user2 is missing
210    assert_none!(user2.todos().filter_by_id(todo.id).first(&mut db).await?);
211
212    let reloaded = User::filter_by_id(user1.id)
213        .todos()
214        .get_by_id(&mut db, &todo.id)
215        .await?;
216
217    assert_eq!(reloaded.id, todo.id);
218    assert_eq!(reloaded.title, todo.title);
219
220    // Deleting the TODO from the user 2 scope fails
221    user2
222        .todos()
223        .filter_by_id(todo.id)
224        .delete()
225        .exec(&mut db)
226        .await?;
227    let reloaded = user1.todos().get_by_id(&mut db, &todo.id).await?;
228    assert_eq!(reloaded.id, todo.id);
229    Ok(())
230}
231
232// The has_many association uses the target's primary key as the association's
233// foreign key. In this case, the relation's query should not be duplicated.
234#[driver_test(id(ID))]
235pub async fn has_many_on_target_pk(_test: &mut Test) {}
236
237// The target model has an explicit index on (FK, PK). In this case, the query
238// generated by the (FK, PK) pair should not be duplicated by the relation.
239#[driver_test(id(ID))]
240pub async fn has_many_when_target_indexes_fk_and_pk(_test: &mut Test) {}
241
242// When the FK is composite, things should still work
243#[driver_test(id(ID))]
244pub async fn has_many_when_fk_is_composite(test: &mut Test) -> Result<()> {
245    #[derive(Debug, toasty::Model)]
246    struct User {
247        #[key]
248        #[auto]
249        id: ID,
250
251        #[has_many]
252        todos: toasty::HasMany<Todo>,
253    }
254
255    #[derive(Debug, toasty::Model)]
256    #[key(partition = user_id, local = id)]
257    struct Todo {
258        #[auto]
259        id: uuid::Uuid,
260
261        user_id: ID,
262
263        #[belongs_to(key = user_id, references = id)]
264        user: toasty::BelongsTo<User>,
265
266        title: String,
267    }
268
269    let mut db = test.setup_db(models!(User, Todo)).await;
270
271    // Create a user
272    let user = User::create().exec(&mut db).await?;
273
274    // No TODOs
275    assert_eq!(0, user.todos().exec(&mut db).await?.len());
276
277    // Create a Todo associated with the user
278    let todo = user
279        .todos()
280        .create()
281        .title("hello world")
282        .exec(&mut db)
283        .await?;
284
285    // Find the todo by ID
286    let list = Todo::filter_by_user_id_and_id(user.id, todo.id)
287        .exec(&mut db)
288        .await?;
289
290    assert_eq!(1, list.len());
291    assert_eq!(todo.id, list[0].id);
292
293    // Find the TODO by user ID
294    let list = Todo::filter_by_user_id(user.id).exec(&mut db).await?;
295
296    assert_eq!(1, list.len());
297    assert_eq!(todo.id, list[0].id);
298
299    let mut created = HashMap::new();
300    let mut ids = vec![todo.id];
301    created.insert(todo.id, todo);
302
303    // Create a few more TODOs
304    for i in 0..5 {
305        let title = format!("hello world {i}");
306
307        let todo = if i.is_even() {
308            // Create via user
309            user.todos().create().title(title).exec(&mut db).await?
310        } else {
311            // Create via todo builder
312            Todo::create()
313                .user(&user)
314                .title(title)
315                .exec(&mut db)
316                .await?
317        };
318
319        ids.push(todo.id);
320        assert_none!(created.insert(todo.id, todo));
321    }
322
323    // Load all TODOs
324    let list = user.todos().exec(&mut db).await?;
325
326    assert_eq!(6, list.len());
327
328    let loaded: HashMap<_, _> = list.into_iter().map(|todo| (todo.id, todo)).collect();
329    assert_eq!(6, loaded.len());
330
331    for (id, expect) in &created {
332        assert_eq!(expect.title, loaded[id].title);
333    }
334
335    // Find all TODOs by user (using the belongs_to queries)
336    let list = Todo::filter_by_user_id(user.id).exec(&mut db).await?;
337    assert_eq!(6, list.len());
338
339    let by_id: HashMap<_, _> = list.into_iter().map(|todo| (todo.id, todo)).collect();
340
341    assert_eq!(6, by_id.len());
342
343    for (id, expect) in by_id {
344        assert_eq!(expect.title, loaded[&id].title);
345    }
346
347    // Create a second user
348    let user2 = User::create().exec(&mut db).await?;
349
350    // No TODOs associated with `user2`
351    assert_eq!(0, user2.todos().exec(&mut db).await?.len());
352
353    // Create a TODO for user2
354    let u2_todo = user2
355        .todos()
356        .create()
357        .title("user 2 todo")
358        .exec(&mut db)
359        .await?;
360
361    let u1_todos = user.todos().exec(&mut db).await?;
362
363    for todo in u1_todos {
364        assert_ne!(u2_todo.id, todo.id);
365    }
366
367    // Delete a TODO by value
368    let todo = Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[0]).await?;
369    todo.delete().exec(&mut db).await?;
370
371    // Can no longer get the todo via id
372    assert_err!(Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[0]).await);
373
374    // Can no longer get the todo scoped
375    assert_err!(user.todos().get_by_id(&mut db, &ids[0]).await);
376
377    // Delete a TODO by scope
378    user.todos()
379        .filter_by_id(ids[1])
380        .delete()
381        .exec(&mut db)
382        .await?;
383
384    // Can no longer get the todo via id
385    assert_err!(Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[1]).await);
386
387    // Can no longer get the todo scoped
388    assert_err!(user.todos().get_by_id(&mut db, &ids[1]).await);
389
390    // Successfuly a todo by scope
391    user.todos()
392        .filter_by_id(ids[2])
393        .update()
394        .title("batch update 1")
395        .exec(&mut db)
396        .await?;
397    let todo = Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[2]).await?;
398    assert_eq!(todo.title, "batch update 1");
399
400    // Now fail to update it by scoping by other user
401    user2
402        .todos()
403        .filter_by_id(ids[2])
404        .update()
405        .title("batch update 2")
406        .exec(&mut db)
407        .await?;
408    let todo = Todo::get_by_user_id_and_id(&mut db, &user.id, &ids[2]).await?;
409    assert_eq!(todo.title, "batch update 1");
410    Ok(())
411}
412
413// When the PK is composite, things should still work
414#[driver_test(id(ID))]
415pub async fn has_many_when_pk_is_composite(_test: &mut Test) {}
416
417// When both the FK and PK are composite, things should still work
418#[driver_test(id(ID))]
419pub async fn has_many_when_fk_and_pk_are_composite(_test: &mut Test) {}
420
421#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
422pub async fn belongs_to_required(test: &mut Test) {
423    let mut db = setup(test).await;
424
425    assert_err!(Todo::create().exec(&mut db).await);
426}
427
428#[driver_test(id(ID))]
429pub async fn delete_when_belongs_to_optional(test: &mut Test) -> Result<()> {
430    #[derive(Debug, toasty::Model)]
431    struct User {
432        #[key]
433        #[auto]
434        id: ID,
435
436        #[has_many]
437        todos: toasty::HasMany<Todo>,
438    }
439
440    #[derive(Debug, toasty::Model)]
441    struct Todo {
442        #[key]
443        #[auto]
444        id: ID,
445
446        #[index]
447        user_id: Option<ID>,
448
449        #[belongs_to(key = user_id, references = id)]
450        user: toasty::BelongsTo<Option<User>>,
451    }
452
453    let mut db = test.setup_db(models!(User, Todo)).await;
454
455    let user = User::create().exec(&mut db).await?;
456    let mut ids = vec![];
457
458    for _ in 0..3 {
459        let todo = user.todos().create().exec(&mut db).await?;
460        ids.push(todo.id);
461    }
462
463    // Delete the user
464    user.delete().exec(&mut db).await?;
465
466    // All the todos still exist and `user` is set to `None`.
467    for id in ids {
468        let todo = Todo::get_by_id(&mut db, id).await?;
469        assert_none!(todo.user_id);
470    }
471
472    // Deleting a user leaves the todo in place.
473    Ok(())
474}
475
476#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
477pub async fn associate_new_user_with_todo_on_update_via_creation(test: &mut Test) -> Result<()> {
478    let mut db = setup(test).await;
479
480    // Create a user with a todo
481    let u1 = User::create()
482        .name("User 1")
483        .todo(Todo::create().title("hello world"))
484        .exec(&mut db)
485        .await?;
486
487    // Get the todo
488    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
489    assert_eq!(1, todos.len());
490    let mut todo = todos.into_iter().next().unwrap();
491
492    todo.update()
493        .user(User::create().name("User 2"))
494        .exec(&mut db)
495        .await?;
496    Ok(())
497}
498
499#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
500pub async fn associate_new_user_with_todo_on_update_query_via_creation(
501    test: &mut Test,
502) -> Result<()> {
503    let mut db = setup(test).await;
504
505    // Create a user with a todo
506    let u1 = User::create()
507        .name("User 1")
508        .todo(Todo::create().title("a todo"))
509        .exec(&mut db)
510        .await?;
511
512    // Get the todo
513    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
514    assert_eq!(1, todos.len());
515    let todo = todos.into_iter().next().unwrap();
516
517    Todo::filter_by_id(todo.id)
518        .update()
519        .user(User::create().name("User 2"))
520        .exec(&mut db)
521        .await?;
522    Ok(())
523}
524
525#[driver_test(id(ID))]
526#[should_panic]
527pub async fn update_user_with_null_todo_is_err(test: &mut Test) -> Result<()> {
528    #[derive(Debug, toasty::Model)]
529    struct User {
530        #[key]
531        #[auto]
532        id: ID,
533
534        #[has_many]
535        todos: toasty::HasMany<Todo>,
536    }
537
538    #[derive(Debug, toasty::Model)]
539    struct Todo {
540        #[key]
541        #[auto]
542        id: ID,
543
544        #[index]
545        user_id: ID,
546
547        #[belongs_to(key = user_id, references = id)]
548        user: toasty::BelongsTo<User>,
549    }
550
551    use toasty::stmt::{self, IntoExpr};
552
553    let mut db = test.setup_db(models!(User, Todo)).await;
554
555    // Create a user with a todo
556    let u1 = User::create().todo(Todo::create()).exec(&mut db).await?;
557
558    // Get the todo
559    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
560    assert_eq!(1, todos.len());
561    let todo = todos.into_iter().next().unwrap();
562
563    // Updating the todo w/ null is an error. Thus requires a bit of a hack to make work
564    let mut stmt: stmt::Update<Todo> =
565        stmt::Update::new(stmt::Query::from_expr((&todo).into_expr()));
566    stmt.set(2, toasty_core::stmt::Value::Null);
567    let _ = db.exec(stmt.into()).await?;
568
569    // User is not deleted
570    let u1_reloaded = User::get_by_id(&mut db, &u1.id).await?;
571    assert_eq!(u1_reloaded.id, u1.id);
572    Ok(())
573}
574
575#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
576pub async fn assign_todo_that_already_has_user_on_create(test: &mut Test) -> Result<()> {
577    let mut db = setup(test).await;
578
579    let todo = Todo::create()
580        .title("a todo")
581        .user(User::create().name("User 1"))
582        .exec(&mut db)
583        .await?;
584
585    let u1 = todo.user().get(&mut db).await?;
586
587    let u2 = User::create()
588        .name("User 2")
589        .todo(&todo)
590        .exec(&mut db)
591        .await?;
592
593    let todo_reload = Todo::get_by_id(&mut db, &todo.id).await?;
594
595    assert_eq!(u2.id, todo_reload.user_id);
596
597    // First user has no todos
598    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
599    assert_eq!(0, todos.len());
600
601    // Second user has the todo
602    let todos: Vec<_> = u2.todos().exec(&mut db).await?;
603    assert_eq!(1, todos.len());
604    assert_eq!(todo.id, todos[0].id);
605    Ok(())
606}
607
608#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
609pub async fn assign_todo_that_already_has_user_on_update(test: &mut Test) -> Result<()> {
610    let mut db = setup(test).await;
611
612    let todo = Todo::create()
613        .title("a todo")
614        .user(User::create().name("User 1"))
615        .exec(&mut db)
616        .await?;
617
618    let u1 = todo.user().get(&mut db).await?;
619
620    let mut u2 = User::create().name("User 2").exec(&mut db).await?;
621
622    // Update the user
623    u2.update().todo(&todo).exec(&mut db).await?;
624
625    let todo_reload = Todo::get_by_id(&mut db, &todo.id).await?;
626
627    assert_eq!(u2.id, todo_reload.user_id);
628
629    // First user has no todos
630    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
631    assert_eq!(0, todos.len());
632
633    // Second user has the todo
634    let todos: Vec<_> = u2.todos().exec(&mut db).await?;
635    assert_eq!(1, todos.len());
636    assert_eq!(todo.id, todos[0].id);
637    Ok(())
638}
639
640#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
641pub async fn assign_existing_user_to_todo(test: &mut Test) -> Result<()> {
642    let mut db = setup(test).await;
643
644    let mut todo = Todo::create()
645        .title("hello")
646        .user(User::create().name("User 1"))
647        .exec(&mut db)
648        .await?;
649
650    let u1 = todo.user().get(&mut db).await?;
651
652    let u2 = User::create().name("User 2").exec(&mut db).await?;
653
654    // Update the todo
655    todo.update().user(&u2).exec(&mut db).await?;
656
657    let todo_reload = Todo::get_by_id(&mut db, &todo.id).await?;
658
659    assert_eq!(u2.id, todo_reload.user_id);
660
661    // First user has no todos
662    let todos: Vec<_> = u1.todos().exec(&mut db).await?;
663    assert_eq!(0, todos.len());
664
665    // Second user has the todo
666    let todos: Vec<_> = u2.todos().exec(&mut db).await?;
667    assert_eq!(1, todos.len());
668    assert_eq!(todo.id, todos[0].id);
669    Ok(())
670}
671
672#[driver_test(id(ID), scenario(crate::scenarios::has_many_belongs_to))]
673pub async fn assign_todo_to_user_on_update_query(test: &mut Test) -> Result<()> {
674    let mut db = setup(test).await;
675
676    let user = User::create().name("User 1").exec(&mut db).await?;
677
678    User::filter_by_id(user.id)
679        .update()
680        .todo(Todo::create().title("hello"))
681        .exec(&mut db)
682        .await?;
683
684    let todos: Vec<_> = user.todos().exec(&mut db).await?;
685    assert_eq!(1, todos.len());
686    assert_eq!("hello", todos[0].title);
687    Ok(())
688}
689
690#[driver_test(id(ID))]
691pub async fn has_many_when_fk_is_composite_with_snippets(test: &mut Test) -> Result<()> {
692    #[derive(Debug, toasty::Model)]
693    struct User {
694        #[key]
695        #[auto]
696        id: ID,
697
698        #[has_many]
699        todos: toasty::HasMany<Todo>,
700    }
701
702    #[derive(Debug, toasty::Model)]
703    #[key(partition = user_id, local = id)]
704    struct Todo {
705        #[auto]
706        id: uuid::Uuid,
707
708        user_id: ID,
709
710        #[belongs_to(key = user_id, references = id)]
711        user: toasty::BelongsTo<User>,
712
713        title: String,
714    }
715
716    let mut db = test.setup_db(models!(User, Todo)).await;
717
718    // Create users
719    let user1 = User::create().exec(&mut db).await?;
720    let user2 = User::create().exec(&mut db).await?;
721
722    // Create a Todo associated with the user
723    user1
724        .todos()
725        .create()
726        .title("hello world")
727        .exec(&mut db)
728        .await?;
729
730    let todo2 = user2
731        .todos()
732        .create()
733        .title("hello world")
734        .exec(&mut db)
735        .await?;
736
737    // Update the Todos with the snippets
738    Todo::update_by_user_id(user1.id)
739        .title("Title 2")
740        .exec(&mut db)
741        .await?;
742
743    let todo = Todo::get_by_user_id(&mut db, user1.id).await?;
744    assert!(todo.title == "Title 2");
745
746    Todo::update_by_user_id_and_id(user2.id, todo2.id)
747        .title("Title 3")
748        .exec(&mut db)
749        .await?;
750
751    let todo = Todo::get_by_user_id_and_id(&mut db, user2.id, todo2.id).await?;
752    assert!(todo.title == "Title 3");
753
754    // Delete the Todos with the snippets
755    Todo::delete_by_user_id(&mut db, user1.id).await?;
756    assert_err!(Todo::get_by_user_id(&mut db, user1.id).await);
757
758    Todo::delete_by_user_id_and_id(&mut db, user2.id, todo2.id).await?;
759    assert_err!(Todo::get_by_user_id_and_id(&mut db, user2.id, todo2.id).await);
760
761    Ok(())
762}