open-standards/openpay/crates/op-openbanking/tests/uk_ob_aisp.rs
Transaction Science 6ea90afbed ingest wave A4: 3 crates · smart-byte-atproto + op-openbanking + eoc-distributed
Smart Byte (1 crate, 2127 LOC, 35 tests)
  smart-byte-atproto    ATProto (Bluesky federation) adapter.
                        PLC + did:web resolution; XRPC client; CARv1 encode/decode
                        with CIDv1 (dag-cbor + raw, SHA-256); sketch MST with
                        order-independent BLAKE3 root; Repo + UnsignedCommit /
                        SignedCommit (Ed25519 over CBOR); firehose transport
                        trait + in-memory impl; AT URI ↔ smart-byte AID bridge
                        with SAID commitment.

OpenPay (1 crate, 2397 LOC, 44 tests)
  op-openbanking        UK OBIE R/W v3.1.10/3.1.11, Berlin Group NextGenPSD2
                        XS2A v1.3.13, STET PSD2 v1.7, Australia CDR Banking v1.31,
                        FDX v5/v6, SGFinDex shapes. AISP / PISP / PIIS / VRP
                        traits. FAPI 1.0 Advanced (PS256/ES256, mTLS, RFC 8705
                        certificate binding, JWK registration). Operators wire
                        their own JWS signer + HTTP transport — keeps the crate
                        KMS-agnostic.

EOC (1 crate, 2179 LOC, 32 tests)
  eoc-distributed       Distributed-inference scheduling. Worker trait with
                        Capability + Accelerator (CPU/GPU/TPU/NPU/Mixed) +
                        Load.busyness()/projected_micro_joules()/projected_g_co2e().
                        RoundRobin / LeastBusy / LatencyWeighted / JouleWeighted
                        (default) / CarbonWeighted routers. Work-stealing
                        scheduler. vLLM-style continuous batching with KV-block
                        budget. Ray-Serve-style auto-scaler with cooldown.
                        KV-cache-aware sticky-session routing.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-24 12:22:09 -04:00

141 lines
4.5 KiB
Rust

//! UK Open Banking AISP integration test.
//!
//! Exercises the binding-layer URL builder, scope membership, and the
//! end-to-end shape of the AISP service trait via an in-memory mock.
use op_core::{Currency, Money};
use op_openbanking::aisp::{
Account, AccountInfoService, AccountType, Balance, BalanceType, ConsentId, Transaction,
TransactionCredit, TransactionStatus,
};
use op_openbanking::fapi::OAuth2Token;
use op_openbanking::uk_ob::{UkOpenBankingService, UkRwVersion, UkScope};
use op_openbanking::{Error, Result};
struct MockUkAisp {
svc: UkOpenBankingService,
}
impl AccountInfoService for MockUkAisp {
fn accounts(&self, _consent: &ConsentId, _token: &OAuth2Token) -> Result<Vec<Account>> {
if !self.svc.has(UkScope::ReadAccountsDetail) {
return Err(Error::ScopeNotAuthorised("ReadAccountsDetail".into()));
}
Ok(vec![Account {
id: "acc-1".into(),
identifier: "GB29NWBK60161331926819".into(),
nickname: Some("Personal Current".into()),
account_type: AccountType::Personal,
subtype: None,
currency: Currency::GBP,
}])
}
fn balances(
&self,
_consent: &ConsentId,
_token: &OAuth2Token,
_account_id: &str,
) -> Result<Vec<Balance>> {
if !self.svc.has(UkScope::ReadBalances) {
return Err(Error::ScopeNotAuthorised("ReadBalances".into()));
}
Ok(vec![Balance {
balance_type: BalanceType::InterimAvailable,
amount: Money::from_minor(125_000, Currency::GBP),
credit_debit: TransactionCredit::Credit,
as_of: time::OffsetDateTime::UNIX_EPOCH,
}])
}
fn transactions(
&self,
_consent: &ConsentId,
_token: &OAuth2Token,
_account_id: &str,
_from: Option<time::OffsetDateTime>,
_to: Option<time::OffsetDateTime>,
) -> Result<Vec<Transaction>> {
if !self.svc.has(UkScope::ReadTransactionsDetail) {
return Err(Error::ScopeNotAuthorised("ReadTransactionsDetail".into()));
}
Ok(vec![Transaction {
id: "tx-1".into(),
status: TransactionStatus::Booked,
amount: Money::from_minor(2_500, Currency::GBP),
credit_debit: TransactionCredit::Debit,
booking_date: time::OffsetDateTime::UNIX_EPOCH,
description: "Shop XYZ".into(),
remittance: None,
}])
}
}
fn token() -> OAuth2Token {
OAuth2Token {
access_token: "tok".into(),
token_type: "Bearer".into(),
scopes: vec!["accounts".into()],
expires_in: 3600,
refresh_token: None,
cert_thumbprint: None,
}
}
#[test]
fn endpoint_root_is_v3_1() {
let svc = UkOpenBankingService {
version: UkRwVersion::V3_1_11,
scopes: vec![UkScope::ReadAccountsDetail],
aspsp_base_url: "https://api.aspsp.example".into(),
};
let url = svc.endpoint("/aisp/accounts");
assert!(url.starts_with("https://api.aspsp.example/open-banking/v3.1/"));
assert!(url.ends_with("/aisp/accounts"));
}
#[test]
fn aisp_happy_path_returns_account_balance_tx() {
let svc = UkOpenBankingService {
version: UkRwVersion::V3_1_11,
scopes: vec![
UkScope::ReadAccountsDetail,
UkScope::ReadBalances,
UkScope::ReadTransactionsDetail,
],
aspsp_base_url: "https://api.aspsp.example".into(),
};
let mock = MockUkAisp { svc };
let consent = ConsentId("consent-1".into());
let tok = token();
let accounts = mock.accounts(&consent, &tok).expect("accounts");
assert_eq!(accounts.len(), 1);
assert_eq!(accounts[0].currency, Currency::GBP);
let balances = mock
.balances(&consent, &tok, &accounts[0].id)
.expect("balances");
assert_eq!(balances.len(), 1);
assert_eq!(balances[0].balance_type, BalanceType::InterimAvailable);
let txs = mock
.transactions(&consent, &tok, &accounts[0].id, None, None)
.expect("transactions");
assert_eq!(txs.len(), 1);
assert_eq!(txs[0].credit_debit, TransactionCredit::Debit);
}
#[test]
fn missing_scope_fires_403() {
let svc = UkOpenBankingService {
version: UkRwVersion::V3_1_11,
scopes: vec![UkScope::ReadAccountsDetail],
aspsp_base_url: "x".into(),
};
let mock = MockUkAisp { svc };
let err = mock
.balances(&ConsentId("c".into()), &token(), "acc-1")
.expect_err("scope guard");
assert!(matches!(err, Error::ScopeNotAuthorised(_)));
}