Lines
100 %
Functions
Branches
//! Tests for the logical→physical transaction lowering.
use num_rational::Rational64;
use sqlx::types::Uuid;
use crate::command::CmdError;
use super::{LogicalSplit, PriceRow, lower_logical_split, lower_logical_transaction};
fn same_currency_ls() -> LogicalSplit {
let c = Uuid::new_v4();
LogicalSplit {
from: Uuid::new_v4(),
to: Uuid::new_v4(),
from_commodity: c,
to_commodity: c,
value: Rational64::new(100, 1),
to_amount: None,
}
fn cross_currency_ls(value: Rational64, to_amount: Rational64) -> LogicalSplit {
from_commodity: Uuid::new_v4(),
to_commodity: Uuid::new_v4(),
value,
to_amount: Some(to_amount),
#[test]
fn single_currency_produces_no_price() {
let ls = same_currency_ls();
let (from, to, price) = lower_logical_split(&ls).expect("same-currency lowering");
assert!(
price.is_none(),
"same-currency must not produce a price row"
);
assert_eq!(from.account_id, ls.from);
assert_eq!(to.account_id, ls.to);
assert_eq!(from.value, -ls.value);
assert_eq!(to.value, ls.value);
fn cross_currency_price_num_denom_and_split_linkage() {
let value = Rational64::new(100, 1);
let to_amount = Rational64::new(15000, 1);
let ls = cross_currency_ls(value, to_amount);
let (from, to, price) = lower_logical_split(&ls).expect("cross-currency lowering");
let PriceRow {
commodity_id,
currency_id,
commodity_split,
currency_split,
value_num,
value_denom,
..
} = price.expect("cross-currency must produce a price row");
// 100/15000 reduces to 1/150
assert_eq!(value_num, 1);
assert_eq!(value_denom, 150);
assert_eq!(commodity_id, ls.to_commodity);
assert_eq!(currency_id, ls.from_commodity);
// to_id is the commodity-split, from_id is the currency-split
assert_eq!(commodity_split, to.id);
assert_eq!(currency_split, from.id);
fn zero_value_is_error() {
let ls = cross_currency_ls(Rational64::new(0, 1), Rational64::new(100, 1));
let err = lower_logical_split(&ls).expect_err("zero value must error");
matches!(err, CmdError::Args(ref m) if m.contains("value must be positive")),
"expected positive-value error, got: {err:?}"
fn negative_value_is_error() {
let ls = cross_currency_ls(Rational64::new(-100, 1), Rational64::new(50, 1));
let err = lower_logical_split(&ls).expect_err("negative value must error");
fn zero_to_amount_is_error() {
let ls = cross_currency_ls(Rational64::new(100, 1), Rational64::new(0, 1));
let err = lower_logical_split(&ls).expect_err("zero to_amount must error");
matches!(err, CmdError::Args(ref m) if m.contains("to_amount must be positive")),
"expected positive-to_amount error, got: {err:?}"
fn negative_to_amount_is_error() {
let ls = cross_currency_ls(Rational64::new(100, 1), Rational64::new(-50, 1));
let err = lower_logical_split(&ls).expect_err("negative to_amount must error");
fn same_currency_with_to_amount_is_error() {
let mut ls = same_currency_ls();
ls.to_amount = Some(Rational64::new(42, 1));
let err = lower_logical_split(&ls).expect_err("same-currency to_amount must error");
matches!(err, CmdError::Args(ref m) if m.contains("only valid for cross-currency")),
"expected cross-currency-only error, got: {err:?}"
fn missing_to_amount_cross_currency_is_error() {
let ls = LogicalSplit {
};
let err = lower_logical_split(&ls).expect_err("missing to_amount must error");
matches!(err, CmdError::Args(ref m) if m.contains("to_amount")),
"expected to_amount error, got: {err:?}"
fn i64_overflow_after_reduction_is_error() {
// 4 * i64::MAX cannot fit i64 after reduction
let ls = cross_currency_ls(Rational64::new(i64::MAX, 1), Rational64::new(1, 4));
let err = lower_logical_split(&ls).expect_err("overflow must error");
matches!(err, CmdError::Args(ref m) if m.contains("overflow")),
"expected overflow error, got: {err:?}"
fn reducible_large_rate_canonicalises() {
// MAX / (MAX/2) = 2/1 after reduction
let ls = cross_currency_ls(Rational64::new(i64::MAX, 1), Rational64::new(i64::MAX, 2));
let (_, _, price) = lower_logical_split(&ls).expect("reducible large input");
let p = price.expect("cross-currency must produce price");
assert_eq!(p.value_num, 2);
assert_eq!(p.value_denom, 1);
fn lower_logical_transaction_single_currency() {
let (splits, prices) = lower_logical_transaction(&[ls]).expect("single-currency multi");
assert_eq!(splits.len(), 2);
assert!(prices.is_empty());
fn lower_logical_transaction_cross_currency() {
let ls = cross_currency_ls(Rational64::new(100, 1), Rational64::new(15000, 1));
let (splits, prices) = lower_logical_transaction(&[ls]).expect("cross-currency multi");
assert_eq!(prices.len(), 1);
fn lower_logical_transaction_empty_is_error() {
let err = lower_logical_transaction(&[]).expect_err("empty slice must error");
matches!(err, CmdError::Args(ref m) if m.contains("at least one")),
"expected at-least-one error, got: {err:?}"