OpenPay (1 crate, 2487 LOC, 33 tests)
op-fraud-graph Graph-based fraud detection. EntityKind (card/email/
device/IP/address/phone/BIN+last4/account) with
canonicalization + hashed EntityKey + billing↔shipping
mismatch detection. Adjacency-list FraudGraph with
idempotent upsert + edge-merge. Double-hashing Bloom
filter (capacity+FPR-sized). Union-find connected
components. PageRank with dangling-sink handling.
Louvain community detection. Shared-instrument hub
rings. Sliding ring-buffer velocity counters. Synthetic
identity heuristics (freshness + heavy-use + mail-drop).
Structuring / smurfing / mule-chain DFS. EntityRisk +
TransactionRisk + RiskBand combiner.
Smart Byte (1 crate, ~1844 LOC, 44 tests)
smart-byte-ilp Interledger Protocol v4. OER codec (length-determinant,
var-octet-string, BE u32/u64). Prepare/Fulfill/Reject
packet roundtrip with all 19 IL-RFC-27 reject codes.
SHA-256 Condition + Fulfillment with constant-time
verify. ILP address validator (7 schemes, 1-128 char
segments, 1023-byte cap, segment-aligned prefix match).
STREAM (15 frame types). BTP. SPSP payment-pointer
resolver. Open Payments API. Longest-prefix routing
Connector with fixed-point quote conversion + balance +
rate-limit checks.
EOC (1 crate, 1921 LOC, 36 tests)
eoc-quantize Quantization primitives. int8 (symmetric/asymmetric
with zero-inclusion fix), int4 (GPTQ + AWQ group quant),
int2 (BitNet), fp8 (E4M3/E5M2), nf4 (QLoRA). GGUF v3
header parse. safetensors read sketch. Calibration
datasets + per-channel scales. KV-cache quant. Energy
tradeoff curves (joules-per-token across schemes).
Co-Authored-By: Claude Opus 4.7 <[email protected]>
68 lines
1.7 KiB
Rust
68 lines
1.7 KiB
Rust
//! Integration tests for [`op_fraud_graph::VelocityCounter`].
|
|
|
|
use op_fraud_graph::{EntityKey, EntityKind, VelocityCounter, VelocityWindow};
|
|
|
|
fn k(s: &str) -> EntityKey {
|
|
EntityKey::from_raw(EntityKind::Account, s)
|
|
}
|
|
|
|
#[test]
|
|
fn counts_within_window() {
|
|
let mut c = VelocityCounter::new(VelocityWindow::default()).expect("ok");
|
|
let key = k("acc-A");
|
|
for t in 0..20 {
|
|
c.record(key, t);
|
|
}
|
|
assert_eq!(c.count(key, 19), 20);
|
|
}
|
|
|
|
#[test]
|
|
fn drops_events_outside_window() {
|
|
let mut c = VelocityCounter::new(VelocityWindow {
|
|
window_secs: 10,
|
|
bucket_secs: 1,
|
|
})
|
|
.expect("ok");
|
|
let key = k("acc-B");
|
|
for t in 0..10 {
|
|
c.record(key, t);
|
|
}
|
|
assert_eq!(c.count(key, 9), 10);
|
|
// Advance halfway: ts=14 → buckets at t=0..4 should have rolled off
|
|
// (window covers ts-9..=ts).
|
|
let later = c.count(key, 14);
|
|
assert!(later < 10 && later >= 5, "got {later}");
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_key_is_zero() {
|
|
let mut c = VelocityCounter::new(VelocityWindow::default()).expect("ok");
|
|
assert_eq!(c.count(k("missing"), 0), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn evict_stale_removes_old_entries() {
|
|
let mut c = VelocityCounter::new(VelocityWindow {
|
|
window_secs: 5,
|
|
bucket_secs: 1,
|
|
})
|
|
.expect("ok");
|
|
let key = k("acc-C");
|
|
c.record(key, 0);
|
|
assert_eq!(c.count(key, 0), 1);
|
|
c.evict_stale(1000);
|
|
// After eviction, the key should report zero (no state).
|
|
assert_eq!(c.count(key, 1000), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn multiple_keys_isolated() {
|
|
let mut c = VelocityCounter::new(VelocityWindow::default()).expect("ok");
|
|
let a = k("acc-X");
|
|
let b = k("acc-Y");
|
|
c.record(a, 0);
|
|
c.record(a, 1);
|
|
c.record(b, 0);
|
|
assert_eq!(c.count(a, 1), 2);
|
|
assert_eq!(c.count(b, 1), 1);
|
|
}
|