Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions payjoin/contrib/test.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
#!/usr/bin/env bash
set -e

features=("v1" "v2")

cargo test --locked --package payjoin --verbose --all-features --lib
cargo test --locked --package payjoin --verbose --all-features --test integration

for feature in "${features[@]}"; do
cargo test --locked --package payjoin --verbose --no-default-features --features "$feature" --lib
cargo test --locked --package payjoin --verbose --no-default-features --features "$feature" --test integration
done
9 changes: 4 additions & 5 deletions payjoin/src/core/send/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,12 +403,11 @@ impl WellKnownError {

#[cfg(test)]
mod tests {
use serde_json::json;

use super::*;

#[test]
#[cfg(feature = "v1")]
fn test_parse_json() {
use super::*;

let known_str_error = r#"{"errorCode":"version-unsupported", "message":"custom message here", "supported": [1, 2]}"#;
match ResponseError::parse(known_str_error) {
ResponseError::WellKnown(e) => {
Expand All @@ -426,7 +425,7 @@ mod tests {
ResponseError::parse(unrecognized_error),
ResponseError::Unrecognized { .. }
));
let invalid_json_error = json!({
let invalid_json_error = serde_json::json!({
"err": "random",
"message": "This version of payjoin is not supported."
});
Expand Down
154 changes: 1 addition & 153 deletions payjoin/src/core/uri/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,6 @@ mod tests {
assert!(Uri::try_from(uri).is_err(), "pj is not a valid url");
}

#[test]
fn test_missing_amount() {
let uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?pj=https://testnet.demo.btcpayserver.org/BTC/pj";
assert!(Uri::try_from(uri).is_ok(), "missing amount should be ok");
}

#[test]
fn test_unencrypted() {
let uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=http://example.com";
Expand All @@ -282,29 +276,6 @@ mod tests {
assert!(Uri::try_from(uri).is_err(), "unencrypted connection");
}

#[test]
fn test_valid_uris() {
let https = "https://example.com";
let onion = "http://vjdpwgybvubne5hda6v4c5iaeeevhge6jvo3w2cl6eocbwwvwxp7b7qd.onion";

let base58 = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX";
let bech32_upper = "BITCOIN:TB1Q6D3A2W975YNY0ASUVD9A67NER4NKS58FF0Q8G4";
let bech32_lower = "bitcoin:tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4";

for address in [base58, bech32_upper, bech32_lower].iter() {
for pj in [https, onion].iter() {
let uri_with_amount = format!("{address}?amount=1&pj={pj}");
assert!(Uri::try_from(uri_with_amount).is_ok());

let uri_without_amount = format!("{address}?pj={pj}");
assert!(Uri::try_from(uri_without_amount).is_ok());

let uri_shuffled_params = format!("{address}?pj={pj}&amount=1");
assert!(Uri::try_from(uri_shuffled_params).is_ok());
}
}
}

#[test]
fn test_unsupported() {
assert!(
Expand All @@ -316,26 +287,10 @@ mod tests {
);
}

#[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(),
"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 uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?pjos=1&pj=HTTPS://EXAMPLE.COM/TXJCGKTKXLUUZ%23EX1C4UC6ES-OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC-RK1Q0DJS3VVDXWQQTLQ8022QGXSX7ML9PHZ6EDSF6AKEWQG758JPS2EV";
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;
Expand All @@ -350,111 +305,4 @@ mod tests {
"An unknown_param should not match 'pj' or 'pjos'"
);
}

#[test]
fn test_pj_duplicate_params() {
let uri =
"bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?pjos=1&pjos=1&pj=HTTPS://EXAMPLE.COM/\
%23OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC";
let pjuri = Uri::try_from(uri);
assert!(matches!(
pjuri,
Err(bitcoin_uri::de::Error::Extras(PjParseError(
InternalPjParseError::DuplicateParams("pjos")
)))
));
let uri =
"bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?pjos=1&pj=HTTPS://EXAMPLE.COM/\
%23OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC&pj=HTTPS://EXAMPLE.COM/\
%23OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC";
let pjuri = Uri::try_from(uri);
assert!(matches!(
pjuri,
Err(bitcoin_uri::de::Error::Extras(PjParseError(
InternalPjParseError::DuplicateParams("pj")
)))
));
}

#[test]
fn test_serialize_pjos() {
let uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?pj=HTTPS://EXAMPLE.COM/%23OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC";
let expected_is_disabled = "pjos=0";
let expected_is_enabled = "pjos=1";
let mut pjuri = Uri::try_from(uri)
.expect("Invalid uri")
.assume_checked()
.check_pj_supported()
.expect("Could not parse pj extras");

pjuri.extras.output_substitution = OutputSubstitution::Disabled;
assert!(
pjuri.to_string().contains(expected_is_disabled),
"Pj uri should contain param: {expected_is_disabled}, but it did not"
);

pjuri.extras.output_substitution = OutputSubstitution::Enabled;
assert!(
!pjuri.to_string().contains(expected_is_enabled),
"Pj uri should elide param: {expected_is_enabled}, but it did not"
);
}

#[test]
fn test_deserialize_pjos() {
// pjos=0 should disable output substitution
let uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?pj=https://example.com&pjos=0";
let parsed = Uri::try_from(uri).unwrap();
match parsed.extras {
MaybePayjoinExtras::Supported(extras) =>
assert_eq!(extras.output_substitution, OutputSubstitution::Disabled),
_ => panic!("Expected Supported PayjoinExtras"),
}

// pjos=1 should allow output substitution
let uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?pj=https://example.com&pjos=1";
let parsed = Uri::try_from(uri).unwrap();
match parsed.extras {
MaybePayjoinExtras::Supported(extras) =>
assert_eq!(extras.output_substitution, OutputSubstitution::Enabled),
_ => panic!("Expected Supported PayjoinExtras"),
}

// Elided pjos=1 should allow output substitution
let uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?pj=https://example.com";
let parsed = Uri::try_from(uri).unwrap();
match parsed.extras {
MaybePayjoinExtras::Supported(extras) =>
assert_eq!(extras.output_substitution, OutputSubstitution::Enabled),
_ => panic!("Expected Supported PayjoinExtras"),
}
}

/// Test that rejects HTTP URLs that are not onion addresses
#[test]
fn test_http_non_onion_rejected() {
// HTTP to regular domain should be rejected
let url = "http://example.com";
let result = PjParam::parse(url);
assert!(
matches!(result, Err(PjParseError(InternalPjParseError::UnsecureEndpoint))),
"Expected UnsecureEndpoint error for HTTP to non-onion domain"
);

// HTTPS to subdomain should be accepted
let url = "https://example.com";
let result = PjParam::parse(url);
assert!(
matches!(result, Ok(PjParam::V1(_))),
"Expected PjParam::V1 for HTTPS to non-onion domain without fragment"
);

// HTTP to domain ending in .onion should be accepted
let url = "http://example.onion";
let result = PjParam::parse(url);
assert!(
matches!(result, Ok(PjParam::V1(_))),
"Expected PjParam::V1 for HTTP to onion domain without fragment"
);
}
}
Loading
Loading