open-standards/openpay/crates/op-connect/tests/end_to_end.rs
Transaction Science 3fe109d58f ingest batch 4: 9 crates · table-stakes completers across all three workspaces
OpenPay (3 crates, ~13k LOC, 175 tests)
  op-connect           sub-merchant onboarding + KYB + split-payments + payouts + 1099-K
  op-3ds2              3DS2.3 SCA processor (AReq/ARes/CReq/CRes/RReq/RRes + exemptions)
  op-tax               US/EU/UK/AU/CA/IN tax with Avalara/Stripe/CCH/Vertex/TaxJar adapters

Smart Byte (3 crates, ~7k LOC, 57 tests)
  smart-byte-libp2p    libp2p transport (kad/gossipsub/mdns/autonat/relay/dcutr/identify/ping/req_resp)
  smart-byte-crdt      LWW/GCounter/PnCounter/OrSet/LwwMap/RgaList/2P-Set + HLC + vector clock + Automerge/Yjs interop
  smart-byte-keri-witness  KERI key-rotation cycle + witness layer + watchers + recovery + delegation

EOC (3 crates, ~8k LOC, 108 tests)
  eoc-tools            function-calling across Anthropic/OpenAI/Google/Mistral/Cohere + parallel exec + sandbox
  eoc-multimodal       vision (CLIP/SigLIP/ColPali) + audio (Whisper/MMS) + video sampling + modality router
  eoc-spec-decode      vanilla + SpS-with-temp + lookahead speculative decoding + EAGLE/Medusa stubs

All three workspaces compile cleanly. Forbidden-unsafe, no unwrap outside tests.

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

231 lines
6.9 KiB
Rust

//! End-to-end onboarding test for `op-connect`.
//!
//! Walks an account from BusinessInfo through TosAcceptance, verifying
//! that [`Requirements`] transitions correctly at each step and the
//! flow lands in [`OnboardingStatus::PendingVerification`] after the
//! last required step (because IdentityVerification is `eventually_due`).
use chrono::{NaiveDate, Utc};
use op_connect::{
AccountId, AccountType, Address, BeneficialOwner, BusinessProfile, BusinessStructure,
CountryCode, NativeProvider, OnboardingProvider, OnboardingStep, Person, StepPayload, TaxId,
TosAcceptance,
onboarding::ExternalAccount,
};
fn business() -> BusinessProfile {
BusinessProfile {
legal_name: "Acme Widgets LLC".into(),
trade_name: Some("Acme".into()),
structure: BusinessStructure::SingleMemberLlc,
tax_id: Some(TaxId::Ein("12-3456789".into())),
mcc: 5734,
country: CountryCode("US".into()),
registered_address: address(),
support_email: Some("[email protected]".into()),
support_phone: None,
website: Some("https://acme.example".into()),
}
}
fn address() -> Address {
Address {
line1: "1 Main St".into(),
line2: None,
city: "Austin".into(),
region: "TX".into(),
postal_code: "78701".into(),
country: CountryCode("US".into()),
}
}
fn owner(name: &str, ownership: f32, control: bool) -> BeneficialOwner {
BeneficialOwner {
person: Person {
legal_name: name.into(),
dob: NaiveDate::from_ymd_opt(1985, 5, 15).expect("date"),
address: address(),
ssn_last_4: Some("1234".into()),
ssn_or_itin_full: None,
government_id: None,
},
ownership_pct: ownership,
control,
is_pep: false,
}
}
#[tokio::test]
async fn full_onboarding_walk() {
let provider = NativeProvider::new(AccountType::Custom);
let acct = provider.create_account(&business()).await.expect("create");
// Initial snapshot: currently_due has the canonical block of items.
let snap = provider.snapshot(&acct).expect("snapshot");
assert!(
snap.requirements
.currently_due
.iter()
.any(|r| r == "business.legal_name"),
"expected business.legal_name as currently_due, got {:?}",
snap.requirements.currently_due
);
// ---- 1. Business info ----
let res = provider
.submit_step(
&acct,
OnboardingStep::BusinessInfo,
StepPayload::BusinessInfo(business()),
)
.await
.expect("ok");
assert!(res.accepted);
assert!(!res
.requirements
.currently_due
.iter()
.any(|r| r == "business.legal_name"));
// ---- 2. Representative info ----
let rep_owner = owner("Jane Doe", 100.0, true);
let res = provider
.submit_step(
&acct,
OnboardingStep::RepresentativeInfo,
StepPayload::RepresentativeInfo(rep_owner.clone()),
)
.await
.expect("ok");
assert!(res.accepted);
assert!(!res
.requirements
.currently_due
.iter()
.any(|r| r == "representative.identity"));
// ---- 3. Beneficial owners (FinCEN CDD) ----
let res = provider
.submit_step(
&acct,
OnboardingStep::BeneficialOwners,
StepPayload::BeneficialOwners(vec![rep_owner]),
)
.await
.expect("ok");
assert!(res.accepted);
assert!(!res
.requirements
.currently_due
.iter()
.any(|r| r == "beneficial_owners"));
// ---- 4. Bank account ----
let res = provider
.submit_step(
&acct,
OnboardingStep::BankAccount,
StepPayload::BankAccount(ExternalAccount {
country: "US".into(),
currency: "USD".into(),
routing: "110000000".into(),
account_number: "000123456789".into(),
account_holder_name: "Acme Widgets LLC".into(),
}),
)
.await
.expect("ok");
assert!(res.accepted);
assert!(!res
.requirements
.currently_due
.iter()
.any(|r| r == "external_account"));
// External-account verification is now currently-due.
assert!(res
.requirements
.currently_due
.iter()
.any(|r| r == "external_account.verification"));
// ---- 5. ToS acceptance ----
let res = provider
.submit_step(
&acct,
OnboardingStep::TosAcceptance,
StepPayload::TosAcceptance(TosAcceptance {
ip: "203.0.113.42".into(),
user_agent: "Mozilla/5.0".into(),
accepted_at: Utc::now(),
version_hash: "tos-2026-01".into(),
}),
)
.await
.expect("ok");
assert!(res.accepted);
assert!(!res
.requirements
.currently_due
.iter()
.any(|r| r == "tos_acceptance"));
// ---- 6. External-account verification (micro-deposits) ----
provider.arm_micro_deposits(&acct, [27, 31]);
let res = provider
.submit_step(
&acct,
OnboardingStep::ExternalAccountVerification,
StepPayload::ExternalAccountVerification {
amounts_minor: [27, 31],
},
)
.await
.expect("ok");
assert!(res.accepted);
assert!(!res
.requirements
.currently_due
.iter()
.any(|r| r == "external_account.verification"));
// ---- 7. Identity verification (optional / eventually-due) ----
let mut docs = std::collections::BTreeMap::new();
docs.insert("Jane Doe".into(), "tok_v7_id_document_001".into());
let res = provider
.submit_step(
&acct,
OnboardingStep::IdentityVerification,
StepPayload::IdentityVerification { documents: docs },
)
.await
.expect("ok");
assert!(res.accepted);
// Eventually-due cleared.
assert!(!res
.requirements
.eventually_due
.iter()
.any(|r| r == "identity.document.verification"));
// Now the account is fully clear.
let status = provider.status(&acct).await.expect("status");
assert_eq!(
status,
op_connect::OnboardingStatus::Active,
"expected active after final step; got {status:?}"
);
let snap = provider.snapshot(&acct).expect("snap");
assert!(snap.is_active(), "account should be active");
}
#[tokio::test]
async fn unknown_account_errors() {
let provider = NativeProvider::new(AccountType::Custom);
let bogus = AccountId("acct_does_not_exist".into());
let err = provider
.status(&bogus)
.await
.expect_err("unknown account");
assert!(matches!(err, op_connect::Error::AccountNotFound(_)));
}