@@ -66,6 +66,26 @@ mod e2e {
6666 res
6767 }
6868
69+ /// Read all lines from `child_stdout` until EOF and return them joined by newlines.
70+ /// Also writes every read line to tokio::io::stdout();
71+ async fn read_all_stdout ( child_stdout : & mut tokio:: process:: ChildStdout ) -> String {
72+ let reader = BufReader :: new ( child_stdout) ;
73+ let mut lines = reader. lines ( ) ;
74+ let mut out = String :: new ( ) ;
75+
76+ let mut stdout = tokio:: io:: stdout ( ) ;
77+ while let Some ( line) = lines. next_line ( ) . await . expect ( "Failed to read line from stdout" ) {
78+ stdout
79+ . write_all ( format ! ( "{line}\n " ) . as_bytes ( ) )
80+ . await
81+ . expect ( "Failed to write to stdout" ) ;
82+ out. push_str ( & line) ;
83+ out. push ( '\n' ) ;
84+ }
85+
86+ out
87+ }
88+
6989 async fn send_until_request_timeout ( mut cli_sender : Child ) -> Result < ( ) , BoxError > {
7090 let mut stdout = cli_sender. stdout . take ( ) . expect ( "failed to take stdout of child process" ) ;
7191 let timeout = tokio:: time:: Duration :: from_secs ( 35 ) ;
@@ -783,6 +803,49 @@ mod e2e {
783803 "fallback tx should be in the mempool after cancel"
784804 ) ;
785805
806+ // Re-run `cancel` on the now-closed session: the command must still
807+ // recognize the session exists and report it is already closed.
808+ let mut cli_cancel_again = Command :: new ( payjoin_cli)
809+ . arg ( "--root-certificate" )
810+ . arg ( cert_path)
811+ . arg ( "--rpchost" )
812+ . arg ( & sender_rpchost)
813+ . arg ( "--cookie-file" )
814+ . arg ( cookie_file)
815+ . arg ( "--db-path" )
816+ . arg ( & sender_db_path)
817+ . arg ( "--ohttp-relays" )
818+ . arg ( ohttp_relays)
819+ . arg ( "cancel" )
820+ . arg ( session_id. to_string ( ) )
821+ . stdout ( Stdio :: piped ( ) )
822+ . stderr ( Stdio :: inherit ( ) )
823+ . spawn ( )
824+ . expect ( "Failed to execute payjoin-cli cancel on closed session" ) ;
825+
826+ let raw_tx = sender. get_transaction ( fallback_txid) ?. into_model ( ) ?. tx ;
827+ let expected_hex = payjoin:: bitcoin:: consensus:: encode:: serialize_hex ( & raw_tx) ;
828+
829+ let mut cancel_again_stdout = cli_cancel_again
830+ . stdout
831+ . take ( )
832+ . expect ( "failed to take stdout of cancel on closed session" ) ;
833+ let cancel_again_output =
834+ tokio:: time:: timeout ( timeout, read_all_stdout ( & mut cancel_again_stdout) ) . await ?;
835+ terminate ( cli_cancel_again)
836+ . await
837+ . expect ( "Failed to kill payjoin-cli cancel on closed session" ) ;
838+
839+ assert ! (
840+ cancel_again_output
841+ . contains( & format!( "Session {session_id} was already cancelled" ) ) ,
842+ "cancel on closed session should reference the session id and report it is already closed; got: {cancel_again_output}"
843+ ) ;
844+ assert ! (
845+ cancel_again_output. contains( & expected_hex) ,
846+ "cancel on closed session should print the fallback transaction consensus hex; got: {cancel_again_output}"
847+ ) ;
848+
786849 Ok ( ( ) )
787850 }
788851
@@ -868,8 +931,6 @@ mod e2e {
868931 . arg ( ohttp_relays)
869932 . arg ( "cancel" )
870933 . arg ( session_id. to_string ( ) )
871- . arg ( "--role" )
872- . arg ( "receiver" )
873934 . stdout ( Stdio :: piped ( ) )
874935 . stderr ( Stdio :: inherit ( ) )
875936 . spawn ( )
@@ -894,6 +955,48 @@ mod e2e {
894955 "cancel should reference the cancelled session id"
895956 ) ;
896957
958+ // Re-run `cancel` on the now-closed session: the command must still
959+ // recognize the session exists and report it is already closed.
960+ let mut cli_cancel_again = Command :: new ( payjoin_cli)
961+ . arg ( "--root-certificate" )
962+ . arg ( cert_path)
963+ . arg ( "--rpchost" )
964+ . arg ( & receiver_rpchost)
965+ . arg ( "--cookie-file" )
966+ . arg ( cookie_file)
967+ . arg ( "--db-path" )
968+ . arg ( & receiver_db_path)
969+ . arg ( "--ohttp-relays" )
970+ . arg ( ohttp_relays)
971+ . arg ( "cancel" )
972+ . arg ( session_id. to_string ( ) )
973+ . stdout ( Stdio :: piped ( ) )
974+ . stderr ( Stdio :: inherit ( ) )
975+ . spawn ( )
976+ . expect ( "Failed to execute payjoin-cli cancel on closed session" ) ;
977+
978+ let mut cancel_again_stdout = cli_cancel_again
979+ . stdout
980+ . take ( )
981+ . expect ( "failed to take stdout of cancel on closed session" ) ;
982+ let cancel_again_line = tokio:: time:: timeout (
983+ timeout,
984+ wait_for_stdout_match ( & mut cancel_again_stdout, |l| {
985+ l. contains ( "is already closed" )
986+ } ) ,
987+ )
988+ . await ?;
989+ terminate ( cli_cancel_again)
990+ . await
991+ . expect ( "Failed to kill payjoin-cli cancel on closed session" ) ;
992+ let cancel_again_output = cancel_again_line
993+ . expect ( "cancel on closed session should report it is already closed" ) ;
994+
995+ assert ! (
996+ cancel_again_output. contains( & format!( "Session {session_id} is already closed" ) ) ,
997+ "cancel on closed session should reference the session id and report it is already closed"
998+ ) ;
999+
8971000 Ok ( ( ) )
8981001 }
8991002
0 commit comments