1use collections::{HashMap, HashSet};
2use rpc::{
3 proto::{self},
4 ConnectionId,
5};
6
7use crate::{
8 db::{
9 queries::channels::ChannelGraph,
10 tests::{graph, TEST_RELEASE_CHANNEL},
11 ChannelId, Database, NewUserParams,
12 },
13 test_both_dbs,
14};
15use std::sync::Arc;
16
17test_both_dbs!(test_channels, test_channels_postgres, test_channels_sqlite);
18
19async fn test_channels(db: &Arc<Database>) {
20 let a_id = db
21 .create_user(
22 "user1@example.com",
23 false,
24 NewUserParams {
25 github_login: "user1".into(),
26 github_user_id: 5,
27 invite_count: 0,
28 },
29 )
30 .await
31 .unwrap()
32 .user_id;
33
34 let b_id = db
35 .create_user(
36 "user2@example.com",
37 false,
38 NewUserParams {
39 github_login: "user2".into(),
40 github_user_id: 6,
41 invite_count: 0,
42 },
43 )
44 .await
45 .unwrap()
46 .user_id;
47
48 let zed_id = db.create_root_channel("zed", "1", a_id).await.unwrap();
49
50 // Make sure that people cannot read channels they haven't been invited to
51 assert!(db.get_channel(zed_id, b_id).await.unwrap().is_none());
52
53 db.invite_channel_member(zed_id, b_id, a_id, false)
54 .await
55 .unwrap();
56
57 db.respond_to_channel_invite(zed_id, b_id, true)
58 .await
59 .unwrap();
60
61 let crdb_id = db
62 .create_channel("crdb", Some(zed_id), "2", a_id)
63 .await
64 .unwrap();
65 let livestreaming_id = db
66 .create_channel("livestreaming", Some(zed_id), "3", a_id)
67 .await
68 .unwrap();
69 let replace_id = db
70 .create_channel("replace", Some(zed_id), "4", a_id)
71 .await
72 .unwrap();
73
74 let mut members = db.get_channel_members(replace_id).await.unwrap();
75 members.sort();
76 assert_eq!(members, &[a_id, b_id]);
77
78 let rust_id = db.create_root_channel("rust", "5", a_id).await.unwrap();
79 let cargo_id = db
80 .create_channel("cargo", Some(rust_id), "6", a_id)
81 .await
82 .unwrap();
83
84 let cargo_ra_id = db
85 .create_channel("cargo-ra", Some(cargo_id), "7", a_id)
86 .await
87 .unwrap();
88
89 let result = db.get_channels_for_user(a_id).await.unwrap();
90 assert_eq!(
91 result.channels,
92 graph(
93 &[
94 (zed_id, "zed"),
95 (crdb_id, "crdb"),
96 (livestreaming_id, "livestreaming"),
97 (replace_id, "replace"),
98 (rust_id, "rust"),
99 (cargo_id, "cargo"),
100 (cargo_ra_id, "cargo-ra")
101 ],
102 &[
103 (crdb_id, zed_id),
104 (livestreaming_id, zed_id),
105 (replace_id, zed_id),
106 (cargo_id, rust_id),
107 (cargo_ra_id, cargo_id),
108 ]
109 )
110 );
111
112 let result = db.get_channels_for_user(b_id).await.unwrap();
113 assert_eq!(
114 result.channels,
115 graph(
116 &[
117 (zed_id, "zed"),
118 (crdb_id, "crdb"),
119 (livestreaming_id, "livestreaming"),
120 (replace_id, "replace")
121 ],
122 &[
123 (crdb_id, zed_id),
124 (livestreaming_id, zed_id),
125 (replace_id, zed_id)
126 ]
127 )
128 );
129
130 // Update member permissions
131 let set_subchannel_admin = db.set_channel_member_admin(crdb_id, a_id, b_id, true).await;
132 assert!(set_subchannel_admin.is_err());
133 let set_channel_admin = db.set_channel_member_admin(zed_id, a_id, b_id, true).await;
134 assert!(set_channel_admin.is_ok());
135
136 let result = db.get_channels_for_user(b_id).await.unwrap();
137 assert_eq!(
138 result.channels,
139 graph(
140 &[
141 (zed_id, "zed"),
142 (crdb_id, "crdb"),
143 (livestreaming_id, "livestreaming"),
144 (replace_id, "replace")
145 ],
146 &[
147 (crdb_id, zed_id),
148 (livestreaming_id, zed_id),
149 (replace_id, zed_id)
150 ]
151 )
152 );
153
154 // Remove a single channel
155 db.delete_channel(crdb_id, a_id).await.unwrap();
156 assert!(db.get_channel(crdb_id, a_id).await.unwrap().is_none());
157
158 // Remove a channel tree
159 let (mut channel_ids, user_ids) = db.delete_channel(rust_id, a_id).await.unwrap();
160 channel_ids.sort();
161 assert_eq!(channel_ids, &[rust_id, cargo_id, cargo_ra_id]);
162 assert_eq!(user_ids, &[a_id]);
163
164 assert!(db.get_channel(rust_id, a_id).await.unwrap().is_none());
165 assert!(db.get_channel(cargo_id, a_id).await.unwrap().is_none());
166 assert!(db.get_channel(cargo_ra_id, a_id).await.unwrap().is_none());
167}
168
169test_both_dbs!(
170 test_joining_channels,
171 test_joining_channels_postgres,
172 test_joining_channels_sqlite
173);
174
175async fn test_joining_channels(db: &Arc<Database>) {
176 let owner_id = db.create_server("test").await.unwrap().0 as u32;
177
178 let user_1 = db
179 .create_user(
180 "user1@example.com",
181 false,
182 NewUserParams {
183 github_login: "user1".into(),
184 github_user_id: 5,
185 invite_count: 0,
186 },
187 )
188 .await
189 .unwrap()
190 .user_id;
191 let user_2 = db
192 .create_user(
193 "user2@example.com",
194 false,
195 NewUserParams {
196 github_login: "user2".into(),
197 github_user_id: 6,
198 invite_count: 0,
199 },
200 )
201 .await
202 .unwrap()
203 .user_id;
204
205 let channel_1 = db
206 .create_root_channel("channel_1", "1", user_1)
207 .await
208 .unwrap();
209 let room_1 = db.room_id_for_channel(channel_1).await.unwrap();
210
211 // can join a room with membership to its channel
212 let joined_room = db
213 .join_room(
214 room_1,
215 user_1,
216 ConnectionId { owner_id, id: 1 },
217 TEST_RELEASE_CHANNEL,
218 )
219 .await
220 .unwrap();
221 assert_eq!(joined_room.room.participants.len(), 1);
222
223 drop(joined_room);
224 // cannot join a room without membership to its channel
225 assert!(db
226 .join_room(
227 room_1,
228 user_2,
229 ConnectionId { owner_id, id: 1 },
230 TEST_RELEASE_CHANNEL
231 )
232 .await
233 .is_err());
234}
235
236test_both_dbs!(
237 test_channel_invites,
238 test_channel_invites_postgres,
239 test_channel_invites_sqlite
240);
241
242async fn test_channel_invites(db: &Arc<Database>) {
243 db.create_server("test").await.unwrap();
244
245 let user_1 = db
246 .create_user(
247 "user1@example.com",
248 false,
249 NewUserParams {
250 github_login: "user1".into(),
251 github_user_id: 5,
252 invite_count: 0,
253 },
254 )
255 .await
256 .unwrap()
257 .user_id;
258 let user_2 = db
259 .create_user(
260 "user2@example.com",
261 false,
262 NewUserParams {
263 github_login: "user2".into(),
264 github_user_id: 6,
265 invite_count: 0,
266 },
267 )
268 .await
269 .unwrap()
270 .user_id;
271
272 let user_3 = db
273 .create_user(
274 "user3@example.com",
275 false,
276 NewUserParams {
277 github_login: "user3".into(),
278 github_user_id: 7,
279 invite_count: 0,
280 },
281 )
282 .await
283 .unwrap()
284 .user_id;
285
286 let channel_1_1 = db
287 .create_root_channel("channel_1", "1", user_1)
288 .await
289 .unwrap();
290
291 let channel_1_2 = db
292 .create_root_channel("channel_2", "2", user_1)
293 .await
294 .unwrap();
295
296 db.invite_channel_member(channel_1_1, user_2, user_1, false)
297 .await
298 .unwrap();
299 db.invite_channel_member(channel_1_2, user_2, user_1, false)
300 .await
301 .unwrap();
302 db.invite_channel_member(channel_1_1, user_3, user_1, true)
303 .await
304 .unwrap();
305
306 let user_2_invites = db
307 .get_channel_invites_for_user(user_2) // -> [channel_1_1, channel_1_2]
308 .await
309 .unwrap()
310 .into_iter()
311 .map(|channel| channel.id)
312 .collect::<Vec<_>>();
313
314 assert_eq!(user_2_invites, &[channel_1_1, channel_1_2]);
315
316 let user_3_invites = db
317 .get_channel_invites_for_user(user_3) // -> [channel_1_1]
318 .await
319 .unwrap()
320 .into_iter()
321 .map(|channel| channel.id)
322 .collect::<Vec<_>>();
323
324 assert_eq!(user_3_invites, &[channel_1_1]);
325
326 let members = db
327 .get_channel_member_details(channel_1_1, user_1)
328 .await
329 .unwrap();
330 assert_eq!(
331 members,
332 &[
333 proto::ChannelMember {
334 user_id: user_1.to_proto(),
335 kind: proto::channel_member::Kind::Member.into(),
336 admin: true,
337 },
338 proto::ChannelMember {
339 user_id: user_2.to_proto(),
340 kind: proto::channel_member::Kind::Invitee.into(),
341 admin: false,
342 },
343 proto::ChannelMember {
344 user_id: user_3.to_proto(),
345 kind: proto::channel_member::Kind::Invitee.into(),
346 admin: true,
347 },
348 ]
349 );
350
351 db.respond_to_channel_invite(channel_1_1, user_2, true)
352 .await
353 .unwrap();
354
355 let channel_1_3 = db
356 .create_channel("channel_3", Some(channel_1_1), "1", user_1)
357 .await
358 .unwrap();
359
360 let members = db
361 .get_channel_member_details(channel_1_3, user_1)
362 .await
363 .unwrap();
364 assert_eq!(
365 members,
366 &[
367 proto::ChannelMember {
368 user_id: user_1.to_proto(),
369 kind: proto::channel_member::Kind::Member.into(),
370 admin: true,
371 },
372 proto::ChannelMember {
373 user_id: user_2.to_proto(),
374 kind: proto::channel_member::Kind::AncestorMember.into(),
375 admin: false,
376 },
377 ]
378 );
379}
380
381test_both_dbs!(
382 test_channel_renames,
383 test_channel_renames_postgres,
384 test_channel_renames_sqlite
385);
386
387async fn test_channel_renames(db: &Arc<Database>) {
388 db.create_server("test").await.unwrap();
389
390 let user_1 = db
391 .create_user(
392 "user1@example.com",
393 false,
394 NewUserParams {
395 github_login: "user1".into(),
396 github_user_id: 5,
397 invite_count: 0,
398 },
399 )
400 .await
401 .unwrap()
402 .user_id;
403
404 let user_2 = db
405 .create_user(
406 "user2@example.com",
407 false,
408 NewUserParams {
409 github_login: "user2".into(),
410 github_user_id: 6,
411 invite_count: 0,
412 },
413 )
414 .await
415 .unwrap()
416 .user_id;
417
418 let zed_id = db.create_root_channel("zed", "1", user_1).await.unwrap();
419
420 db.rename_channel(zed_id, user_1, "#zed-archive")
421 .await
422 .unwrap();
423
424 let zed_archive_id = zed_id;
425
426 let (channel, _) = db
427 .get_channel(zed_archive_id, user_1)
428 .await
429 .unwrap()
430 .unwrap();
431 assert_eq!(channel.name, "zed-archive");
432
433 let non_permissioned_rename = db
434 .rename_channel(zed_archive_id, user_2, "hacked-lol")
435 .await;
436 assert!(non_permissioned_rename.is_err());
437
438 let bad_name_rename = db.rename_channel(zed_id, user_1, "#").await;
439 assert!(bad_name_rename.is_err())
440}
441
442test_both_dbs!(
443 test_db_channel_moving,
444 test_channels_moving_postgres,
445 test_channels_moving_sqlite
446);
447
448async fn test_db_channel_moving(db: &Arc<Database>) {
449 let a_id = db
450 .create_user(
451 "user1@example.com",
452 false,
453 NewUserParams {
454 github_login: "user1".into(),
455 github_user_id: 5,
456 invite_count: 0,
457 },
458 )
459 .await
460 .unwrap()
461 .user_id;
462
463 let zed_id = db.create_root_channel("zed", "1", a_id).await.unwrap();
464
465 let crdb_id = db
466 .create_channel("crdb", Some(zed_id), "2", a_id)
467 .await
468 .unwrap();
469
470 let gpui2_id = db
471 .create_channel("gpui2", Some(zed_id), "3", a_id)
472 .await
473 .unwrap();
474
475 let livestreaming_id = db
476 .create_channel("livestreaming", Some(crdb_id), "4", a_id)
477 .await
478 .unwrap();
479
480 let livestreaming_dag_id = db
481 .create_channel("livestreaming_dag", Some(livestreaming_id), "5", a_id)
482 .await
483 .unwrap();
484
485 // ========================================================================
486 // sanity check
487 // Initial DAG:
488 // /- gpui2
489 // zed -- crdb - livestreaming - livestreaming_dag
490 let result = db.get_channels_for_user(a_id).await.unwrap();
491 assert_dag(
492 result.channels,
493 &[
494 (zed_id, None),
495 (crdb_id, Some(zed_id)),
496 (gpui2_id, Some(zed_id)),
497 (livestreaming_id, Some(crdb_id)),
498 (livestreaming_dag_id, Some(livestreaming_id)),
499 ],
500 );
501
502 // Attempt to make a cycle
503 assert!(db
504 .link_channel(a_id, zed_id, livestreaming_id)
505 .await
506 .is_err());
507
508 // ========================================================================
509 // Make a link
510 db.link_channel(a_id, livestreaming_id, zed_id)
511 .await
512 .unwrap();
513
514 // DAG is now:
515 // /- gpui2
516 // zed -- crdb - livestreaming - livestreaming_dag
517 // \---------/
518 let result = db.get_channels_for_user(a_id).await.unwrap();
519 assert_dag(
520 result.channels,
521 &[
522 (zed_id, None),
523 (crdb_id, Some(zed_id)),
524 (gpui2_id, Some(zed_id)),
525 (livestreaming_id, Some(zed_id)),
526 (livestreaming_id, Some(crdb_id)),
527 (livestreaming_dag_id, Some(livestreaming_id)),
528 ],
529 );
530
531 // ========================================================================
532 // Create a new channel below a channel with multiple parents
533 let livestreaming_dag_sub_id = db
534 .create_channel(
535 "livestreaming_dag_sub",
536 Some(livestreaming_dag_id),
537 "6",
538 a_id,
539 )
540 .await
541 .unwrap();
542
543 // DAG is now:
544 // /- gpui2
545 // zed -- crdb - livestreaming - livestreaming_dag - livestreaming_dag_sub_id
546 // \---------/
547 let result = db.get_channels_for_user(a_id).await.unwrap();
548 assert_dag(
549 result.channels,
550 &[
551 (zed_id, None),
552 (crdb_id, Some(zed_id)),
553 (gpui2_id, Some(zed_id)),
554 (livestreaming_id, Some(zed_id)),
555 (livestreaming_id, Some(crdb_id)),
556 (livestreaming_dag_id, Some(livestreaming_id)),
557 (livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
558 ],
559 );
560
561 // ========================================================================
562 // Test a complex DAG by making another link
563 let returned_channels = db
564 .link_channel(a_id, livestreaming_dag_sub_id, livestreaming_id)
565 .await
566 .unwrap();
567
568 // DAG is now:
569 // /- gpui2 /---------------------\
570 // zed - crdb - livestreaming - livestreaming_dag - livestreaming_dag_sub_id
571 // \--------/
572
573 // make sure we're getting just the new link
574 // Not using the assert_dag helper because we want to make sure we're returning the full data
575 pretty_assertions::assert_eq!(
576 returned_channels,
577 graph(
578 &[(livestreaming_dag_sub_id, "livestreaming_dag_sub")],
579 &[(livestreaming_dag_sub_id, livestreaming_id)]
580 )
581 );
582
583 let result = db.get_channels_for_user(a_id).await.unwrap();
584 assert_dag(
585 result.channels,
586 &[
587 (zed_id, None),
588 (crdb_id, Some(zed_id)),
589 (gpui2_id, Some(zed_id)),
590 (livestreaming_id, Some(zed_id)),
591 (livestreaming_id, Some(crdb_id)),
592 (livestreaming_dag_id, Some(livestreaming_id)),
593 (livestreaming_dag_sub_id, Some(livestreaming_id)),
594 (livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
595 ],
596 );
597
598 // ========================================================================
599 // Test a complex DAG by making another link
600 let returned_channels = db
601 .link_channel(a_id, livestreaming_id, gpui2_id)
602 .await
603 .unwrap();
604
605 // DAG is now:
606 // /- gpui2 -\ /---------------------\
607 // zed - crdb -- livestreaming - livestreaming_dag - livestreaming_dag_sub_id
608 // \---------/
609
610 // Make sure that we're correctly getting the full sub-dag
611 pretty_assertions::assert_eq!(
612 returned_channels,
613 graph(
614 &[
615 (livestreaming_id, "livestreaming"),
616 (livestreaming_dag_id, "livestreaming_dag"),
617 (livestreaming_dag_sub_id, "livestreaming_dag_sub"),
618 ],
619 &[
620 (livestreaming_id, gpui2_id),
621 (livestreaming_dag_id, livestreaming_id),
622 (livestreaming_dag_sub_id, livestreaming_id),
623 (livestreaming_dag_sub_id, livestreaming_dag_id),
624 ]
625 )
626 );
627
628 let result = db.get_channels_for_user(a_id).await.unwrap();
629 assert_dag(
630 result.channels,
631 &[
632 (zed_id, None),
633 (crdb_id, Some(zed_id)),
634 (gpui2_id, Some(zed_id)),
635 (livestreaming_id, Some(zed_id)),
636 (livestreaming_id, Some(crdb_id)),
637 (livestreaming_id, Some(gpui2_id)),
638 (livestreaming_dag_id, Some(livestreaming_id)),
639 (livestreaming_dag_sub_id, Some(livestreaming_id)),
640 (livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
641 ],
642 );
643
644 // ========================================================================
645 // Test unlinking in a complex DAG by removing the inner link
646 db.unlink_channel(a_id, livestreaming_dag_sub_id, livestreaming_id)
647 .await
648 .unwrap();
649
650 // DAG is now:
651 // /- gpui2 -\
652 // zed - crdb -- livestreaming - livestreaming_dag - livestreaming_dag_sub
653 // \---------/
654
655 let result = db.get_channels_for_user(a_id).await.unwrap();
656 assert_dag(
657 result.channels,
658 &[
659 (zed_id, None),
660 (crdb_id, Some(zed_id)),
661 (gpui2_id, Some(zed_id)),
662 (livestreaming_id, Some(gpui2_id)),
663 (livestreaming_id, Some(zed_id)),
664 (livestreaming_id, Some(crdb_id)),
665 (livestreaming_dag_id, Some(livestreaming_id)),
666 (livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
667 ],
668 );
669
670 // ========================================================================
671 // Test unlinking in a complex DAG by removing the inner link
672 db.unlink_channel(a_id, livestreaming_id, gpui2_id)
673 .await
674 .unwrap();
675
676 // DAG is now:
677 // /- gpui2
678 // zed - crdb -- livestreaming - livestreaming_dag - livestreaming_dag_sub
679 // \---------/
680 let result = db.get_channels_for_user(a_id).await.unwrap();
681 assert_dag(
682 result.channels,
683 &[
684 (zed_id, None),
685 (crdb_id, Some(zed_id)),
686 (gpui2_id, Some(zed_id)),
687 (livestreaming_id, Some(zed_id)),
688 (livestreaming_id, Some(crdb_id)),
689 (livestreaming_dag_id, Some(livestreaming_id)),
690 (livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
691 ],
692 );
693
694 // ========================================================================
695 // Test moving DAG nodes by moving livestreaming to be below gpui2
696 db.move_channel(a_id, livestreaming_id, crdb_id, gpui2_id)
697 .await
698 .unwrap();
699
700 // DAG is now:
701 // /- gpui2 -- livestreaming - livestreaming_dag - livestreaming_dag_sub
702 // zed - crdb /
703 // \---------/
704 let result = db.get_channels_for_user(a_id).await.unwrap();
705 assert_dag(
706 result.channels,
707 &[
708 (zed_id, None),
709 (crdb_id, Some(zed_id)),
710 (gpui2_id, Some(zed_id)),
711 (livestreaming_id, Some(zed_id)),
712 (livestreaming_id, Some(gpui2_id)),
713 (livestreaming_dag_id, Some(livestreaming_id)),
714 (livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
715 ],
716 );
717
718 // ========================================================================
719 // Deleting a channel should not delete children that still have other parents
720 db.delete_channel(gpui2_id, a_id).await.unwrap();
721
722 // DAG is now:
723 // zed - crdb
724 // \- livestreaming - livestreaming_dag - livestreaming_dag_sub
725 let result = db.get_channels_for_user(a_id).await.unwrap();
726 assert_dag(
727 result.channels,
728 &[
729 (zed_id, None),
730 (crdb_id, Some(zed_id)),
731 (livestreaming_id, Some(zed_id)),
732 (livestreaming_dag_id, Some(livestreaming_id)),
733 (livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
734 ],
735 );
736
737 // ========================================================================
738 // Unlinking a channel from it's parent should automatically promote it to a root channel
739 db.unlink_channel(a_id, crdb_id, zed_id).await.unwrap();
740
741 // DAG is now:
742 // crdb
743 // zed
744 // \- livestreaming - livestreaming_dag - livestreaming_dag_sub
745
746 let result = db.get_channels_for_user(a_id).await.unwrap();
747 assert_dag(
748 result.channels,
749 &[
750 (zed_id, None),
751 (crdb_id, None),
752 (livestreaming_id, Some(zed_id)),
753 (livestreaming_dag_id, Some(livestreaming_id)),
754 (livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
755 ],
756 );
757
758 // ========================================================================
759 // You should be able to move a root channel into a non-root channel
760 db.link_channel(a_id, crdb_id, zed_id).await.unwrap();
761
762 // DAG is now:
763 // zed - crdb
764 // \- livestreaming - livestreaming_dag - livestreaming_dag_sub
765
766 let result = db.get_channels_for_user(a_id).await.unwrap();
767 assert_dag(
768 result.channels,
769 &[
770 (zed_id, None),
771 (crdb_id, Some(zed_id)),
772 (livestreaming_id, Some(zed_id)),
773 (livestreaming_dag_id, Some(livestreaming_id)),
774 (livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
775 ],
776 );
777
778 // ========================================================================
779 // Prep for DAG deletion test
780 db.link_channel(a_id, livestreaming_id, crdb_id)
781 .await
782 .unwrap();
783
784 // DAG is now:
785 // zed - crdb - livestreaming - livestreaming_dag - livestreaming_dag_sub
786 // \--------/
787
788 let result = db.get_channels_for_user(a_id).await.unwrap();
789 assert_dag(
790 result.channels,
791 &[
792 (zed_id, None),
793 (crdb_id, Some(zed_id)),
794 (livestreaming_id, Some(zed_id)),
795 (livestreaming_id, Some(crdb_id)),
796 (livestreaming_dag_id, Some(livestreaming_id)),
797 (livestreaming_dag_sub_id, Some(livestreaming_dag_id)),
798 ],
799 );
800
801 // Deleting the parent of a DAG should delete the whole DAG:
802 db.delete_channel(zed_id, a_id).await.unwrap();
803 let result = db.get_channels_for_user(a_id).await.unwrap();
804
805 assert!(result.channels.is_empty())
806}
807
808test_both_dbs!(
809 test_db_channel_moving_bugs,
810 test_db_channel_moving_bugs_postgres,
811 test_db_channel_moving_bugs_sqlite
812);
813
814async fn test_db_channel_moving_bugs(db: &Arc<Database>) {
815 let user_id = db
816 .create_user(
817 "user1@example.com",
818 false,
819 NewUserParams {
820 github_login: "user1".into(),
821 github_user_id: 5,
822 invite_count: 0,
823 },
824 )
825 .await
826 .unwrap()
827 .user_id;
828
829 let zed_id = db.create_root_channel("zed", "1", user_id).await.unwrap();
830
831 let projects_id = db
832 .create_channel("projects", Some(zed_id), "2", user_id)
833 .await
834 .unwrap();
835
836 let livestreaming_id = db
837 .create_channel("livestreaming", Some(projects_id), "3", user_id)
838 .await
839 .unwrap();
840
841 // Dag is: zed - projects - livestreaming
842
843 // Move to same parent should be a no-op
844 assert!(db
845 .move_channel(user_id, projects_id, zed_id, zed_id)
846 .await
847 .unwrap()
848 .is_empty());
849
850 // Stranding a channel should retain it's sub channels
851 db.unlink_channel(user_id, projects_id, zed_id)
852 .await
853 .unwrap();
854
855 let result = db.get_channels_for_user(user_id).await.unwrap();
856 assert_dag(
857 result.channels,
858 &[
859 (zed_id, None),
860 (projects_id, None),
861 (livestreaming_id, Some(projects_id)),
862 ],
863 );
864}
865
866#[track_caller]
867fn assert_dag(actual: ChannelGraph, expected: &[(ChannelId, Option<ChannelId>)]) {
868 let mut actual_map: HashMap<ChannelId, HashSet<ChannelId>> = HashMap::default();
869 for channel in actual.channels {
870 actual_map.insert(channel.id, HashSet::default());
871 }
872 for edge in actual.edges {
873 actual_map
874 .get_mut(&ChannelId::from_proto(edge.channel_id))
875 .unwrap()
876 .insert(ChannelId::from_proto(edge.parent_id));
877 }
878
879 let mut expected_map: HashMap<ChannelId, HashSet<ChannelId>> = HashMap::default();
880
881 for (child, parent) in expected {
882 let entry = expected_map.entry(*child).or_default();
883 if let Some(parent) = parent {
884 entry.insert(*parent);
885 }
886 }
887
888 pretty_assertions::assert_eq!(actual_map, expected_map)
889}