1use crate::prelude::*;
9
10#[driver_test(id(ID), scenario(crate::scenarios::has_many_multi_relation))]
14pub async fn user_todos_category(test: &mut Test) -> Result<()> {
15 let mut db = setup(test).await;
16
17 let user = toasty::create!(User { name: "Anchovy" })
18 .exec(&mut db)
19 .await?;
20 let other_user = toasty::create!(User { name: "Other" })
21 .exec(&mut db)
22 .await?;
23
24 let food = toasty::create!(Category { name: "Food" })
25 .exec(&mut db)
26 .await?;
27 let drink = toasty::create!(Category { name: "Drink" })
28 .exec(&mut db)
29 .await?;
30 let unused = toasty::create!(Category { name: "Unused" })
31 .exec(&mut db)
32 .await?;
33
34 toasty::create!(Todo::[
35 { title: "salad", user: &user, category: &food },
36 { title: "tea", user: &user, category: &drink },
37 { title: "sushi", user: &user, category: &food },
38 { title: "wine", user: &other_user, category: &unused },
39 ])
40 .exec(&mut db)
41 .await?;
42
43 let mut categories = user.todos().category().exec(&mut db).await?;
44 categories.sort_by_key(|c| c.name.clone());
45
46 let ids: Vec<_> = categories.iter().map(|c| c.id).collect();
47 assert_unique!(ids);
48 assert_eq!(categories.len(), 2);
49 assert_eq!(categories[0].id, drink.id);
50 assert_eq!(categories[1].id, food.id);
51 Ok(())
52}
53
54#[driver_test(id(ID), scenario(crate::scenarios::has_many_multi_relation))]
56pub async fn chain_from_empty_source_is_empty(test: &mut Test) -> Result<()> {
57 let mut db = setup(test).await;
58
59 let user = toasty::create!(User { name: "Lonely" })
60 .exec(&mut db)
61 .await?;
62
63 let other = toasty::create!(User { name: "Busy" }).exec(&mut db).await?;
66 let food = toasty::create!(Category { name: "Food" })
67 .exec(&mut db)
68 .await?;
69 toasty::create!(Todo {
70 title: "salad",
71 user: &other,
72 category: &food
73 })
74 .exec(&mut db)
75 .await?;
76
77 let categories = user.todos().category().exec(&mut db).await?;
78 assert!(categories.is_empty());
79 Ok(())
80}
81
82#[driver_test(id(ID), scenario(crate::scenarios::has_many_multi_relation))]
85pub async fn chain_dedupes_when_todos_share_category(test: &mut Test) -> Result<()> {
86 let mut db = setup(test).await;
87
88 let user = toasty::create!(User { name: "Cooky" })
89 .exec(&mut db)
90 .await?;
91 let food = toasty::create!(Category { name: "Food" })
92 .exec(&mut db)
93 .await?;
94
95 for i in 0..5 {
96 let title = format!("todo {i}");
97 toasty::create!(Todo {
98 title,
99 user: &user,
100 category: &food
101 })
102 .exec(&mut db)
103 .await?;
104 }
105
106 let categories = user.todos().category().exec(&mut db).await?;
107 assert_eq!(categories.len(), 1);
108 assert_eq!(categories[0].id, food.id);
109 Ok(())
110}
111
112#[driver_test(id(ID), scenario(crate::scenarios::has_many_multi_relation))]
115pub async fn chain_scopes_per_starting_user(test: &mut Test) -> Result<()> {
116 let mut db = setup(test).await;
117
118 let alice = toasty::create!(User { name: "Alice" })
119 .exec(&mut db)
120 .await?;
121 let bob = toasty::create!(User { name: "Bob" }).exec(&mut db).await?;
122
123 let a = toasty::create!(Category { name: "A" })
124 .exec(&mut db)
125 .await?;
126 let b = toasty::create!(Category { name: "B" })
127 .exec(&mut db)
128 .await?;
129 let c = toasty::create!(Category { name: "C" })
130 .exec(&mut db)
131 .await?;
132
133 toasty::create!(Todo::[
134 { title: "a1", user: &alice, category: &a },
135 { title: "a2", user: &alice, category: &b },
136 { title: "b1", user: &bob, category: &b },
137 { title: "b2", user: &bob, category: &c },
138 ])
139 .exec(&mut db)
140 .await?;
141
142 let mut alice_cats = alice.todos().category().exec(&mut db).await?;
143 alice_cats.sort_by_key(|c| c.name.clone());
144 let alice_ids: Vec<_> = alice_cats.iter().map(|c| c.id).collect();
145 assert_eq!(alice_ids, vec![a.id, b.id]);
146
147 let mut bob_cats = bob.todos().category().exec(&mut db).await?;
148 bob_cats.sort_by_key(|c| c.name.clone());
149 let bob_ids: Vec<_> = bob_cats.iter().map(|c| c.id).collect();
150 assert_eq!(bob_ids, vec![b.id, c.id]);
151 Ok(())
152}
153
154#[driver_test(id(ID), scenario(crate::scenarios::has_many_multi_relation))]
157pub async fn chain_then_filter(test: &mut Test) -> Result<()> {
158 let mut db = setup(test).await;
159
160 let user = toasty::create!(User { name: "Filty" })
161 .exec(&mut db)
162 .await?;
163 let food = toasty::create!(Category { name: "Food" })
164 .exec(&mut db)
165 .await?;
166 let drink = toasty::create!(Category { name: "Drink" })
167 .exec(&mut db)
168 .await?;
169
170 toasty::create!(Todo::[
171 { title: "salad", user: &user, category: &food },
172 { title: "tea", user: &user, category: &drink },
173 ])
174 .exec(&mut db)
175 .await?;
176
177 let only_food = user
178 .todos()
179 .category()
180 .filter(Category::fields().name().eq("Food"))
181 .exec(&mut db)
182 .await?;
183 assert_eq!(only_food.len(), 1);
184 assert_eq!(only_food[0].id, food.id);
185 Ok(())
186}
187
188#[driver_test(id(ID), scenario(crate::scenarios::user_post_comment))]
191pub async fn has_many_through_has_many(test: &mut Test) -> Result<()> {
192 let mut db = setup(test).await;
193
194 let alice = toasty::create!(User { name: "Alice" })
195 .exec(&mut db)
196 .await?;
197 let bob = toasty::create!(User { name: "Bob" }).exec(&mut db).await?;
198
199 let p1 = toasty::create!(Post {
200 title: "p1",
201 user: &alice
202 })
203 .exec(&mut db)
204 .await?;
205 let p2 = toasty::create!(Post {
206 title: "p2",
207 user: &alice
208 })
209 .exec(&mut db)
210 .await?;
211 let p3 = toasty::create!(Post {
212 title: "p3",
213 user: &bob
214 })
215 .exec(&mut db)
216 .await?;
217
218 toasty::create!(Comment::[
219 { body: "c1", post: &p1 },
220 { body: "c2", post: &p1 },
221 { body: "c3", post: &p2 },
222 { body: "c4", post: &p3 },
223 ])
224 .exec(&mut db)
225 .await?;
226
227 let mut alice_comments = alice.posts().comments().exec(&mut db).await?;
228 alice_comments.sort_by_key(|c| c.body.clone());
229 let bodies: Vec<_> = alice_comments.iter().map(|c| c.body.clone()).collect();
230 assert_eq!(bodies, vec!["c1", "c2", "c3"]);
231
232 let bob_comments = bob.posts().comments().exec(&mut db).await?;
233 assert_eq!(bob_comments.len(), 1);
234 assert_eq!(bob_comments[0].body, "c4");
235 Ok(())
236}
237
238#[driver_test]
242pub async fn three_step_chain(test: &mut Test) -> Result<()> {
243 #[derive(Debug, toasty::Model)]
244 struct User {
245 #[key]
246 #[auto]
247 id: uuid::Uuid,
248 name: String,
249 #[has_many]
250 projects: toasty::Deferred<Vec<Project>>,
251 }
252
253 #[derive(Debug, toasty::Model)]
254 struct Project {
255 #[key]
256 #[auto]
257 id: uuid::Uuid,
258 #[index]
259 user_id: uuid::Uuid,
260 #[belongs_to(key = user_id, references = id)]
261 user: toasty::Deferred<User>,
262 name: String,
263 #[has_many]
264 tasks: toasty::Deferred<Vec<Task>>,
265 }
266
267 #[derive(Debug, toasty::Model)]
268 struct Task {
269 #[key]
270 #[auto]
271 id: uuid::Uuid,
272 #[index]
273 project_id: uuid::Uuid,
274 #[belongs_to(key = project_id, references = id)]
275 project: toasty::Deferred<Project>,
276 title: String,
277 #[index]
278 tag_id: uuid::Uuid,
279 #[belongs_to(key = tag_id, references = id)]
280 tag: toasty::Deferred<Tag>,
281 }
282
283 #[derive(Debug, toasty::Model)]
284 struct Tag {
285 #[key]
286 #[auto]
287 id: uuid::Uuid,
288 name: String,
289 #[has_many]
290 tasks: toasty::Deferred<Vec<Task>>,
291 }
292
293 let mut db = test.setup_db(models!(User, Project, Task, Tag)).await;
294
295 let user = toasty::create!(User { name: "Owner" })
296 .exec(&mut db)
297 .await?;
298 let other = toasty::create!(User { name: "Other" })
299 .exec(&mut db)
300 .await?;
301
302 let backend = toasty::create!(Project {
303 name: "Backend",
304 user: &user
305 })
306 .exec(&mut db)
307 .await?;
308 let frontend = toasty::create!(Project {
309 name: "Frontend",
310 user: &user
311 })
312 .exec(&mut db)
313 .await?;
314 let unrelated = toasty::create!(Project {
315 name: "Unrelated",
316 user: &other
317 })
318 .exec(&mut db)
319 .await?;
320
321 let bug = toasty::create!(Tag { name: "bug" }).exec(&mut db).await?;
322 let feat = toasty::create!(Tag { name: "feature" })
323 .exec(&mut db)
324 .await?;
325 let chore = toasty::create!(Tag { name: "chore" }).exec(&mut db).await?;
326
327 toasty::create!(Task::[
328 { title: "fix login", project: &backend, tag: &bug },
329 { title: "add dark mode", project: &frontend, tag: &feat },
330 { title: "rotate keys", project: &backend, tag: &chore },
331 { title: "different user", project: &unrelated, tag: &bug },
332 ])
333 .exec(&mut db)
334 .await?;
335
336 let mut tags = user.projects().tasks().tag().exec(&mut db).await?;
337 tags.sort_by_key(|t| t.name.clone());
338 let ids: Vec<_> = tags.iter().map(|t| t.id).collect();
339 assert_unique!(ids);
340 let names: Vec<_> = tags.iter().map(|t| t.name.clone()).collect();
341 assert_eq!(names, vec!["bug", "chore", "feature"]);
342 Ok(())
343}
344
345#[driver_test]
350pub async fn four_step_chain(test: &mut Test) -> Result<()> {
351 #[derive(Debug, toasty::Model)]
352 struct Org {
353 #[key]
354 #[auto]
355 id: uuid::Uuid,
356 name: String,
357 #[has_many]
358 teams: toasty::Deferred<Vec<Team>>,
359 }
360
361 #[derive(Debug, toasty::Model)]
362 struct Team {
363 #[key]
364 #[auto]
365 id: uuid::Uuid,
366 #[index]
367 org_id: uuid::Uuid,
368 #[belongs_to(key = org_id, references = id)]
369 org: toasty::Deferred<Org>,
370 name: String,
371 #[has_many]
372 projects: toasty::Deferred<Vec<Project>>,
373 }
374
375 #[derive(Debug, toasty::Model)]
376 struct Project {
377 #[key]
378 #[auto]
379 id: uuid::Uuid,
380 #[index]
381 team_id: uuid::Uuid,
382 #[belongs_to(key = team_id, references = id)]
383 team: toasty::Deferred<Team>,
384 name: String,
385 #[has_many]
386 issues: toasty::Deferred<Vec<Issue>>,
387 }
388
389 #[derive(Debug, toasty::Model)]
390 struct Issue {
391 #[key]
392 #[auto]
393 id: uuid::Uuid,
394 #[index]
395 project_id: uuid::Uuid,
396 #[belongs_to(key = project_id, references = id)]
397 project: toasty::Deferred<Project>,
398 title: String,
399 #[index]
400 tag_id: uuid::Uuid,
401 #[belongs_to(key = tag_id, references = id)]
402 tag: toasty::Deferred<Tag>,
403 }
404
405 #[derive(Debug, toasty::Model)]
406 struct Tag {
407 #[key]
408 #[auto]
409 id: uuid::Uuid,
410 name: String,
411 #[has_many]
412 issues: toasty::Deferred<Vec<Issue>>,
413 }
414
415 let mut db = test.setup_db(models!(Org, Team, Project, Issue, Tag)).await;
416
417 let mine = toasty::create!(Org { name: "Mine" }).exec(&mut db).await?;
418 let theirs = toasty::create!(Org { name: "Theirs" })
419 .exec(&mut db)
420 .await?;
421
422 let core = toasty::create!(Team {
423 name: "core",
424 org: &mine
425 })
426 .exec(&mut db)
427 .await?;
428 let ops = toasty::create!(Team {
429 name: "ops",
430 org: &mine
431 })
432 .exec(&mut db)
433 .await?;
434 let outside = toasty::create!(Team {
435 name: "outside",
436 org: &theirs
437 })
438 .exec(&mut db)
439 .await?;
440
441 let backend = toasty::create!(Project {
442 name: "backend",
443 team: &core
444 })
445 .exec(&mut db)
446 .await?;
447 let frontend = toasty::create!(Project {
448 name: "frontend",
449 team: &core
450 })
451 .exec(&mut db)
452 .await?;
453 let infra = toasty::create!(Project {
454 name: "infra",
455 team: &ops
456 })
457 .exec(&mut db)
458 .await?;
459 let unrelated = toasty::create!(Project {
460 name: "unrelated",
461 team: &outside
462 })
463 .exec(&mut db)
464 .await?;
465
466 let bug = toasty::create!(Tag { name: "bug" }).exec(&mut db).await?;
467 let feat = toasty::create!(Tag { name: "feature" })
468 .exec(&mut db)
469 .await?;
470 let chore = toasty::create!(Tag { name: "chore" }).exec(&mut db).await?;
471 let unused = toasty::create!(Tag { name: "unused" })
472 .exec(&mut db)
473 .await?;
474
475 toasty::create!(Issue::[
476 { title: "fix login", project: &backend, tag: &bug },
477 { title: "dark mode", project: &frontend, tag: &feat },
478 { title: "rotate keys", project: &infra, tag: &chore },
479 { title: "duplicate", project: &backend, tag: &bug },
480 { title: "their issue", project: &unrelated, tag: &unused },
481 ])
482 .exec(&mut db)
483 .await?;
484
485 let mut tags = mine.teams().projects().issues().tag().exec(&mut db).await?;
486 tags.sort_by_key(|t| t.name.clone());
487
488 let ids: Vec<_> = tags.iter().map(|t| t.id).collect();
489 assert_unique!(ids);
490 let names: Vec<_> = tags.iter().map(|t| t.name.clone()).collect();
491 assert_eq!(names, vec!["bug", "chore", "feature"]);
492 Ok(())
493}
494
495#[driver_test]
498pub async fn chain_skips_null_belongs_to(test: &mut Test) -> Result<()> {
499 #[derive(Debug, toasty::Model)]
500 struct User {
501 #[key]
502 #[auto]
503 id: uuid::Uuid,
504 name: String,
505 #[has_many]
506 todos: toasty::Deferred<Vec<Todo>>,
507 }
508
509 #[derive(Debug, toasty::Model)]
510 struct Todo {
511 #[key]
512 #[auto]
513 id: uuid::Uuid,
514 #[index]
515 user_id: uuid::Uuid,
516 #[belongs_to(key = user_id, references = id)]
517 user: toasty::Deferred<User>,
518 title: String,
519 #[index]
520 category_id: Option<uuid::Uuid>,
521 #[belongs_to(key = category_id, references = id)]
522 category: toasty::Deferred<Option<Category>>,
523 }
524
525 #[derive(Debug, toasty::Model)]
526 struct Category {
527 #[key]
528 #[auto]
529 id: uuid::Uuid,
530 name: String,
531 }
532
533 let mut db = test.setup_db(models!(User, Todo, Category)).await;
534
535 let user = toasty::create!(User { name: "Tester" })
536 .exec(&mut db)
537 .await?;
538 let cat = toasty::create!(Category { name: "Only" })
539 .exec(&mut db)
540 .await?;
541
542 toasty::create!(Todo::[
543 { title: "with cat", user: &user, category: &cat },
544 { title: "no cat 1", user: &user },
545 { title: "no cat 2", user: &user },
546 ])
547 .exec(&mut db)
548 .await?;
549
550 let cats = user.todos().category().exec(&mut db).await?;
551 assert_eq!(cats.len(), 1);
552 assert_eq!(cats[0].id, cat.id);
553 Ok(())
554}