#![allow(unused_crate_dependencies, reason = "sibling tests consume them")]
#![allow(
clippy::tests_outside_test_module,
reason = "integration test binaries put #[test] fns at file scope"
)]
#![allow(
clippy::indexing_slicing,
clippy::shadow_unrelated,
clippy::unwrap_used,
reason = "idiomatic test-code patterns"
)]
use r402_core::ErrorReason;
use r402_core::wire::{
ExtensionEntry, Extensions, PaymentRequired, PaymentRequirements, ResourceInfo, SettleResponse,
SupportedPaymentKind, SupportedResponse, V2, VerifyResponse,
};
#[test]
fn payment_required_full_envelope_roundtrip() {
let resource = ResourceInfo::new("https://api.example.com/premium")
.with_description("Premium dataset")
.with_mime_type("application/json");
let req = PaymentRequirements::new(
"exact".into(),
"eip155:8453".parse().unwrap(),
"1000000".into(),
"0x209693Bc6afc0C5328bA36FaF03C514EF312287C".into(),
"0x036CbD53842c5426634e7929541eC2318f3dCF7e".into(),
60,
)
.with_extra(serde_json::json!({"name": "USDC", "version": "2"}));
let mut extensions = Extensions::new();
extensions.insert(
"bazaar",
ExtensionEntry::info(serde_json::json!({"registered": true})),
);
let body = PaymentRequired::new(resource)
.add_accept(req)
.with_error("missing X-PAYMENT")
.with_extensions(extensions);
let json = serde_json::to_value(&body).expect("serialize");
let back: PaymentRequired = serde_json::from_value(json.clone()).expect("deserialize");
let again = serde_json::to_value(&back).unwrap();
assert_eq!(json, again);
assert_eq!(body.x402_version, V2);
assert_eq!(body.accepts.len(), 1);
}
#[test]
fn supported_response_minimal_and_full_roundtrip() {
let minimal = SupportedResponse::new().with_kinds(vec![SupportedPaymentKind::new(
2,
"exact",
"eip155:8453",
)]);
let json = serde_json::to_value(&minimal).unwrap();
assert_eq!(json["kinds"][0]["scheme"], "exact");
assert_eq!(json["extensions"], serde_json::json!([]));
let back: SupportedResponse = serde_json::from_value(json).unwrap();
assert_eq!(back, minimal);
let full = SupportedResponse::new()
.with_kinds(vec![
SupportedPaymentKind::new(2, "exact", "eip155:8453"),
SupportedPaymentKind::new(2, "exact", "solana:mainnet").with_extra(serde_json::json!({
"feePayer": "CKPKJWNdJEqa81x7CkZ14BVPiY6y16Sxs7owznqtWYp5",
})),
])
.with_extensions(vec!["bazaar".into(), "payment-identifier".into()])
.with_signers({
let mut m = std::collections::HashMap::new();
m.insert(
"eip155:*".into(),
vec!["0x1234567890abcdef1234567890abcdef12345678".into()],
);
m.insert(
"solana:*".into(),
vec!["CKPKJWNdJEqa81x7CkZ14BVPiY6y16Sxs7owznqtWYp5".into()],
);
m
});
let json = serde_json::to_value(&full).unwrap();
let back: SupportedResponse = serde_json::from_value(json).unwrap();
assert_eq!(back, full);
let chain: r402_core::chain::ChainId = "eip155:8453".parse().unwrap();
let signers = back.signers_for_chain(&chain);
assert_eq!(signers.len(), 1);
assert!(signers[0].starts_with("0x"));
}
#[test]
fn settle_failure_emits_empty_transaction() {
let failure = SettleResponse::Failure {
reason: ErrorReason::DuplicateSettlement,
message: Some("already processed".into()),
payer: Some("0xpayer".into()),
network: "eip155:8453".into(),
extensions: Extensions::new(),
};
let json = serde_json::to_value(&failure).unwrap();
assert_eq!(
json["transaction"], "",
"F-002 regression: empty transaction must be present, not omitted"
);
assert_eq!(json["errorReason"], "duplicate_settlement");
let back: SettleResponse = serde_json::from_value(json).unwrap();
assert_eq!(back, failure);
}
#[test]
fn verify_response_invalid_preserves_unknown_reason() {
let unknown_code = "vendor_specific_diagnostic_42";
let resp = VerifyResponse::invalid(Some("0xabc".into()), ErrorReason::from_wire(unknown_code));
let json = serde_json::to_value(&resp).unwrap();
assert_eq!(json["isValid"], false);
assert_eq!(json["invalidReason"], unknown_code);
let back: VerifyResponse = serde_json::from_value(json).unwrap();
assert_eq!(back, resp);
}
#[test]
fn unknown_field_rejection_for_top_level_envelopes() {
let with_typo = serde_json::json!({
"url": "https://example.com",
"descriptionTypo": "should reject",
});
assert!(serde_json::from_value::<ResourceInfo>(with_typo).is_err());
let kind_typo = serde_json::json!({
"x402Version": 2,
"scheme": "exact",
"network": "eip155:1",
"extraTypo": null,
});
assert!(serde_json::from_value::<SupportedPaymentKind>(kind_typo).is_err());
}