Files
escarmouche/src/services/_transactions.rs
T
2023-01-25 12:50:16 -05:00

310 lines
12 KiB
Rust

use rusqlite::{Connection, ffi, params, Savepoint, Transaction};
use crate::db::accounts;
use crate::db::accounts::get_account_by_id;
use crate::db::cheques::Cheque;
use crate::db::external_id::AccountId;
use crate::util::*;
fn _move_securities(tx: &Savepoint,
cheque: &Cheque,
account_id: &AccountId) -> CustomResult<(accounts::Receipt, accounts::Receipt)> {
let drawer_receipt = accounts::add_to_account(tx, &cheque.drawer_account_id, -cheque.amount)?;
let payee_receipt = accounts::add_to_account(tx, account_id, cheque.amount)?;
if drawer_receipt.amount < 0 {
let m = format!("Drawer of {:?} does not have sufficient amount in account.", cheque);
return Err(Error::InvalidAmount(m));
}
Ok((drawer_receipt, payee_receipt))
}
fn _add_cheque_to_cashed_cheques_log(tx: &Connection,
cheque: &Cheque,
drawer_receipt: &accounts::Receipt,
payee_receipt: &accounts::Receipt) -> Result<(), Error> {
let q = "
INSERT INTO cashed_cheques(
drawer_user_id,
drawer_account_id,
drawer_account_version,
payee_user_id,
payee_account_id,
payee_account_version,
cheque_number,
amount,
underlying_security
)
VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
";
let params = params![cheque.drawer_user_id,
cheque.drawer_account_id,
drawer_receipt.version,
cheque.payee_user_id,
payee_receipt.account_id,
payee_receipt.version,
cheque.cheque_number,
cheque.amount,
cheque.security_id];
let ret = tx.execute(q, params).map_err(Error::SqlError);
match ret {
Ok(_) => Ok(()),
Err(Error::SqlError(rusqlite::Error::SqliteFailure(ffi::Error { code: _, extended_code: 2067 }, _))) =>
Err(Error::DoubleSpendingError(format!("Cheque number {} from user {} has already been cached.", cheque.cheque_number, cheque.drawer_user_id))),
Err(a) => Err(a)
}
}
fn cash_cheque_unsafe(savepoint: &Savepoint,
cheque: &Cheque,
account_id: &AccountId) -> CustomResult<()> {
if cheque.amount < 0 {
return Err(Error::InvalidAmount(format!("The cheque {:?} has a negative amount.", cheque)));
}
if !accounts::is_owner(&savepoint, &cheque.drawer_account_id, &cheque.drawer_user_id)? {
return Err(Error::InvalidDrawer(format!("The drawer is the security owner: {:?}", cheque)));
}
if !accounts::is_owner(&savepoint, account_id, &cheque.payee_user_id)? {
return Err(Error::InvalidPayee(format!("Trying to cash the cheque {:?} in \
an account {:?} that is not owned by the payee.", cheque, account_id)));
}
let (drawer_receipt, payee_receipt) = _move_securities(&savepoint, cheque, account_id)?;
_add_cheque_to_cashed_cheques_log(&savepoint,
cheque,
&drawer_receipt,
&payee_receipt)?;
Ok(())
}
fn cash_cheque(tx: &mut Transaction,
cheque: &Cheque,
account_id: &AccountId) -> CustomResult<()> {
let mut savepoint = tx.savepoint().map_err(Error::SqlError)?;
let ret = cash_cheque_unsafe(&savepoint, cheque, account_id);
match ret {
Err(_) => {
savepoint.rollback().map_err(Error::SqlError)?;
ret
}
Ok(_) => {
savepoint.commit().map_err(Error::SqlError)
}
}
}
#[cfg(test)]
mod test_caching_cheques {
use rusqlite::Connection;
use crate::db::accounts::{create_checking_account, create_security_emitter_account};
use crate::db::cheques::{Drawer, Payee};
use crate::db::external_id::{Id, UserId};
use crate::db::test_utils;
use crate::migration;
use super::*;
fn setup_conn() -> Connection {
let mut conn = Connection::open_in_memory().unwrap();
migration::apply_migrations(&mut conn).unwrap();
return conn;
}
fn setup_security_emitter() -> (Connection, Drawer, Payee, AccountId) {
let mut conn = setup_conn();
let tx = conn.transaction().unwrap();
let (users, securities, accounts) = test_utils::setup_basic_environment(&tx).unwrap();
let sec = securities.get(0).unwrap();
let drawer = users.get(0).unwrap();
let payee = users.get(1).unwrap();
let security_emitter_account = create_security_emitter_account(&tx, &sec.id(), &drawer.id()).unwrap();
let payee_account = create_checking_account(&tx, &sec.id(), &payee.id()).unwrap();
tx.commit().unwrap();
let security_emitter: Drawer = (drawer.id(), security_emitter_account.id(), sec.id());
let payee: Payee = (payee.id(), sec.id());
(conn, security_emitter, payee, payee_account.id())
}
fn _create_100_secs_in_payee_account(tx: &mut Transaction, security_emitter: &Drawer, payee: &Payee, payee_account: &AccountId) {
let cheque = Cheque::new(
security_emitter.clone(), payee.clone(), 100, 99);
cash_cheque(tx, &cheque, &payee_account).unwrap();
}
fn _create_drawer(payee: Payee, payee_account: AccountId) -> Drawer {
(payee.0, payee_account, payee.1)
}
fn setup_basic() -> (Connection, Drawer, Payee, AccountId) {
let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter();
let mut tx = conn.transaction().unwrap();
_create_100_secs_in_payee_account(&mut tx, &security_emitter, &payee, &payee_account);
let reg_account = create_checking_account(&tx, &security_emitter.2, &security_emitter.0).unwrap();
tx.commit().unwrap();
let drawer: Drawer = _create_drawer(payee, payee_account);
let new_payee: Payee = (security_emitter.0, security_emitter.2);
return (conn, drawer, new_payee, reg_account.id());
}
#[test]
fn test_happy_path_currency_emitter() -> CustomResult<()> {
let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter();
let mut tx = conn.transaction().unwrap();
let cheque = Cheque::new(
security_emitter, payee, 1000, 99);
cash_cheque(&mut tx, &cheque, &payee_account)?;
tx.commit().unwrap();
let tx = conn.transaction().unwrap();
let acc = get_account_by_id(&tx, &payee_account).unwrap();
assert_eq!(acc.raw_amount(), 1000);
Ok(())
}
#[test]
fn test_happy_path_regular_cheque() -> CustomResult<()> {
let (mut conn, drawer, payee, payee_account) = setup_basic();
let mut tx = conn.transaction().unwrap();
let cheque = Cheque::new(
drawer.clone(), payee, 25, 10001);
cash_cheque(&mut tx, &cheque, &payee_account)?;
let payee_acc = get_account_by_id(&tx, &payee_account).unwrap();
assert_eq!(payee_acc.raw_amount(), 25);
let drawer_acc = get_account_by_id(&tx, &drawer.1).unwrap();
assert_eq!(drawer_acc.raw_amount(), 75);
Ok(())
}
fn test_negative_cheque(conn: &mut Connection, drawer: Drawer, payee: Payee, payee_account: AccountId) -> CustomResult<()> {
let mut tx = conn.transaction().unwrap();
let cheque = Cheque::new(
drawer, payee, -1, 99);
let ret = cash_cheque(&mut tx, &cheque, &payee_account);
match ret {
Err(Error::InvalidAmount(_)) => Ok(()),
_ => Err(Error::Testing("Expected invalid amount error here.".to_string()))
}
}
#[test]
fn test_negative_security_emitter_cheque() -> CustomResult<()> {
let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter();
test_negative_cheque(&mut conn, security_emitter, payee, payee_account)
}
#[test]
fn test_negative_regular_cheque() -> CustomResult<()> {
let (mut conn, drawer, payee, payee_account) = setup_basic();
test_negative_cheque(&mut conn, drawer, payee, payee_account)
}
fn test_double_spend(conn: &mut Connection, drawer: Drawer, payee: Payee, payee_account: AccountId) -> CustomResult<()> {
let mut tx = conn.transaction().unwrap();
let cheque = Cheque::new(
drawer,
payee,
10,
99,
);
cash_cheque(&mut tx, &cheque, &payee_account)?;
tx.commit().unwrap();
let mut tx = conn.transaction().unwrap();
let ret = cash_cheque(&mut tx, &cheque, &payee_account);
match ret {
Err(Error::DoubleSpendingError(_)) => Ok(()),
_ => Err(Error::Testing("Expected double spending error here.".to_string()))
}
}
#[test]
fn test_double_spend_security_emitter_cheque() -> CustomResult<()> {
let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter();
test_double_spend(&mut conn, security_emitter, payee, payee_account)
}
#[test]
fn test_double_spend_regular_emitter_cheque() -> CustomResult<()> {
let (mut conn, drawer, payee, payee_account) = setup_basic();
test_double_spend(&mut conn, drawer, payee, payee_account)
}
fn test_invalid_drawer(conn: &mut Connection, drawer: Drawer, payee: Payee, payee_account: AccountId) -> CustomResult<()> {
let mut tx = conn.transaction().unwrap();
let not_currency_owner_user_id = UserId::from_id(drawer.0.get_id() + 72623);
let not_currency_owner: Drawer = (not_currency_owner_user_id, drawer.1, drawer.2);
let cheque = Cheque::new(
not_currency_owner,
payee,
100,
99,
);
let ret = cash_cheque(&mut tx, &cheque, &payee_account);
match ret {
Err(Error::InvalidDrawer(_)) => Ok(()),
_ => Err(Error::Testing("Expected invalid drawer here.".to_string()))
}
}
#[test]
fn test_invalid_drawer_security_emitter() -> CustomResult<()> {
let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter();
test_invalid_drawer(&mut conn, security_emitter, payee, payee_account)
}
#[test]
fn test_invalid_drawer_regular_cheque() -> CustomResult<()> {
let (mut conn, drawer, payee, payee_account) = setup_basic();
test_invalid_drawer(&mut conn, drawer, payee, payee_account)
}
fn test_invalid_payee(conn: &mut Connection, security_emitter: Drawer, payee: Payee) -> CustomResult<()> {
let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter();
let mut tx = conn.transaction().unwrap();
let wrong_payee_account = &security_emitter.1;
let cheque = Cheque::new(
security_emitter.clone(), payee.clone(), 1000, 99);
let ret = cash_cheque(&mut tx, &cheque, wrong_payee_account);
match ret {
Err(Error::InvalidPayee(_)) => Ok(()),
_ => Err(Error::Testing("Expected invalid payee here.".to_string()))
}
}
#[test]
fn test_invalid_payee_security_emitter_cheque() -> CustomResult<()> {
let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter();
test_invalid_payee(&mut conn, security_emitter, payee)
}
#[test]
fn test_invalid_payee_regular_cheque() -> CustomResult<()> {
let (mut conn, drawer, payee, payee_account) = setup_basic();
test_invalid_payee(&mut conn, drawer, payee)
}
}