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]>
141 lines
4.5 KiB
Rust
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(_)));
|
|
}
|