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.
237 lines
8 KiB
Rust
237 lines
8 KiB
Rust
//! Integration tests that run inside a real wasm host (Node.js or
|
|
//! a headless browser via `wasm-pack test`).
|
|
//!
|
|
//! These tests exercise the actual `#[wasm_bindgen]` glue, including
|
|
//! the JS object ownership protocol, constructor / getter dispatch,
|
|
//! and `Result<T, JsValue>` exception propagation. Run with:
|
|
//!
|
|
//! ```text
|
|
//! # Node.js (default)
|
|
//! wasm-pack test --node
|
|
//!
|
|
//! # Headless Chrome
|
|
//! wasm-pack test --headless --chrome
|
|
//!
|
|
//! # Headless Firefox
|
|
//! wasm-pack test --headless --firefox
|
|
//! ```
|
|
//!
|
|
//! ## Note on error inspection
|
|
//!
|
|
//! When a `Result<T, JsValue>` fails, the `JsValue` is opaque from
|
|
//! Rust's perspective — wasm-bindgen doesn't expose a public
|
|
//! `TryFromJsValue` for our exported `OpenPayError` class. So we
|
|
//! verify failure by checking `result.is_err()` and trust the
|
|
//! crate-level `error::tests` to verify the OpenPayError contents
|
|
//! before they cross the boundary. JS-side tests (in
|
|
//! `js/test.mjs`) cover the consumer-visible error shape.
|
|
|
|
#![cfg(target_arch = "wasm32")]
|
|
|
|
use op_wasm::{
|
|
CardData, HeuristicScorer, RustVault, TokenFormat, TokenLifetime, TokenizationPolicy, VaultRef,
|
|
};
|
|
use wasm_bindgen_test::*;
|
|
|
|
// By default tests run in Node; uncomment to force browser:
|
|
// wasm_bindgen_test_configure!(run_in_browser);
|
|
|
|
const VALID_VISA: &str = "4242424242424242";
|
|
const VALID_MC: &str = "5555555555554444";
|
|
|
|
#[wasm_bindgen_test]
|
|
fn card_data_valid_constructs() {
|
|
let card = CardData::new(VALID_VISA.to_owned(), 12, 2030).unwrap();
|
|
assert_eq!(card.first_six(), "424242");
|
|
assert_eq!(card.last_four(), "4242");
|
|
assert_eq!(card.exp_month(), 12);
|
|
assert_eq!(card.exp_year(), 2030);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn card_data_invalid_pan_returns_err() {
|
|
assert!(CardData::new("1111111111111111".to_owned(), 12, 2030).is_err());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn card_data_invalid_exp_month_returns_err() {
|
|
assert!(CardData::new(VALID_VISA.to_owned(), 13, 2030).is_err());
|
|
assert!(CardData::new(VALID_VISA.to_owned(), 0, 2030).is_err());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn vault_round_trip() {
|
|
let vault = RustVault::new("wasm-test".to_owned());
|
|
assert_eq!(vault.name(), "wasm-test");
|
|
|
|
let card = CardData::new(VALID_VISA.to_owned(), 12, 2030).unwrap();
|
|
let token = vault.tokenize(card, None).unwrap();
|
|
assert!(token.as_string().starts_with("tok_v7_"));
|
|
|
|
let recovered = vault.detokenize(&token).unwrap();
|
|
assert_eq!(recovered.last_four(), "4242");
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn vault_unknown_token_returns_err() {
|
|
let vault = RustVault::new("err".to_owned());
|
|
let fake = VaultRef::from_string("tok_v7_doesnotexist".to_owned());
|
|
assert!(vault.detokenize(&fake).is_err());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn vault_malformed_token_also_returns_err() {
|
|
let vault = RustVault::new("err".to_owned());
|
|
let bad = VaultRef::from_string("not-a-real-token".to_owned());
|
|
assert!(vault.detokenize(&bad).is_err());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn vault_single_use_consumes_on_first_detokenize() {
|
|
let vault = RustVault::new("single".to_owned());
|
|
let card = CardData::new(VALID_VISA.to_owned(), 12, 2030).unwrap();
|
|
let policy = TokenizationPolicy::single_use(Some(120));
|
|
let token = vault.tokenize(card, Some(policy)).unwrap();
|
|
|
|
// First detokenize succeeds.
|
|
let recovered = vault.detokenize(&token).unwrap();
|
|
assert_eq!(recovered.last_four(), "4242");
|
|
|
|
// Second detokenize fails — single-use already consumed.
|
|
assert!(vault.detokenize(&token).is_err());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn vault_reusable_allows_multiple_detokenizes() {
|
|
let vault = RustVault::new("reusable".to_owned());
|
|
let card = CardData::new(VALID_VISA.to_owned(), 12, 2030).unwrap();
|
|
let token = vault.tokenize(card, None).unwrap();
|
|
for _ in 0..5 {
|
|
let r = vault.detokenize(&token).unwrap();
|
|
assert_eq!(r.last_four(), "4242");
|
|
}
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn vault_exists_and_delete() {
|
|
let vault = RustVault::new("del".to_owned());
|
|
let card = CardData::new(VALID_VISA.to_owned(), 12, 2030).unwrap();
|
|
let token = vault.tokenize(card, None).unwrap();
|
|
|
|
assert!(vault.exists(&token).unwrap());
|
|
assert!(vault.delete(&token).unwrap());
|
|
assert!(!vault.exists(&token).unwrap());
|
|
// Idempotent.
|
|
assert!(!vault.delete(&token).unwrap());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn vault_distinct_tokens_for_same_pan() {
|
|
let vault = RustVault::new("uniq".to_owned());
|
|
let c1 = CardData::new(VALID_VISA.to_owned(), 12, 2030).unwrap();
|
|
let c2 = CardData::new(VALID_VISA.to_owned(), 12, 2030).unwrap();
|
|
let t1 = vault.tokenize(c1, None).unwrap();
|
|
let t2 = vault.tokenize(c2, None).unwrap();
|
|
assert_ne!(t1.as_string(), t2.as_string());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn vault_multiple_cards_coexist() {
|
|
let vault = RustVault::new("multi".to_owned());
|
|
let visa = vault
|
|
.tokenize(
|
|
CardData::new(VALID_VISA.to_owned(), 12, 2030).unwrap(),
|
|
None,
|
|
)
|
|
.unwrap();
|
|
let mc = vault
|
|
.tokenize(CardData::new(VALID_MC.to_owned(), 11, 2028).unwrap(), None)
|
|
.unwrap();
|
|
|
|
assert_eq!(vault.detokenize(&visa).unwrap().last_four(), "4242");
|
|
let mc_card = vault.detokenize(&mc).unwrap();
|
|
assert_eq!(mc_card.last_four(), "4444");
|
|
assert_eq!(mc_card.exp_month(), 11);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn vault_ref_round_trip() {
|
|
let original = "tok_v7_test123";
|
|
let v = VaultRef::from_string(original.to_owned());
|
|
assert_eq!(v.as_string(), original);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn heuristic_scorer_name_stable() {
|
|
let s = HeuristicScorer::new();
|
|
assert_eq!(s.name(), "heuristic-v1");
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn tokenization_policy_helpers() {
|
|
let single = TokenizationPolicy::single_use(Some(60));
|
|
assert_eq!(single.lifetime(), TokenLifetime::SingleUse);
|
|
assert_eq!(single.ttl_seconds(), 60);
|
|
|
|
let cof = TokenizationPolicy::card_on_file();
|
|
assert_eq!(cof.lifetime(), TokenLifetime::Reusable);
|
|
assert_eq!(cof.ttl_seconds(), 0);
|
|
|
|
let def = TokenizationPolicy::new();
|
|
assert_eq!(def.format(), TokenFormat::Random);
|
|
assert_eq!(def.lifetime(), TokenLifetime::Reusable);
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn pan_never_appears_in_token_string() {
|
|
let vault = RustVault::new("opacity".to_owned());
|
|
let card = CardData::new(VALID_VISA.to_owned(), 12, 2030).unwrap();
|
|
let token = vault.tokenize(card, None).unwrap();
|
|
let tok_str = token.as_string();
|
|
assert!(!tok_str.contains(VALID_VISA));
|
|
assert!(!tok_str.contains("424242"));
|
|
assert!(!tok_str.contains("4242"));
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn tokenize_from_string_convenience_function() {
|
|
let vault = RustVault::new("conv".to_owned());
|
|
let token = op_wasm::vault::tokenize_from_string(&vault, VALID_VISA.to_owned(), 12, 2030, None)
|
|
.unwrap();
|
|
assert!(token.as_string().starts_with("tok_v7_"));
|
|
|
|
let recovered = vault.detokenize(&token).unwrap();
|
|
assert_eq!(recovered.last_four(), "4242");
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn tokenize_from_string_invalid_pan_returns_err() {
|
|
let vault = RustVault::new("conv-err".to_owned());
|
|
let r =
|
|
op_wasm::vault::tokenize_from_string(&vault, "1111111111111111".to_owned(), 12, 2030, None);
|
|
assert!(r.is_err());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn deterministic_format_produces_same_token_for_same_pan() {
|
|
let vault = RustVault::new("det".to_owned());
|
|
let mut policy = TokenizationPolicy::new();
|
|
policy.set_format(TokenFormat::Deterministic);
|
|
|
|
let c1 = CardData::new(VALID_VISA.to_owned(), 12, 2030).unwrap();
|
|
let c2 = CardData::new(VALID_VISA.to_owned(), 12, 2030).unwrap();
|
|
let t1 = vault.tokenize(c1, Some(policy)).unwrap();
|
|
let t2 = vault.tokenize(c2, Some(policy)).unwrap();
|
|
assert_eq!(t1.as_string(), t2.as_string());
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
fn vault_name_is_telemetry_only_does_not_affect_behavior() {
|
|
// Constructing two vaults with the same name doesn't make them
|
|
// share state — they're independent in-memory instances.
|
|
let a = RustVault::new("same".to_owned());
|
|
let b = RustVault::new("same".to_owned());
|
|
let card = CardData::new(VALID_VISA.to_owned(), 12, 2030).unwrap();
|
|
let token = a.tokenize(card, None).unwrap();
|
|
assert!(b.detokenize(&token).is_err());
|
|
}
|