Skip to content

Commit e456f1d

Browse files
authored
Add tests for OriginalPayload and PsbtContext (#1583)
2 parents 81c8db8 + 136d5fb commit e456f1d

1 file changed

Lines changed: 184 additions & 1 deletion

File tree

  • payjoin/src/core/receive

payjoin/src/core/receive/mod.rs

Lines changed: 184 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)