@@ -625,6 +625,197 @@ mod tests {
625625 Ok ( ( ) )
626626 }
627627
628+ #[ actix_web:: test]
629+ async fn test_concurrent_downloads ( ) -> Result < ( ) > {
630+ use std:: rc:: Rc ;
631+
632+ // Initialize the app
633+ let path = TmpDir :: new ( "test_concurrent_downloads" ) . await ?;
634+
635+ BACKEND . get_or_init ( || init_backend ( path. to_path_buf ( ) . as_path ( ) ) ) ;
636+ {
637+ let backend = get_backend ( ) . await ?;
638+ backend. start ( ) . await . expect ( "Backend failed to start" ) ;
639+ }
640+
641+ let app = Rc :: new (
642+ test:: init_service (
643+ App :: new ( )
644+ . service ( status)
645+ . service ( web:: scope ( "/api" ) . service ( groups:: scope ( ) ) ) ,
646+ )
647+ . await
648+ ) ;
649+
650+ // Step 1: Create a group
651+ let create_group_req = test:: TestRequest :: post ( )
652+ . uri ( "/api/groups" )
653+ . set_json ( json ! ( { "name" : "Test Group" } ) )
654+ . to_request ( ) ;
655+ let create_group_resp: serde_json:: Value =
656+ test:: call_and_read_body_json ( & app, create_group_req) . await ;
657+ let group_id = create_group_resp[ "key" ]
658+ . as_str ( )
659+ . expect ( "No group key returned" ) ;
660+
661+ // Step 2: Create a repo
662+ let create_repo_req = test:: TestRequest :: post ( )
663+ . uri ( & format ! ( "/api/groups/{}/repos" , group_id) )
664+ . set_json ( json ! ( { "name" : "Test Repo" } ) )
665+ . to_request ( ) ;
666+ let create_repo_resp: serde_json:: Value =
667+ test:: call_and_read_body_json ( & app, create_repo_req) . await ;
668+ let repo_id = create_repo_resp[ "key" ]
669+ . as_str ( )
670+ . expect ( "No repo key returned" ) ;
671+
672+ // Step 3: Upload multiple files
673+ let file_contents = vec ! [
674+ ( "file1.txt" , b"Content for file 1" ) ,
675+ ( "file2.txt" , b"Content for file 2" ) ,
676+ ( "file3.txt" , b"Content for file 3" ) ,
677+ ] ;
678+
679+ for ( file_name, content) in & file_contents {
680+ let upload_req = test:: TestRequest :: post ( )
681+ . uri ( & format ! (
682+ "/api/groups/{}/repos/{}/media/{}" ,
683+ group_id, repo_id, file_name
684+ ) )
685+ . set_payload ( content. to_vec ( ) )
686+ . to_request ( ) ;
687+ let upload_resp = test:: call_service ( & app, upload_req) . await ;
688+ assert ! ( upload_resp. status( ) . is_success( ) , "File upload failed for {}" , file_name) ;
689+ }
690+
691+ // Step 4: Create download requests
692+ let mut download_futures = Vec :: new ( ) ;
693+
694+ for ( file_name, content) in & file_contents {
695+ let get_file_req = test:: TestRequest :: get ( )
696+ . uri ( & format ! (
697+ "/api/groups/{}/repos/{}/media/{}" ,
698+ group_id, repo_id, file_name
699+ ) )
700+ . to_request ( ) ;
701+
702+ let content = content. to_vec ( ) ;
703+ let file_name = file_name. to_string ( ) ;
704+ let app = Rc :: clone ( & app) ;
705+
706+ // Create a future for each download
707+ let download_future = async move {
708+ let resp = test:: call_service ( & app, get_file_req) . await ;
709+ assert ! ( resp. status( ) . is_success( ) , "File download failed for {}" , file_name) ;
710+
711+ let got_file_data = test:: read_body ( resp) . await ;
712+ assert_eq ! (
713+ got_file_data. to_vec( ) . as_slice( ) ,
714+ content. as_slice( ) ,
715+ "Downloaded content mismatch for {}" ,
716+ file_name
717+ ) ;
718+ } ;
719+
720+ download_futures. push ( download_future) ;
721+ }
722+
723+ // Execute all downloads concurrently
724+ futures:: future:: join_all ( download_futures) . await ;
725+
726+ // Clean up: Stop the backend
727+ {
728+ let backend = get_backend ( ) . await ?;
729+ backend. stop ( ) . await . expect ( "Backend failed to stop" ) ;
730+ }
731+
732+ Ok ( ( ) )
733+ }
734+
735+ #[ actix_web:: test]
736+ async fn test_repo_permissions ( ) -> Result < ( ) > {
737+ // Initialize the app
738+ let path = TmpDir :: new ( "test_repo_permissions" ) . await ?;
739+
740+ // Initialize backend2 first (this will be the creator of the group/repo)
741+ let store2 = iroh_blobs:: store:: fs:: Store :: load ( path. to_path_buf ( ) . join ( "iroh2" ) ) . await ?;
742+ let ( veilid_api2, update_rx2) = save_dweb_backend:: common:: init_veilid (
743+ path. to_path_buf ( ) . join ( "test2" ) . as_path ( ) ,
744+ "test2" . to_string ( ) ,
745+ )
746+ . await ?;
747+ let backend2 = save_dweb_backend:: backend:: Backend :: from_dependencies (
748+ & path. to_path_buf ( ) ,
749+ veilid_api2. clone ( ) ,
750+ update_rx2,
751+ store2,
752+ )
753+ . await
754+ . unwrap ( ) ;
755+
756+ // Initialize the main backend (this will join the group)
757+ BACKEND . get_or_init ( || init_backend ( path. to_path_buf ( ) . as_path ( ) ) ) ;
758+ {
759+ let backend = get_backend ( ) . await ?;
760+ backend. start ( ) . await . expect ( "Backend failed to start" ) ;
761+ }
762+
763+ // Create group and repo in backend2 (creator)
764+ let mut group = backend2. create_group ( ) . await ?;
765+ let join_url = group. get_url ( ) ;
766+ group. set_name ( TEST_GROUP_NAME ) . await ?;
767+
768+ let repo = group. create_repo ( ) . await ?;
769+ repo. set_name ( TEST_GROUP_NAME ) . await ?;
770+
771+ // Verify creator has write access
772+ let creator_repo: SnowbirdRepo = repo. clone ( ) . into ( ) ;
773+ assert ! ( creator_repo. can_write, "Creator should have write access" ) ;
774+
775+ // Join the group with the main backend
776+ {
777+ let backend = get_backend ( ) . await ?;
778+ backend. join_from_url ( join_url. as_str ( ) ) . await ?;
779+ }
780+
781+ let app = test:: init_service (
782+ App :: new ( )
783+ . service ( status)
784+ . service ( web:: scope ( "/api" ) . service ( groups:: scope ( ) ) ) ,
785+ )
786+ . await ;
787+
788+ // Get the repo info through the API for the joined backend
789+ let get_repo_req = test:: TestRequest :: get ( )
790+ . uri ( & format ! (
791+ "/api/groups/{}/repos/{}" ,
792+ group. id( ) . to_string( ) ,
793+ repo. id( ) . to_string( )
794+ ) )
795+ . to_request ( ) ;
796+ let joined_repo: SnowbirdRepo = test:: call_and_read_body_json ( & app, get_repo_req) . await ;
797+
798+ // Verify joined backend has read-only access
799+ assert ! ( !joined_repo. can_write, "Joined backend should have read-only access" ) ;
800+
801+ // List repos to verify permissions are consistent
802+ let list_repos_req = test:: TestRequest :: get ( )
803+ . uri ( & format ! ( "/api/groups/{}/repos" , group. id( ) . to_string( ) ) )
804+ . to_request ( ) ;
805+ let repos_response: ReposResponse = test:: call_and_read_body_json ( & app, list_repos_req) . await ;
806+
807+ assert_eq ! ( repos_response. repos. len( ) , 1 , "Should see one repo" ) ;
808+ assert ! ( !repos_response. repos[ 0 ] . can_write, "Listed repo should show read-only access" ) ;
809+
810+ // Clean up
811+ backend2. stop ( ) . await ?;
812+ {
813+ let backend = get_backend ( ) . await ?;
814+ backend. stop ( ) . await . expect ( "Backend failed to stop" ) ;
815+ }
816+ tokio:: time:: sleep ( Duration :: from_secs ( 2 ) ) . await ;
817+ veilid_api2. shutdown ( ) . await ;
818+
628819 Ok ( ( ) )
629820 }
630821}
0 commit comments