channel_tests.rs

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