channel_tests.rs

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