//! Cross-driver lifecycle / conformance tests. //! //! These tests load the XML conformance vectors from `vectors/` and //! verify that: //! 1. The pacs.002 parser extracts the documented fields from each vector. //! 2. The status mappers (FedNow, PIX, SEPA Instant) all agree on the //! interpretation of the shared ISO 20022 status codes. //! //! By treating the same `pacs002_*.xml` file as input to three //! different `status_map::map_transaction_status` functions, we //! mechanically prove that the rail-specific code agrees on what ACSC //! and RJCT mean. // The whole file requires the FedNow XML parser. Sub-tests that // additionally need PIX or SEPA mappers carry their own cfg gates. #![cfg(feature = "fednow")] #[cfg(all(feature = "pix", feature = "sepa-instant"))] use op_rails_a2a::acquirer::A2aStatus; use op_rails_a2a::fednow::xml::parse_pacs002; const ACSC_VECTOR: &str = include_str!("../vectors/pacs002_acsc.xml"); const RJCT_VECTOR: &str = include_str!("../vectors/pacs002_rjct_ac03.xml"); #[test] fn acsc_vector_parses_all_documented_fields() { let parsed = parse_pacs002(ACSC_VECTOR).expect("ACSC vector must parse"); assert_eq!(parsed.transaction_status, "ACSC"); assert_eq!( parsed.uetr.as_deref(), Some("12a345b6-7c89-4d01-23e4-567890abcdef") ); assert_eq!(parsed.original_end_to_end_id.as_deref(), Some("INV4242")); assert!( parsed.reason_code.is_none(), "ACSC should have no reason code" ); assert!( parsed.reason_text.is_none(), "ACSC should have no reason text" ); } #[test] fn rjct_vector_parses_reason_code_and_text() { let parsed = parse_pacs002(RJCT_VECTOR).expect("RJCT vector must parse"); assert_eq!(parsed.transaction_status, "RJCT"); assert_eq!(parsed.reason_code.as_deref(), Some("AC03")); assert_eq!( parsed.reason_text.as_deref(), Some("Invalid creditor account number") ); } #[test] #[cfg(all(feature = "pix", feature = "sepa-instant"))] fn all_three_rails_agree_acsc_means_settled() { use op_rails_a2a::fednow::status_map as fednow; use op_rails_a2a::pix::status_map as pix; use op_rails_a2a::sepa_instant::status_map as sepa; let parsed = parse_pacs002(ACSC_VECTOR).unwrap(); assert_eq!( fednow::map_transaction_status(&parsed.transaction_status).unwrap(), A2aStatus::Settled ); assert_eq!( pix::map_transaction_status(&parsed.transaction_status).unwrap(), A2aStatus::Settled ); assert_eq!( sepa::map_transaction_status(&parsed.transaction_status).unwrap(), A2aStatus::Settled ); } #[test] #[cfg(all(feature = "pix", feature = "sepa-instant"))] fn all_three_rails_agree_rjct_means_rejected() { use op_rails_a2a::fednow::status_map as fednow; use op_rails_a2a::pix::status_map as pix; use op_rails_a2a::sepa_instant::status_map as sepa; let parsed = parse_pacs002(RJCT_VECTOR).unwrap(); assert_eq!( fednow::map_transaction_status(&parsed.transaction_status).unwrap(), A2aStatus::Rejected ); assert_eq!( pix::map_transaction_status(&parsed.transaction_status).unwrap(), A2aStatus::Rejected ); assert_eq!( sepa::map_transaction_status(&parsed.transaction_status).unwrap(), A2aStatus::Rejected ); } #[test] fn fednow_outbound_vector_is_well_formed() { let outbound = include_str!("../vectors/fednow_pacs008_outbound.xml"); // FedNow profile sentinel: USABA clearing system assert!(outbound.contains("USABA"), "FedNow uses USABA"); assert!(outbound.contains(r#"xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08""#)); assert!(outbound.contains("CLRG")); assert!(outbound.contains("1")); assert!(outbound.contains("SLEV")); assert!(outbound.contains(r#""#)); // Must NOT contain SEPA-specific sentinels. assert!( !outbound.contains(""), "FedNow does not use LclInstrm" ); assert!(!outbound.contains(""), "FedNow does not use IBAN"); } #[test] #[cfg(feature = "sepa-instant")] fn sepa_instant_outbound_vector_has_mandatory_inst_code() { let outbound = include_str!("../vectors/sepa_instant_pacs008_outbound.xml"); // Mandatory per EPC SCT Inst IG 2019 v1.0. assert!( outbound.contains("INST"), "SCT Inst MUST carry LclInstrm.Cd=INST" ); assert!( outbound.contains("SEPA"), "SCT Inst MUST carry SvcLvl.Cd=SEPA" ); assert!( outbound.contains("1"), "RT1/TIPS require single tx" ); assert!( outbound.contains(r#""#), "SCT Inst is EUR only" ); assert!(outbound.contains("DE89370400440532013000")); assert!(outbound.contains("DEUTDEFFXXX")); // Must NOT contain FedNow-specific sentinels. assert!(!outbound.contains("USABA"), "SEPA does not use USABA"); } #[test] fn outbound_vectors_differ_structurally() { let fednow = include_str!("../vectors/fednow_pacs008_outbound.xml"); let sepa = include_str!("../vectors/sepa_instant_pacs008_outbound.xml"); assert_ne!( fednow, sepa, "outbound vectors must be distinguishable per rail" ); }