open-standards/openpay/crates/op-graph/tests/persistence.rs
Transaction Science 70ecd6caa0 Initial import — three open standards under one shelf
Transaction Science open standards monorepo. Each subdirectory is a
self-contained reference protocol with its own README and licence.

- openpay/    Apache-2.0  payment-acceptance stack (Rust, 26 crates)
- smart-byte/ CC-BY-4.0   content-addressed value carrier (spec)
- eoc/        CC-BY-4.0   energy-optimised compute (spec)

The wire format and the right to fork are public. The optional hosted
services are the offer; the protocols are not.
2026-05-23 07:58:09 -04:00

107 lines
4.1 KiB
Rust

//! Cross-store persistence: one `.graph` file holds refund,
//! dispute, settlement, and idempotency state; reopening the file
//! recovers everything.
use op_core::{Currency, Money};
use op_dispute::reason::DisputeReason;
use op_dispute::{Dispute, DisputeStore};
use op_graph::{
GraphDisputeStore, GraphHandle, GraphIdempotencyStore, GraphRefundStore, GraphSettlementStore,
};
use op_ledger::TransactionId;
use op_orchestrator::{
Attempt, AttemptOutcome, IdempotencyKey, IdempotencyStore, OrchestrationOutcome, TerminalStatus,
};
use op_refund::reason::RefundReason;
use op_refund::{Refund, RefundStore};
use op_settlement::{Batch, PayoutRail, SettlementStore};
use tempfile::tempdir;
#[test]
fn refund_dispute_settlement_idempotency_all_persist_on_one_file() {
let dir = tempdir().unwrap();
let path = dir.path().join("openpay.graph");
// Phase 1: write through fresh handle.
let (refund_id, dispute_id, batch_id, idem_key) = {
let handle = GraphHandle::new_persistent(&path).unwrap();
let refunds = GraphRefundStore::with_handle(handle.clone());
let disputes = GraphDisputeStore::with_handle(handle.clone());
let settlement = GraphSettlementStore::with_handle(handle.clone());
let idempotency = GraphIdempotencyStore::with_handle(handle.clone());
// Refund.
let r = Refund::new(
TransactionId::new(),
Money::from_minor(500, Currency::USD),
RefundReason::CustomerRequest,
1_000,
)
.unwrap()
.with_external_id("ref-persist");
let refund_id = refunds.create_refund(r).unwrap();
// Dispute.
let d = Dispute::new(
TransactionId::new(),
Money::from_minor(700, Currency::USD),
DisputeReason::Fraudulent,
2_000,
)
.unwrap()
.with_external_id("disp-persist");
let dispute_id = disputes.create_dispute(d).unwrap();
// Settlement.
let b = Batch::open(Currency::USD, PayoutRail::AchNacha, 3_000)
.with_external_id("batch-persist");
let batch_id = settlement.create_batch(b).unwrap();
// Idempotency.
let key = IdempotencyKey::new("idem-persist");
let prior = idempotency.reserve(&key, "sig-1");
assert!(prior.is_none(), "first reserve should be empty");
idempotency.commit(
&key,
&OrchestrationOutcome {
terminal_status: TerminalStatus::Approved,
attempts: vec![Attempt {
rail: op_core::RailKind::Card,
driver: "test".into(),
outcome: AttemptOutcome::Success,
}],
rail_used: Some(op_core::RailKind::Card),
psp_payment_id: Some("psp_x".into()),
uetr: None,
},
);
handle.compact().unwrap();
(refund_id, dispute_id, batch_id, key)
};
// Phase 2: drop the original handle, reopen, verify recovery.
let handle = GraphHandle::new_persistent(&path).unwrap();
let refunds = GraphRefundStore::with_handle(handle.clone());
let disputes = GraphDisputeStore::with_handle(handle.clone());
let settlement = GraphSettlementStore::with_handle(handle.clone());
let idempotency = GraphIdempotencyStore::with_handle(handle.clone());
let r = refunds.get_refund(refund_id).unwrap();
assert_eq!(r.external_id.as_deref(), Some("ref-persist"));
assert_eq!(r.amount, Money::from_minor(500, Currency::USD));
let d = disputes.get_dispute(dispute_id).unwrap();
assert_eq!(d.external_id.as_deref(), Some("disp-persist"));
assert_eq!(d.reason, DisputeReason::Fraudulent);
let b = settlement.get_batch(batch_id).unwrap();
assert_eq!(b.external_id.as_deref(), Some("batch-persist"));
assert_eq!(b.rail, PayoutRail::AchNacha);
let rec = idempotency.reserve(&idem_key, "sig-1").unwrap();
assert_eq!(rec.body_signature, "sig-1");
let outcome = rec.outcome.expect("committed outcome should persist");
assert_eq!(outcome.terminal_status, TerminalStatus::Approved);
assert_eq!(outcome.psp_payment_id.as_deref(), Some("psp_x"));
}