@@ -484,7 +484,9 @@ pub(crate) mod tests {
484484 witness, Amount , PubkeyHash , ScriptBuf , ScriptHash , Sequence , Txid , WScriptHash ,
485485 XOnlyPublicKey ,
486486 } ;
487- use payjoin_test_utils:: { DUMMY20 , DUMMY32 , PARSED_ORIGINAL_PSBT , QUERY_PARAMS } ;
487+ use payjoin_test_utils:: {
488+ DUMMY20 , DUMMY32 , PARSED_ORIGINAL_PSBT , PARSED_PAYJOIN_PROPOSAL , QUERY_PARAMS ,
489+ } ;
488490
489491 use super :: * ;
490492 use crate :: psbt:: InternalPsbtInputError :: InvalidScriptPubKey ;
@@ -496,6 +498,24 @@ pub(crate) mod tests {
496498 OriginalPayload { psbt : PARSED_ORIGINAL_PSBT . clone ( ) , params }
497499 }
498500
501+ pub ( crate ) fn original_missing_prevtxout_from_test_vector ( ) -> OriginalPayload {
502+ let params = Params :: from_query_str ( QUERY_PARAMS , & [ Version :: One ] )
503+ . expect ( "Could not parse params from query str" ) ;
504+ let mut psbt: Psbt = PARSED_ORIGINAL_PSBT . clone ( ) ;
505+ for psbtin in psbt. inputs_mut ( ) {
506+ psbtin. non_witness_utxo = None ;
507+ psbtin. witness_utxo = None ;
508+ }
509+ OriginalPayload { psbt : psbt. clone ( ) , params }
510+ }
511+
512+ pub ( crate ) fn psbt_context_from_test_vector ( ) -> PsbtContext {
513+ PsbtContext {
514+ payjoin_psbt : PARSED_PAYJOIN_PROPOSAL . clone ( ) ,
515+ original_psbt : PARSED_ORIGINAL_PSBT . clone ( ) ,
516+ }
517+ }
518+
499519 #[ test]
500520 fn input_pair_with_expected_weight ( ) {
501521 let p2wsh_txout = TxOut {
@@ -830,6 +850,141 @@ pub(crate) mod tests {
830850 assert_eq ! ( err, PsbtInputError :: from( InternalPsbtInputError :: ProvidedUnnecessaryWeight ) ) ;
831851 }
832852
853+ #[ test]
854+ fn test_check_broadcast_suitability ( ) {
855+ let original = original_from_test_vector ( ) ;
856+
857+ // Outcome 1: min_fee_rate too high → PsbtBelowFeeRate error
858+ let err = original
859+ . clone ( )
860+ . check_broadcast_suitability ( Some ( FeeRate :: MAX ) , |_| Ok ( true ) )
861+ . expect_err ( "Should fail when fee rate is below minimum" ) ;
862+ match err {
863+ Error :: Protocol ( ProtocolError :: OriginalPayload ( PayloadError (
864+ InternalPayloadError :: PsbtBelowFeeRate ( original_fee_rate, min_fee_rate) ,
865+ ) ) ) => {
866+ assert_eq ! ( original_fee_rate, original. psbt_fee_rate( ) . unwrap( ) ) ;
867+ assert_eq ! ( min_fee_rate, FeeRate :: MAX ) ;
868+ }
869+ _ => panic ! ( "Expected PsbtBelowFeeRate error, got: {err:?}" ) ,
870+ }
871+
872+ // Outcome 2: can_broadcast returns false → OriginalPsbtNotBroadcastable error
873+ let err = original
874+ . clone ( )
875+ . check_broadcast_suitability ( None , |_| Ok ( false ) )
876+ . expect_err ( "Should fail when can_broadcast returns false" ) ;
877+ match err {
878+ Error :: Protocol ( ProtocolError :: OriginalPayload ( PayloadError (
879+ InternalPayloadError :: OriginalPsbtNotBroadcastable ,
880+ ) ) ) => { }
881+ _ => panic ! ( "Expected OriginalPsbtNotBroadcastable error, got: {err:?}" ) ,
882+ }
883+
884+ // Outcome 3: can_broadcast returns an implementation error → Error::Implementation
885+ let err = original
886+ . clone ( )
887+ . check_broadcast_suitability ( None , |_| {
888+ Err ( ImplementationError :: from ( "broadcast check failed" ) )
889+ } )
890+ . expect_err ( "Should fail when can_broadcast returns an implementation error" ) ;
891+ match err {
892+ Error :: Implementation ( error_message) => {
893+ assert_eq ! ( error_message. to_string( ) , "broadcast check failed" . to_string( ) )
894+ }
895+ _ => panic ! ( "Expected Error::Implementation, got: {err:?}" ) ,
896+ }
897+
898+ // Outcome 4: success
899+ original
900+ . check_broadcast_suitability ( None , |_| Ok ( true ) )
901+ . expect ( "Should succeed when fee rate is acceptable and can_broadcast returns true" ) ;
902+ }
903+
904+ #[ test]
905+ fn test_check_inputs_not_owned ( ) {
906+ let original = original_from_test_vector ( ) ;
907+ let original_missing_prevtxout = original_missing_prevtxout_from_test_vector ( ) ;
908+
909+ // Outcome 1: input_scripts returns a PrevTxOut error → Protocol error
910+ let err = original_missing_prevtxout
911+ . check_inputs_not_owned ( & mut |_| Ok ( false ) )
912+ . expect_err ( "Should fail when previous txout is missing" ) ;
913+ match err {
914+ Error :: Protocol ( ProtocolError :: OriginalPayload ( PayloadError (
915+ InternalPayloadError :: PrevTxOut ( _) ,
916+ ) ) ) => { }
917+ _ => panic ! ( "Expected PrevTxOut error, got: {err:?}" ) ,
918+ }
919+
920+ // Outcome 2: is_owned returns true → InputOwned error
921+ let err = original
922+ . clone ( )
923+ . check_inputs_not_owned ( & mut |_| Ok ( true ) )
924+ . expect_err ( "Should fail when input is owned" ) ;
925+ match err {
926+ Error :: Protocol ( ProtocolError :: OriginalPayload ( PayloadError (
927+ InternalPayloadError :: InputOwned ( _) ,
928+ ) ) ) => { }
929+ _ => panic ! ( "Expected InputOwned error, got: {err:?}" ) ,
930+ }
931+
932+ // Outcome 3: is_owned returns an implementation error → Error::Implementation
933+ let err = original
934+ . clone ( )
935+ . check_inputs_not_owned ( & mut |_| {
936+ Err ( ImplementationError :: from ( "ownership check failed" ) )
937+ } )
938+ . expect_err ( "Should fail when is_owned returns an implementation error" ) ;
939+ match err {
940+ Error :: Implementation ( error_message) => {
941+ assert_eq ! ( error_message. to_string( ) , "ownership check failed" . to_string( ) )
942+ }
943+ _ => panic ! ( "Expected Error::Implementation, got: {err:?}" ) ,
944+ }
945+
946+ // Outcome 4: is_owned returns false → success
947+ original
948+ . check_inputs_not_owned ( & mut |_| Ok ( false ) )
949+ . expect ( "Should succeed when no inputs are owned" ) ;
950+ }
951+
952+ #[ test]
953+ fn test_check_no_inputs_seen_before ( ) {
954+ let original = original_from_test_vector ( ) ;
955+
956+ // Outcome 1: is_known returns true → InputSeen error
957+ let err = original
958+ . clone ( )
959+ . check_no_inputs_seen_before ( & mut |_| Ok ( true ) )
960+ . expect_err ( "Should fail when input has been seen before" ) ;
961+ match err {
962+ Error :: Protocol ( ProtocolError :: OriginalPayload ( PayloadError (
963+ InternalPayloadError :: InputSeen ( _) ,
964+ ) ) ) => { }
965+ _ => panic ! ( "Expected InputSeen error, got: {err:?}" ) ,
966+ }
967+
968+ // Outcome 2: is_known returns an implementation error → Error::Implementation
969+ let err = original
970+ . clone ( )
971+ . check_no_inputs_seen_before ( & mut |_| {
972+ Err ( ImplementationError :: from ( "input seen check failed" ) )
973+ } )
974+ . expect_err ( "Should fail when is_known returns an implementation error" ) ;
975+ match err {
976+ Error :: Implementation ( error_message) => {
977+ assert_eq ! ( error_message. to_string( ) , "input seen check failed" . to_string( ) )
978+ }
979+ _ => panic ! ( "Expected Error::Implementation, got: {err:?}" ) ,
980+ }
981+
982+ // Outcome 3: is_known returns false → success
983+ original
984+ . check_no_inputs_seen_before ( & mut |_| Ok ( false ) )
985+ . expect ( "Should succeed when no inputs have been seen before" ) ;
986+ }
987+
833988 #[ test]
834989 fn test_identify_receiver_outputs ( ) {
835990 let original = original_from_test_vector ( ) ;
@@ -864,4 +1019,32 @@ pub(crate) mod tests {
8641019 assert_eq ! ( wants_outputs. owned_vouts, vec![ 0 , 1 ] ) ;
8651020 assert_eq ! ( wants_outputs. params. additional_fee_contribution, None ) ;
8661021 }
1022+
1023+ #[ test]
1024+ fn test_finalize_proposal ( ) {
1025+ let psbt_context = psbt_context_from_test_vector ( ) ;
1026+
1027+ // Outcome 1: wallet_process_psbt returns an implementation error → ImplementationError
1028+ let err = psbt_context
1029+ . clone ( )
1030+ . finalize_proposal ( |_| Err ( ImplementationError :: from ( "wallet signing failed" ) ) )
1031+ . expect_err ( "Should fail when wallet_process_psbt returns an error" ) ;
1032+ assert_eq ! ( err. to_string( ) , "wallet signing failed" ) ;
1033+
1034+ // Outcome 2: wallet_process_psbt returns a psbt with mismatched ntxid → ImplementationError
1035+ let psbt_context = psbt_context_from_test_vector ( ) ;
1036+ let err = psbt_context
1037+ . clone ( )
1038+ . finalize_proposal ( |_| {
1039+ // return a totally different psbt to trigger ntxid mismatch
1040+ Ok ( PARSED_ORIGINAL_PSBT . clone ( ) )
1041+ } )
1042+ . expect_err ( "Should fail when ntxid mismatches" ) ;
1043+ assert ! ( err. to_string( ) . contains( "Ntxid mismatch" ) ) ;
1044+
1045+ // Outcome 3: wallet_process_psbt succeeds → Ok(Psbt)
1046+ let _psbt = psbt_context
1047+ . finalize_proposal ( |_| Ok ( PARSED_PAYJOIN_PROPOSAL . clone ( ) ) )
1048+ . expect ( "Should succeed when wallet_process_psbt returns a valid signed psbt" ) ;
1049+ }
8671050}
0 commit comments