use super::*;
#[test]
fn test_decrypt_receipt_plaintext_parse_error_redacts_plaintext() {
let receipt_id = ReceiptId::new("450e8400-e29b-41d4-a716-446655440000").unwrap();
let sentinel = "SENTINEL_DECRYPTED_PLAINTEXT";
let plaintext = serde_json::to_vec(&json!({
"version": sentinel,
"kind": "paykit.receipt",
}))
.unwrap();
let location = ReceiptAccess::location_for(&receipt_id);
let key = ReceiptDecryptionKey::generate();
let encrypted = encrypt_receipt_plaintext_for_test_location(&plaintext, &key, &location);
let err = decrypt_receipt(&encrypted, &key, &location).unwrap_err();
let (context, source) = match &err {
PaykitError::InvalidData { context, source } => (context.clone(), source),
other => panic!("expected InvalidData error, got {other:?}"),
};
assert_eq!(context, "failed to parse receipt plaintext JSON");
assert!(
source.is_none(),
"plaintext parse error must carry no source"
);
let rendered = format!("{err} / {err:?}");
assert!(
!rendered.contains(sentinel),
"decrypted plaintext leaked into error output: {rendered}"
);
}
#[test]
fn test_decrypt_receipt_wire_validation_error_redacts_plaintext_kind() {
let receipt_id = ReceiptId::new("450e8400-e29b-41d4-a716-446655440000").unwrap();
let sentinel = "SENTINEL_PLAINTEXT_KIND";
let receipt = Receipt {
receipt_id: receipt_id.clone(),
payment_reference: PaymentReference::new("invoice-2026-0001").unwrap(),
payment_request_id: None,
billing_period: None,
recipient_public_key: Keypair::random().public_key(),
payment_endpoint_identifier: None,
amount: None,
metadata: JsonMap::new(),
};
let mut plaintext = serde_json::to_value(ReceiptWire::from(&receipt)).unwrap();
plaintext["kind"] = JsonValue::String(sentinel.to_string());
let plaintext = serde_json::to_vec(&plaintext).unwrap();
let location = ReceiptAccess::location_for(&receipt_id);
let key = ReceiptDecryptionKey::generate();
let encrypted = encrypt_receipt_plaintext_for_test_location(&plaintext, &key, &location);
let err = decrypt_receipt(&encrypted, &key, &location).unwrap_err();
let (context, source) = match &err {
PaykitError::InvalidData { context, source } => (context.clone(), source),
other => panic!("expected InvalidData error, got {other:?}"),
};
assert_eq!(context, "unsupported Receipt version/kind");
assert!(
source.is_none(),
"wire validation error must carry no source"
);
let rendered = format!("{err} / {err:?}");
assert!(
!rendered.contains(sentinel),
"decrypted kind value leaked into error output: {rendered}"
);
}
#[test]
fn test_parse_receipt_access_json_parse_error_redacts_plaintext() {
let sentinel = "SENTINEL_RECEIPT_ACCESS_PLAINTEXT";
let json = format!("{{\"version\":\"{sentinel}\"}}");
let err = parse_receipt_access_json(&json).unwrap_err();
let (context, source) = match &err {
PaykitError::InvalidData { context, source } => (context.clone(), source),
other => panic!("expected InvalidData error, got {other:?}"),
};
assert_eq!(context, "failed to parse Receipt Access JSON");
assert!(
source.is_none(),
"Receipt Access parse error must carry no source"
);
let rendered = format!("{err} / {err:?}");
assert!(
!rendered.contains(sentinel),
"decrypted plaintext leaked into error output: {rendered}"
);
}
#[test]
fn test_store_prepared_receipt_error_redacts_receipt_location() {
let location = "/pub/paykit/v0/private/receipts/550e8400-e29b-41d4-a716-446655440000";
let err = super::access::store_prepared_receipt_error(
location,
anyhow::anyhow!("homeserver rejected put"),
);
let context = match &err {
PaykitError::Transport { context, .. } => context.clone(),
other => panic!("expected Transport error, got {other:?}"),
};
assert_eq!(context, "failed to store encrypted receipt");
let rendered = err.to_string();
assert!(
!rendered.contains(location),
"Receipt Location leaked into Display: {rendered}"
);
}
#[test]
fn test_receipt_decryption_key_debug_and_display_are_redacted() {
let key = ReceiptDecryptionKey::generate();
let raw_key = key.as_str().to_string();
let receipt_id = ReceiptId::new("450e8400-e29b-41d4-a716-446655440000").unwrap();
let access = ReceiptAccess {
version: 1,
kind: PrivateMessageKind::ReceiptAccess,
event_id: crate::EventId::new_v4(),
receipt_id: receipt_id.clone(),
payment_reference: PaymentReference::new("550e8400-e29b-41d4-a716-446655440000").unwrap(),
payment_request_id: None,
billing_period: None,
location: ReceiptAccess::location_for(&receipt_id),
key,
};
assert!(!format!("{access:?}").contains(&raw_key));
assert!(!format!("{:?}", access.key).contains(&raw_key));
assert!(!format!("{}", access.key).contains(&raw_key));
}
#[test]
fn test_receipt_debug_redacts_plaintext_fields() {
let prepared = prepared_receipt_for_test();
let debug = format!("{prepared:?}");
assert!(!debug.contains("preimage"));
assert!(!debug.contains("1000"));
assert!(!debug.contains(&prepared.encrypted_receipt));
assert!(debug.contains("<redacted:"));
}