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]>
126 lines
4.8 KiB
Rust
126 lines
4.8 KiB
Rust
//! Property tests for the native calculator.
|
|
//!
|
|
//! Three invariants we hold across any randomized rate-table + line:
|
|
//!
|
|
//! 1. **Boundedness** — total tax is ≥ 0 and ≤ the line amount
|
|
//! (since rates we test are between 0 and 100%).
|
|
//! 2. **Determinism** — calling `calculate` twice with the same inputs
|
|
//! produces the same output.
|
|
//! 3. **Additivity** — a two-line invoice yields the same total tax as
|
|
//! summing the per-line results of two single-line invoices.
|
|
|
|
use op_core::{Currency, Money};
|
|
use op_tax::{
|
|
Jurisdiction, NativeCalculator, ProductTaxCategory, RateTable, TaxCalculator, TaxContext,
|
|
TaxRate, TaxableLine,
|
|
};
|
|
use proptest::prelude::*;
|
|
use rust_decimal::Decimal;
|
|
|
|
fn strategy_rate() -> impl Strategy<Value = Decimal> {
|
|
// 0..=50% in basis points.
|
|
(0i64..=5000).prop_map(|bp| Decimal::new(bp, 4))
|
|
}
|
|
|
|
fn ctx() -> TaxContext {
|
|
TaxContext::consumer(chrono::NaiveDate::from_ymd_opt(2026, 6, 15).unwrap())
|
|
}
|
|
|
|
fn run_blocking<F, T>(future: F) -> T
|
|
where
|
|
F: std::future::Future<Output = T>,
|
|
{
|
|
tokio::runtime::Runtime::new().unwrap().block_on(future)
|
|
}
|
|
|
|
proptest! {
|
|
#![proptest_config(ProptestConfig { cases: 64, .. ProptestConfig::default() })]
|
|
|
|
#[test]
|
|
fn total_tax_bounded_by_line_amount(
|
|
rate in strategy_rate(),
|
|
amount_minor in 1i64..1_000_000,
|
|
) {
|
|
let t = RateTable::empty("prop").with(
|
|
Jurisdiction::region("US", "WA"),
|
|
ProductTaxCategory::TangibleGoods,
|
|
TaxRate::sales(rate),
|
|
);
|
|
let calc = NativeCalculator::new(t);
|
|
let line = TaxableLine {
|
|
line_id: "L1".into(),
|
|
amount: Money::from_minor(amount_minor, Currency::USD),
|
|
category: ProductTaxCategory::TangibleGoods,
|
|
ship_from: None,
|
|
ship_to: Jurisdiction::region("US", "WA"),
|
|
};
|
|
let r = run_blocking(calc.calculate(&[line], &ctx())).unwrap();
|
|
prop_assert!(r.total_tax.minor_units >= 0);
|
|
// Tax must not exceed the line amount when rate <= 100%.
|
|
prop_assert!(r.total_tax.minor_units <= amount_minor);
|
|
}
|
|
|
|
#[test]
|
|
fn deterministic_repeated(
|
|
rate in strategy_rate(),
|
|
amount_minor in 1i64..1_000_000,
|
|
) {
|
|
let t = RateTable::empty("prop").with(
|
|
Jurisdiction::region("US", "WA"),
|
|
ProductTaxCategory::TangibleGoods,
|
|
TaxRate::sales(rate),
|
|
);
|
|
let calc = NativeCalculator::new(t);
|
|
let line = TaxableLine {
|
|
line_id: "L1".into(),
|
|
amount: Money::from_minor(amount_minor, Currency::USD),
|
|
category: ProductTaxCategory::TangibleGoods,
|
|
ship_from: None,
|
|
ship_to: Jurisdiction::region("US", "WA"),
|
|
};
|
|
let r1 = run_blocking(calc.calculate(std::slice::from_ref(&line), &ctx())).unwrap();
|
|
let r2 = run_blocking(calc.calculate(&[line], &ctx())).unwrap();
|
|
prop_assert_eq!(r1.total_tax.minor_units, r2.total_tax.minor_units);
|
|
}
|
|
|
|
#[test]
|
|
fn additive_across_lines(
|
|
rate in strategy_rate(),
|
|
a in 1i64..500_000,
|
|
b in 1i64..500_000,
|
|
) {
|
|
let t = RateTable::empty("prop").with(
|
|
Jurisdiction::region("US", "WA"),
|
|
ProductTaxCategory::TangibleGoods,
|
|
TaxRate::sales(rate),
|
|
);
|
|
let calc = NativeCalculator::new(t);
|
|
let line_a = TaxableLine {
|
|
line_id: "A".into(),
|
|
amount: Money::from_minor(a, Currency::USD),
|
|
category: ProductTaxCategory::TangibleGoods,
|
|
ship_from: None,
|
|
ship_to: Jurisdiction::region("US", "WA"),
|
|
};
|
|
let line_b = TaxableLine {
|
|
line_id: "B".into(),
|
|
amount: Money::from_minor(b, Currency::USD),
|
|
category: ProductTaxCategory::TangibleGoods,
|
|
ship_from: None,
|
|
ship_to: Jurisdiction::region("US", "WA"),
|
|
};
|
|
let single_a =
|
|
run_blocking(calc.calculate(std::slice::from_ref(&line_a), &ctx())).unwrap();
|
|
let single_b =
|
|
run_blocking(calc.calculate(std::slice::from_ref(&line_b), &ctx())).unwrap();
|
|
let combined = run_blocking(calc.calculate(&[line_a, line_b], &ctx())).unwrap();
|
|
// Additivity holds modulo one minor unit per line of rounding —
|
|
// each line is rounded independently in both worlds, so the
|
|
// difference is bounded by the rounding strategy used per line
|
|
// (round-half-up, max 0.5 minor unit per side, 1 minor unit
|
|
// total when both lines round in opposite directions).
|
|
let sum = single_a.total_tax.minor_units + single_b.total_tax.minor_units;
|
|
let diff = (combined.total_tax.minor_units - sum).abs();
|
|
prop_assert!(diff <= 1, "additivity violated: combined={} single_sum={}", combined.total_tax.minor_units, sum);
|
|
}
|
|
}
|