@@ -666,6 +666,50 @@ async fn write_finalization_failure_emits_no_success_or_load() {
666666 assert_eq ! ( aborts. load( std:: sync:: atomic:: Ordering :: SeqCst ) , 1 ) ;
667667}
668668
669+ #[ tokio:: test]
670+ async fn failed_write_cleans ( ) {
671+ let context = setup_blob_handle ( 1 ) . await ;
672+ let root = tempdir ( ) . unwrap ( ) ;
673+ let location = BackendLocation {
674+ root : root. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ,
675+ storage_bucket : "bucket" . to_string ( ) ,
676+ backend_path : "partial.bin" . to_string ( ) ,
677+ ulid : Ulid :: new ( ) ,
678+ compressed : false ,
679+ encrypted : false ,
680+ created_by : test_user_id ( ) ,
681+ created_at : SystemTime :: now ( ) ,
682+ staging : false ,
683+ partial : false ,
684+ blob_size : 0 ,
685+ hashes : HashMap :: new ( ) ,
686+ } ;
687+ let operator = crate :: opendal:: init_operator (
688+ Backend :: FileSystem ,
689+ HashMap :: from ( [ (
690+ "root" . to_string ( ) ,
691+ root. path ( ) . to_str ( ) . unwrap ( ) . to_string ( ) ,
692+ ) ] ) ,
693+ )
694+ . unwrap ( ) ;
695+ let blob = BackendStream :: new ( futures:: stream:: iter ( [
696+ Ok ( bytes:: Bytes :: from_static ( b"partial" ) ) ,
697+ Err ( std:: io:: Error :: other ( "injected stream failure" ) ) ,
698+ ] ) ) ;
699+
700+ let event = context
701+ . blob_handle
702+ . handler
703+ . write_stream_to_location ( location. clone ( ) , operator, blob)
704+ . await ;
705+
706+ assert ! ( matches!( event, BlobEvent :: Error ( BlobError :: WriteError ( _) ) ) ) ;
707+ assert ! (
708+ !std:: path:: Path :: new( & location. get_full_path( ) . unwrap( ) ) . exists( ) ,
709+ "partial filesystem target remains"
710+ ) ;
711+ }
712+
669713#[ tokio:: test]
670714async fn compose_close_fails ( ) {
671715 let context = setup_blob_handle ( 5 ) . await ;
0 commit comments