diff --git a/payjoin-test-utils/src/lib.rs b/payjoin-test-utils/src/lib.rs index 1cb6f11db..fd801a5b6 100644 --- a/payjoin-test-utils/src/lib.rs +++ b/payjoin-test-utils/src/lib.rs @@ -253,7 +253,7 @@ pub async fn wait_for_service_ready( } pub static EXAMPLE_URL: Lazy = - Lazy::new(|| Url::parse("https://relay.com").expect("invalid URL")); + Lazy::new(|| Url::parse("https://example.com").expect("invalid URL")); pub const KEY_ID: KeyId = 1; pub const KEM: Kem = Kem::K256Sha256; diff --git a/payjoin/src/uri/mod.rs b/payjoin/src/uri/mod.rs index 3f44e7f65..e589b9e50 100644 --- a/payjoin/src/uri/mod.rs +++ b/payjoin/src/uri/mod.rs @@ -207,6 +207,8 @@ impl bitcoin_uri::de::DeserializationState<'_> for DeserializationState { mod tests { use std::convert::TryFrom; + use bitcoin_uri::SerializeParams; + use super::*; #[test] @@ -269,10 +271,48 @@ mod tests { #[test] fn test_unsupported() { - assert!(!Uri::try_from("bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX") + assert!( + !Uri::try_from("bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX") + .unwrap() + .extras + .pj_is_supported(), + "Uri expected a failure with missing pj extras, but it succeeded" + ); + } + + #[test] + fn test_supported() { + assert!( + Uri::try_from( + "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=0.01\ + &pjos=0&pj=HTTPS://EXAMPLE.COM/\ + %23OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC" + ) .unwrap() .extras - .pj_is_supported()); + .pj_is_supported(), + "Uri expected a success with a well formatted pj extras, but it failed" + ); + } + + #[test] + fn test_pj_param_unknown() { + use bitcoin_uri::de::DeserializationState as _; + let uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?pjos=1&pj=HTTPS://EXAMPLE.COM/\ + %23OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC"; + let pjuri = Uri::try_from(uri).unwrap().assume_checked().check_pj_supported().unwrap(); + let serialized_params = pjuri.extras.serialize_params(); + let pjos_key = serialized_params.clone().next().expect("Missing pjos key").0; + let pj_key = serialized_params.clone().next().expect("Missing pj key").0; + + let state = DeserializationState::default(); + + assert!(state.is_param_known(pjos_key), "The pjos key should match 'pjos', but it failed"); + assert!(state.is_param_known(pj_key), "The pj key should match 'pj', but it failed"); + assert!( + !state.is_param_known("unknown_param"), + "An unknown_param should not match 'pj' or 'pjos'" + ); } #[test] diff --git a/payjoin/src/uri/url_ext.rs b/payjoin/src/uri/url_ext.rs index fba86aa38..0078dee6b 100644 --- a/payjoin/src/uri/url_ext.rs +++ b/payjoin/src/uri/url_ext.rs @@ -225,26 +225,29 @@ impl std::error::Error for ParseReceiverPubkeyParamError { #[cfg(test)] mod tests { - use payjoin_test_utils::BoxError; + use payjoin_test_utils::{BoxError, EXAMPLE_URL}; use super::*; use crate::{Uri, UriExt}; #[test] fn test_ohttp_get_set() { - let mut url = Url::parse("https://example.com").unwrap(); + let mut url = EXAMPLE_URL.clone(); let serialized = "OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC"; let ohttp_keys = OhttpKeys::from_str(serialized).unwrap(); url.set_ohttp(ohttp_keys.clone()); assert_eq!(url.fragment(), Some(serialized)); - assert_eq!(url.ohttp().unwrap(), ohttp_keys); + assert_eq!( + url.ohttp().expect("Ohttp keys have been set but are missing on get"), + ohttp_keys + ); } #[test] fn test_errors_when_parsing_ohttp() { - let missing_ohttp_url = Url::parse("https://example.com").unwrap(); + let missing_ohttp_url = EXAMPLE_URL.clone(); assert!(matches!( missing_ohttp_url.ohttp(), Err(ParseOhttpKeysParamError::MissingOhttpKeys) @@ -261,19 +264,19 @@ mod tests { #[test] fn test_exp_get_set() { - let mut url = Url::parse("https://example.com").unwrap(); + let mut url = EXAMPLE_URL.clone(); let exp_time = std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1720547781); url.set_exp(exp_time); assert_eq!(url.fragment(), Some("EX1C4UC6ES")); - assert_eq!(url.exp().unwrap(), exp_time); + assert_eq!(url.exp().expect("Expiry has been set but is missing on get"), exp_time); } #[test] fn test_errors_when_parsing_exp() { - let missing_exp_url = Url::parse("http://example.com").unwrap(); + let missing_exp_url = EXAMPLE_URL.clone(); assert!(matches!(missing_exp_url.exp(), Err(ParseExpParamError::MissingExp))); let invalid_bech32_exp_url = @@ -290,7 +293,41 @@ mod tests { // Not enough data to decode into a u32 let invalid_timestamp_exp_url = Url::parse("http://example.com?pj=https://test-payjoin-url#EX10").unwrap(); - assert!(matches!(invalid_timestamp_exp_url.exp(), Err(ParseExpParamError::InvalidExp(_)))) + assert!(matches!(invalid_timestamp_exp_url.exp(), Err(ParseExpParamError::InvalidExp(_)))); + } + + #[test] + fn test_errors_when_parsing_receiver_pubkey() { + let missing_receiver_pubkey_url = EXAMPLE_URL.clone(); + assert!(matches!( + missing_receiver_pubkey_url.receiver_pubkey(), + Err(ParseReceiverPubkeyParamError::MissingPubkey) + )); + + let invalid_bech32_receiver_pubkey_url = + Url::parse("http://example.com?pj=https://test-payjoin-url#RK1invalid_bech_32") + .unwrap(); + assert!(matches!( + invalid_bech32_receiver_pubkey_url.receiver_pubkey(), + Err(ParseReceiverPubkeyParamError::DecodeBech32(_)) + )); + + // Since the HRP is everything to the left of the right-most separator, the invalid url in + // this test would have it's HRP being parsed as RK101 instead of the expected RK1 + let invalid_hrp_receiver_pubkey_url = + Url::parse("http://example.com?pj=https://test-payjoin-url#RK101").unwrap(); + assert!(matches!( + invalid_hrp_receiver_pubkey_url.receiver_pubkey(), + Err(ParseReceiverPubkeyParamError::InvalidHrp(_)) + )); + + // Not enough data to decode into a u32 + let invalid_receiver_pubkey_url = + Url::parse("http://example.com?pj=https://test-payjoin-url#RK10").unwrap(); + assert!(matches!( + invalid_receiver_pubkey_url.receiver_pubkey(), + Err(ParseReceiverPubkeyParamError::InvalidPubkey(_)) + )); } #[test]