Skip to main content

toasty_driver_integration_suite/tests/
batch_associations.rs

1//! Test batching association-scoped statements (create, query, update, delete)
2//! through `toasty::batch()`.
3
4use crate::prelude::*;
5
6/// Batch two association-scoped creates on the same relation.
7#[driver_test(
8    requires(scan),
9    scenario(crate::scenarios::has_many_belongs_to::id_uuid)
10)]
11pub async fn batch_two_scoped_creates_same_relation(t: &mut Test) -> Result<()> {
12    let mut db = setup(t).await;
13    let user = User::create().name("Alice").exec(&mut db).await?;
14
15    let (t1, t2): (Todo, Todo) = toasty::batch((
16        user.todos().create().title("first"),
17        user.todos().create().title("second"),
18    ))
19    .exec(&mut db)
20    .await?;
21
22    assert_eq!(t1.title, "first");
23    assert_eq!(t2.title, "second");
24    assert_eq!(t1.user_id, user.id);
25    assert_eq!(t2.user_id, user.id);
26
27    let all: Vec<Todo> = user.todos().exec(&mut db).await?;
28    assert_eq!(all.len(), 2);
29
30    Ok(())
31}
32
33/// Batch two association-scoped queries on the same relation.
34#[driver_test(
35    requires(scan),
36    scenario(crate::scenarios::has_many_belongs_to::id_uuid)
37)]
38pub async fn batch_two_scoped_queries_same_relation(t: &mut Test) -> Result<()> {
39    let mut db = setup(t).await;
40
41    let u1 = User::create().name("u1").exec(&mut db).await?;
42    let u2 = User::create().name("u2").exec(&mut db).await?;
43    u1.todos().create().title("u1 todo").exec(&mut db).await?;
44    u2.todos().create().title("u2 todo").exec(&mut db).await?;
45
46    let (u1_todos, u2_todos): (Vec<Todo>, Vec<Todo>) = toasty::batch((u1.todos(), u2.todos()))
47        .exec(&mut db)
48        .await?;
49
50    assert_struct!(u1_todos, [{ title: "u1 todo" }]);
51    assert_struct!(u2_todos, [{ title: "u2 todo" }]);
52
53    Ok(())
54}
55
56/// Batch association-scoped update and delete on the same relation.
57#[driver_test(
58    requires(scan),
59    scenario(crate::scenarios::has_many_belongs_to::id_uuid)
60)]
61pub async fn batch_scoped_update_and_delete_same_relation(t: &mut Test) -> Result<()> {
62    let mut db = setup(t).await;
63    let user = User::create().name("Alice").exec(&mut db).await?;
64    let todo_keep = user.todos().create().title("keep").exec(&mut db).await?;
65    let todo_drop = user.todos().create().title("drop").exec(&mut db).await?;
66
67    let ((), ()): ((), ()) = toasty::batch((
68        user.todos()
69            .filter_by_id(todo_keep.id)
70            .update()
71            .title("kept"),
72        user.todos().filter_by_id(todo_drop.id).delete(),
73    ))
74    .exec(&mut db)
75    .await?;
76
77    let remaining: Vec<Todo> = user.todos().exec(&mut db).await?;
78    assert_eq!(remaining.len(), 1);
79    assert_eq!(remaining[0].title, "kept");
80
81    Ok(())
82}
83
84/// Batch all four CRUD operations through association scope.
85#[driver_test(
86    requires(scan),
87    scenario(crate::scenarios::has_many_belongs_to::id_uuid)
88)]
89pub async fn batch_scoped_all_four_crud(t: &mut Test) -> Result<()> {
90    let mut db = setup(t).await;
91    let user = User::create().name("Alice").exec(&mut db).await?;
92    let existing = user
93        .todos()
94        .create()
95        .title("existing")
96        .exec(&mut db)
97        .await?;
98    let doomed = user.todos().create().title("doomed").exec(&mut db).await?;
99
100    let (queried, created, (), ()): (Vec<Todo>, Todo, (), ()) = toasty::batch((
101        user.todos(),
102        user.todos().create().title("new"),
103        user.todos()
104            .filter_by_id(existing.id)
105            .update()
106            .title("updated"),
107        user.todos().filter_by_id(doomed.id).delete(),
108    ))
109    .exec(&mut db)
110    .await?;
111
112    // Query ran before the update/delete in this batch, so sees original state
113    assert_eq!(queried.len(), 2);
114    assert_eq!(created.title, "new");
115
116    // Verify final state
117    let final_todos: Vec<Todo> = user.todos().exec(&mut db).await?;
118    assert_eq!(final_todos.len(), 2); // "updated" + "new", "doomed" deleted
119
120    let titles: Vec<&str> = final_todos.iter().map(|t| t.title.as_str()).collect();
121    assert!(titles.contains(&"updated"));
122    assert!(titles.contains(&"new"));
123
124    Ok(())
125}
126
127/// Batch association-scoped statements mixed with root-level statements.
128#[driver_test(
129    requires(scan),
130    scenario(crate::scenarios::has_many_belongs_to::id_uuid)
131)]
132pub async fn batch_scoped_with_root_statements(t: &mut Test) -> Result<()> {
133    let mut db = setup(t).await;
134    let user = User::create().name("Alice").exec(&mut db).await?;
135
136    // Mix: root-level query + scoped create + root-level create
137    let (users, todo, new_user): (Vec<User>, Todo, User) = toasty::batch((
138        User::filter_by_name("Alice"),
139        user.todos().create().title("from batch"),
140        User::create().name("Bob"),
141    ))
142    .exec(&mut db)
143    .await?;
144
145    assert_struct!(users, [{ name: "Alice" }]);
146    assert_eq!(todo.title, "from batch");
147    assert_eq!(todo.user_id, user.id);
148    assert_eq!(new_user.name, "Bob");
149
150    Ok(())
151}
152
153/// Batch association statements across different relations of the same parent.
154#[driver_test(requires(scan), scenario(crate::scenarios::user_two_children))]
155pub async fn batch_scoped_across_relations(t: &mut Test) -> Result<()> {
156    let mut db = setup(t).await;
157    let user = User::create().name("Alice").exec(&mut db).await?;
158
159    // Create across two different relations in one batch
160    let (post, comment): (Post, Comment) = toasty::batch((
161        user.posts().create().title("my post"),
162        user.comments().create().text("my comment"),
163    ))
164    .exec(&mut db)
165    .await?;
166
167    assert_eq!(post.title, "my post");
168    assert_eq!(post.user_id, user.id);
169    assert_eq!(comment.text, "my comment");
170    assert_eq!(comment.user_id, user.id);
171
172    Ok(())
173}
174
175/// Batch queries across different relations of the same parent.
176#[driver_test(requires(scan), scenario(crate::scenarios::user_two_children))]
177pub async fn batch_query_across_relations(t: &mut Test) -> Result<()> {
178    let mut db = setup(t).await;
179    let user = User::create().name("Alice").exec(&mut db).await?;
180    user.posts().create().title("t1").exec(&mut db).await?;
181    user.posts().create().title("t2").exec(&mut db).await?;
182    user.comments().create().text("p1").exec(&mut db).await?;
183
184    let (posts, comments): (Vec<Post>, Vec<Comment>) =
185        toasty::batch((user.posts(), user.comments()))
186            .exec(&mut db)
187            .await?;
188
189    assert_eq!(posts.len(), 2);
190    assert_eq!(comments.len(), 1);
191    assert_eq!(comments[0].text, "p1");
192
193    Ok(())
194}
195
196/// Batch scoped operations from different parents.
197#[driver_test(
198    requires(scan),
199    scenario(crate::scenarios::has_many_belongs_to::id_uuid)
200)]
201pub async fn batch_scoped_different_parents(t: &mut Test) -> Result<()> {
202    let mut db = setup(t).await;
203    let alice = User::create().name("Alice").exec(&mut db).await?;
204    let bob = User::create().name("Bob").exec(&mut db).await?;
205
206    // Create todos for different users in one batch
207    let (alice_todo, bob_todo): (Todo, Todo) = toasty::batch((
208        alice.todos().create().title("alice task"),
209        bob.todos().create().title("bob task"),
210    ))
211    .exec(&mut db)
212    .await?;
213
214    assert_eq!(alice_todo.user_id, alice.id);
215    assert_eq!(bob_todo.user_id, bob.id);
216
217    // Query both scopes in one batch
218    let (alice_todos, bob_todos): (Vec<Todo>, Vec<Todo>) =
219        toasty::batch((alice.todos(), bob.todos()))
220            .exec(&mut db)
221            .await?;
222
223    assert_struct!(alice_todos, [{ title: "alice task" }]);
224    assert_struct!(bob_todos, [{ title: "bob task" }]);
225
226    Ok(())
227}
228
229/// Batch a scoped delete together with a root-level update.
230#[driver_test(
231    requires(scan),
232    scenario(crate::scenarios::has_many_belongs_to::id_uuid)
233)]
234pub async fn batch_scoped_delete_with_root_update(t: &mut Test) -> Result<()> {
235    let mut db = setup(t).await;
236    let user = User::create().name("Alice").exec(&mut db).await?;
237    let todo = user.todos().create().title("done").exec(&mut db).await?;
238
239    let ((), ()): ((), ()) = toasty::batch((
240        user.todos().filter_by_id(todo.id).delete(),
241        User::filter_by_name("Alice").update().name("Alice2"),
242    ))
243    .exec(&mut db)
244    .await?;
245
246    // Todo deleted
247    let remaining: Vec<Todo> = user.todos().exec(&mut db).await?;
248    assert!(remaining.is_empty());
249
250    // User updated
251    let updated: Vec<User> = User::filter_by_name("Alice2").exec(&mut db).await?;
252    assert_eq!(updated.len(), 1);
253
254    Ok(())
255}