From 494cdedfc67ffc289d79b7b93831d1303a7c6130 Mon Sep 17 00:00:00 2001 From: didier amyot Date: Sat, 17 Dec 2022 10:07:43 -0500 Subject: [PATCH] going somewhere. --- migrations/create_tables_001.sql | 46 ++++++++--- src/db/README.md | 11 +++ src/db/_accounts.rs | 43 ++++------ src/db/_cheques.rs | 44 +++++++---- src/db/_external_id.rs | 130 ++++++++++++++++++++++++------- src/db/_securities.rs | 90 ++++++++++++--------- src/db/_test_utils.rs | 49 ++++++++++++ src/db/mod.rs | 15 +++- src/services/_transactions.rs | 60 +++++++++++--- 9 files changed, 361 insertions(+), 127 deletions(-) create mode 100644 src/db/README.md create mode 100644 src/db/_test_utils.rs diff --git a/migrations/create_tables_001.sql b/migrations/create_tables_001.sql index 0a3d16d..24298b7 100644 --- a/migrations/create_tables_001.sql +++ b/migrations/create_tables_001.sql @@ -9,21 +9,35 @@ CREATE TABLE IF NOT EXISTS users CREATE TABLE IF NOT EXISTS security_types ( name TEXT PRIMARY KEY -) STRICT ; +) STRICT; INSERT INTO security_types(name) -VALUES ('currency'), ('bond'); +VALUES ('currency'), + ('bond'); CREATE TABLE IF NOT EXISTS securities ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - security_type TEXT NOT NULL, - name TEXT NOT NULL UNIQUE, - symbol TEXT NOT NULL UNIQUE, - creation_time DATETIME DEFAULT CURRENT_TIMESTAMP, + id INTEGER PRIMARY KEY AUTOINCREMENT, + security_type TEXT NOT NULL, + name TEXT NOT NULL UNIQUE, + symbol TEXT NOT NULL UNIQUE, + creation_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (security_type) REFERENCES security_types (name) ); + +CREATE TABLE IF NOT EXISTS security_permissions +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + security_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + creation_time DATETIME DEFAULT CURRENT_TIMESTAMP, + + FOREIGN KEY (security_id) REFERENCES securities (id), + FOREIGN KEY (user_id) REFERENCES users (id) + +); + CREATE TABLE IF NOT EXISTS accounts ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -37,7 +51,19 @@ CREATE TABLE IF NOT EXISTS accounts FOREIGN KEY (owner) REFERENCES users (id) ); -CREATE TABLE IF NOT EXISTS account_values + +CREATE TABLE IF NOT EXISTS cashed_cheques ( - id INTEGER PRIMARY KEY AUTOINCREMENT -) STRICT; + id INTEGER PRIMARY KEY AUTOINCREMENT, + drawer_user_id INTEGER NOT NULL, + drawer_account_id INTEGER NOT NULL, + cheque_number INTEGER NOT NULL, + amount INTEGER NOT NULL, + underlying_security INTEGER NOT NULL, + creation_time DATETIME DEFAULT CURRENT_TIMESTAMP, + + + FOREIGN KEY (underlying_security) REFERENCES securities (id), + FOREIGN KEY (drawer_user_id) REFERENCES users (id), + FOREIGN KEY (drawer_account_id) REFERENCES accounts (id) +); diff --git a/src/db/README.md b/src/db/README.md new file mode 100644 index 0000000..2a08896 --- /dev/null +++ b/src/db/README.md @@ -0,0 +1,11 @@ +# DB schema + +```mermaid +erDiagram + users ||--o{ accounts : owner + users ||--o{ securities : owner + security_types ||--o{ securities: s + securities ||--o{ accounts: ps + users ||--o{ cheques: ps + securities ||--o{ cheques: ps +``` \ No newline at end of file diff --git a/src/db/_accounts.rs b/src/db/_accounts.rs index e0bea59..a385238 100644 --- a/src/db/_accounts.rs +++ b/src/db/_accounts.rs @@ -11,8 +11,8 @@ pub struct Account { } impl Account { - pub fn from_row(row: &Row) -> Result{ - return Ok(Account{ + pub fn from_row(row: &Row) -> Result { + return Ok(Account { id: row.get("acc_id")?, amount: row.get("acc_amount")?, timestamp: row.get("acc_timestamp")?, @@ -41,7 +41,7 @@ pub fn list_accounts<'a>(conn: &Connection) -> Result> { return Ok(ret); } -pub fn get_account_by_id(tx: &Transaction, id: u32) -> Result{ +pub fn get_account_by_id(tx: &Transaction, id: u32) -> Result { let q = "SELECT acc.id AS acc_id, acc.amount AS acc_amount, UNIXEPOCH(acc.timestamp) AS acc_timestamp, @@ -55,7 +55,7 @@ pub fn get_account_by_id(tx: &Transaction, id: u32) -> Result{ return tx.query_row(q, params![id], Account::from_row); } -pub fn create_account(tx: &Transaction, sec: &Security, user: &User) -> Result{ +pub fn create_account(tx: &Transaction, sec: &Security, user: &User) -> Result { let q = "INSERT INTO accounts(security, owner, amount, timestamp) VALUES (?1, ?2, 0, CURRENT_TIMESTAMP)"; tx.execute(q, (sec.id, user.id))?; @@ -64,38 +64,27 @@ pub fn create_account(tx: &Transaction, sec: &Security, user: &User) -> Result Result> { - let user_1 = _users::create_user(tx, &"bernard".to_string(), &"123".to_string())?; - let user_2 = _users::create_user(tx, &"sam".to_string(), &"abc".to_string())?; - return Ok(vec![user_1, user_2]); - } - - fn setup_securities(tx: &Transaction) -> Result> { - let sec_1 = _securities::create_security(tx, &_securities::test_util::mock_currency_1())?; - let sec_2 = _securities::create_security(tx, &_securities::test_util::mock_currency_2())?; - return Ok(vec![sec_1, sec_2]); - } - fn setup() -> (Connection, Vec, Vec) { let mut conn = Connection::open_in_memory().unwrap(); migration::apply_migrations(&mut conn).unwrap(); let tx = conn.transaction().unwrap(); - let users = setup_users(&tx).unwrap(); - let securities = setup_securities(&tx).unwrap(); + let (users, securities) = setup_users_and_securities(&tx).unwrap(); tx.commit().unwrap(); return (conn, users, securities); } - fn create_all_accounts(tx: & Transaction, users: & Vec, secs: & Vec) -> Result<()>{ + fn create_all_accounts(tx: &Transaction, users: &Vec, secs: &Vec) -> Result<()> { for sec in secs.iter() { for user in users.iter() { create_account(&tx, sec, user)?; @@ -105,12 +94,12 @@ mod test { } #[test] - fn test_happy_path() -> Result<()>{ + fn test_happy_path() -> Result<()> { let (mut conn, users, secs) = setup(); let tx = conn.transaction()?; - create_all_accounts(& tx, &users, &secs)?; + create_all_accounts(&tx, &users, &secs)?; tx.commit()?; - let accs = list_accounts(& conn)?; + let accs = list_accounts(&conn)?; assert_eq!(4, accs.len()); assert_eq!(1, accs.get(0).unwrap().id); assert_eq!(4, accs.get(3).unwrap().id); @@ -118,10 +107,10 @@ mod test { } #[test] - fn test_happy_path_no_commit() -> Result<()>{ + fn test_happy_path_no_commit() -> Result<()> { let (mut conn, users, secs) = setup(); - create_all_accounts(& conn.transaction()?, &users, &secs)?; - let accs = list_accounts(& conn)?; + create_all_accounts(&conn.transaction()?, &users, &secs)?; + let accs = list_accounts(&conn)?; assert_eq!(0, accs.len()); return Ok(()); } diff --git a/src/db/_cheques.rs b/src/db/_cheques.rs index 88f470a..a946d19 100644 --- a/src/db/_cheques.rs +++ b/src/db/_cheques.rs @@ -1,21 +1,39 @@ -use crate::db::_external_id::ExternalId; +use rusqlite::{Result, Transaction}; + +use crate::db::_external_id::{AccountId, SecurityId}; pub struct SecurityEmitterCheque { - drawer_user_id: ExternalId, - security_id: ExternalId, - cheque_number: u32, - amount: u32, + pub drawer_user_id: AccountId, + pub security_id: SecurityId, + pub payee_user_id: AccountId, + pub cheque_number: u32, + pub amount: u32, } -impl Cheque { - pub fn is_valid(self: &Cheque) -> bool { - return true; +impl SecurityEmitterCheque { + fn new_safe(tx: &Transaction, + drawer_user_id: AccountId, + security_id: SecurityId, + payee_user_id: AccountId, + amount: u32, + id_generator: fn() -> u32) -> Result { + Ok(SecurityEmitterCheque { + drawer_user_id, + security_id, + payee_user_id, + cheque_number: id_generator(), + amount, + }) } - - pub fn cash_cheque(&self){ - + pub fn assert_valid(&self, tx: &Transaction) -> Result<()> { + Ok(()) } - - } + +pub mod test { + use super::*; + + #[test] + fn test_foo() {} +} \ No newline at end of file diff --git a/src/db/_external_id.rs b/src/db/_external_id.rs index 100191a..5241eaa 100644 --- a/src/db/_external_id.rs +++ b/src/db/_external_id.rs @@ -2,36 +2,34 @@ use std::io::Read; use regex::{Captures, Regex}; - #[derive(PartialEq, Debug)] struct SafeId { encrypted_id: [u8; 4], signature: [u8; 4], } -#[derive(PartialEq, Debug)] -pub enum ExternalId { - AccountId(SafeId), - UserId(SafeId), - SecurityId(SafeId), + +pub trait Id { + type Output; + fn from_regex_captures(caps: &Captures) -> Option; + fn from_string(s: &String) -> Option; + fn from_id(id: u32) -> Self::Output; + fn get_id(&self) -> u32; } + impl ToString for SafeId { fn to_string(&self) -> String { - format!("{}-{}", hex::encode(self.encrypted_id), hex::encode(self.signature)) + format!("{}-{}", + hex::encode(self.encrypted_id), + hex::encode(self.signature)) } } -fn deserialize_str(s: &str) -> Option<[u8; 4]> { - let mut encrypted_id_bytes = [0u8; 4]; - let r = hex::decode_to_slice(s, &mut encrypted_id_bytes as &mut [u8]); - return match r { - Ok(_) => Some(encrypted_id_bytes), - Err(_) => None - }; -} -impl SafeId { +impl Id for SafeId { + type Output = Self; + fn from_regex_captures(caps: &Captures) -> Option { let encrypted_id = caps.name("id")?; let signature = caps.name("sign")?; @@ -42,13 +40,13 @@ impl SafeId { }) } - pub fn from_string(s: &String) -> Option { + fn from_string(s: &String) -> Option { let external_id_pattern = Regex::new(r"^(?P\w+)-(?P\w+)$").unwrap(); let caps = external_id_pattern.captures(&s)?; return Self::from_regex_captures(&caps); } - pub fn from_id(id: u32) -> SafeId { + fn from_id(id: u32) -> SafeId { let bytes = u32::to_be_bytes(id); SafeId { encrypted_id: bytes, @@ -56,18 +54,87 @@ impl SafeId { } } - pub fn get_id(&self) -> u32 { + fn get_id(&self) -> u32 { return u32::from_be_bytes(self.encrypted_id); } } + +macro_rules! impl_ids { + ($T:ident, $prefix:literal) => { + + impl ToString for $T { + fn to_string(&self) -> String { + format!("{}-{}", $prefix, self.0.to_string()) + } + } + + impl Id for $T { + type Output = $T; + + fn from_regex_captures(caps: &Captures) -> Option<$T> { + let type_ = caps.name("type")?; + if(type_.as_str() != $prefix){ + return None; + } + SafeId::from_regex_captures(caps).map(|x| $T(x)) + } + + fn from_string(s: &String) -> Option<$T> { + let external_id_pattern = Regex::new(r"^(?P\w)-(?P\w+)-(?P\w+)$").unwrap(); + let caps = external_id_pattern.captures(&s)?; + Self::from_regex_captures(& caps) + } + + fn from_id(id: u32) -> $T { + $T(SafeId::from_id(id)) + } + + fn get_id(&self) -> u32 { + self.0.get_id() + } + } + } +} + +#[derive(PartialEq, Debug)] +pub struct AccountId(SafeId); + +#[derive(PartialEq, Debug)] +pub struct UserId(SafeId); + +#[derive(PartialEq, Debug)] +pub struct SecurityId(SafeId); + +impl_ids!(AccountId, "a"); +impl_ids!(UserId, "u"); +impl_ids!(SecurityId, "s"); + +#[derive(PartialEq, Debug)] +pub enum ExternalId { + AccountId(AccountId), + UserId(UserId), + SecurityId(SecurityId), +} + + +fn deserialize_str(s: &str) -> Option<[u8; 4]> { + let mut encrypted_id_bytes = [0u8; 4]; + let r = hex::decode_to_slice(s, &mut encrypted_id_bytes as &mut [u8]); + return match r { + Ok(_) => Some(encrypted_id_bytes), + Err(_) => None + }; +} + + impl ToString for ExternalId { fn to_string(&self) -> String { match self { - ExternalId::UserId(sid) => format!("a-{}", sid.to_string()), - ExternalId::AccountId(sid) => format!("u-{}", sid.to_string()), - ExternalId::SecurityId(sid) => format!("s-{}", sid.to_string()), + ExternalId::UserId(sid) => sid.to_string(), + ExternalId::AccountId(sid) => sid.to_string(), + ExternalId::SecurityId(sid) => sid.to_string(), } } } @@ -79,9 +146,9 @@ impl ExternalId { let caps = pattern.captures(&s)?; match caps.name("type")?.as_str() { - "a" => Some(ExternalId::AccountId(SafeId::from_regex_captures(&caps)?)), - "u" => Some(ExternalId::UserId(SafeId::from_regex_captures(&caps)?)), - "s" => Some(ExternalId::SecurityId(SafeId::from_regex_captures(&caps)?)), + "a" => Some(ExternalId::AccountId(AccountId::from_regex_captures(&caps)?)), + "u" => Some(ExternalId::UserId(UserId::from_regex_captures(&caps)?)), + "s" => Some(ExternalId::SecurityId(SecurityId::from_regex_captures(&caps)?)), _ => None } } @@ -96,11 +163,19 @@ impl ExternalId { } -pub mod test_util { +pub mod test { use std::any::Any; use super::*; + #[test] + fn test_id_macro() { + let id = SecurityId::from_id(33); + assert_eq!(id.to_string(), "s-00000021-00000021".to_string()); + assert!(SecurityId::from_string(&"s-00000021-00000021".to_string()).is_some()); + assert!(SecurityId::from_string(&"u-00000021-00000021".to_string()).is_none()); + assert!(SecurityId::from_string(&"00000021-00000021".to_string()).is_none()); + } #[test] fn safe_id_happy_path_1() { @@ -135,17 +210,20 @@ pub mod test_util { ExternalId::UserId(_) => true, _ => false }); + assert_eq!(eid.to_string(), "u-01020304-0708090a".to_string()); let eid = ExternalId::from_string(&"a-01020304-0708090a".to_string()).unwrap(); assert!(match eid { ExternalId::AccountId(_) => true, _ => false }); + assert_eq!(eid.to_string(), "a-01020304-0708090a".to_string()); let eid = ExternalId::from_string(&"s-01020304-0708090a".to_string()).unwrap(); assert!(match eid { ExternalId::SecurityId(_) => true, _ => false }); + assert_eq!(eid.to_string(), "s-01020304-0708090a".to_string()); } } \ No newline at end of file diff --git a/src/db/_securities.rs b/src/db/_securities.rs index 08ec95a..9007847 100644 --- a/src/db/_securities.rs +++ b/src/db/_securities.rs @@ -1,5 +1,6 @@ -use rusqlite::{Connection, params, Result, Row, ToSql, Transaction}; +use rusqlite::{Connection, OptionalExtension, params, Result, Row, ToSql, Transaction}; use serde::Serialize; +use crate::db::users::User; #[derive(Debug, Serialize, PartialEq)] @@ -48,31 +49,51 @@ impl Security { } pub struct NewSecurity { - security_type: SecurityType, - symbol: String, - name: String, + pub security_type: SecurityType, + pub symbol: String, + pub name: String, } -pub fn create_security(tx: &Transaction, sec: &NewSecurity) -> Result { +pub fn create_security(tx: &Transaction, + sec: &NewSecurity, + owner: &User) -> Result { let q = "INSERT INTO securities(name, symbol, security_type) VALUES(?1, ?2, ?3)"; let params = (&sec.name, &sec.symbol, &sec.security_type); tx.execute(q, params)?; - return get_security_by_id(tx, tx.last_insert_rowid() as i32); + let new_sec_id = tx.last_insert_rowid() as i32; + + let q = "INSERT INTO + security_permissions(security_id, user_id) + VALUES(?1, ?2)"; + tx.execute(q, params![new_sec_id, owner.id])?; + + return get_security_by_id(tx, new_sec_id); +} + +pub fn is_owner(tx: &Transaction, sec: &Security, user: &User) -> Result { + let q = "SELECT id + FROM security_permissions + WHERE security_id = ?1 AND user_id = ?2 + LIMIT 1"; + let ret: Option = tx.query_row(q, + params![sec.id, user.id], + |r| r.get(0)).optional()?; + return Ok(ret.is_some()); } pub fn get_security_by_id(tx: &Transaction, id: i32) -> Result { let q = "SELECT id AS sec_id, security_type AS sec_security_type, symbol AS sec_symbol - FROM securities - WHERE id = ?1"; + FROM securities + WHERE id = ?1"; tx.query_row(q, params![id], Security::from_row) } pub fn list_securities(conn: &Connection) -> Result> { let q = "SELECT id AS sec_id, security_type AS sec_security_type, symbol AS sec_symbol - FROM securities - ORDER BY id ASC"; + FROM securities + ORDER BY id ASC"; let mut stmt = conn.prepare(q)?; let ret: Vec = stmt @@ -83,41 +104,27 @@ pub fn list_securities(conn: &Connection) -> Result> { return Ok(ret); } -pub mod test_util { - use super::*; - pub fn mock_currency_1() -> NewSecurity { - return NewSecurity { - security_type: SecurityType::Currency, - symbol: "P".to_string(), - name: "piastre".to_string(), - }; - } - - pub fn mock_currency_2() -> NewSecurity { - return NewSecurity { - security_type: SecurityType::Bond, - symbol: "B".to_string(), - name: "bong".to_string(), - }; - } -} #[cfg(test)] mod test { use rusqlite::{Connection, params}; use super::*; - use super::test_util::*; + use crate::db::_test_utils::*; use crate::migration; - fn setup() -> Connection { + fn setup() -> (Connection, Vec) { let mut conn = Connection::open_in_memory().unwrap(); migration::apply_migrations(&mut conn).unwrap(); - return conn; + let tx = conn.transaction().unwrap(); + let users = setup_users(&tx).unwrap(); + tx.commit().unwrap(); + + return (conn, users); } #[test] fn test_security_types() -> Result<(), rusqlite::Error> { - let conn = setup(); + let (mut conn, users) = setup(); let q = "CREATE TABLE test_security_types( name TEXT, FOREIGN KEY (name) REFERENCES security_types (name) @@ -133,21 +140,28 @@ mod test { #[test] fn test_create_security() -> Result<(), rusqlite::Error> { - let mut conn = setup(); + let (mut conn, users) = setup(); + let owner = users.get(0).unwrap(); + let not_owner = users.get(1).unwrap(); let sec = mock_currency_1(); let tx = conn.transaction()?; - create_security(&tx, &sec)?; - return tx.commit(); + let sec = create_security(&tx, &sec, &owner)?; + + assert!(is_owner(&tx, &sec, &owner)?); + assert!(!is_owner(&tx, &sec, ¬_owner)?); + + tx.commit() } #[test] fn test_list_security() -> Result<(), rusqlite::Error> { - let mut conn = setup(); + let (mut conn, users) = setup(); + let user = users.get(0).unwrap(); assert_eq!(0, list_securities(&conn)?.len()); let sec = mock_currency_1(); let tx = conn.transaction()?; - create_security(&tx, &sec)?; + create_security(&tx, &sec, &user)?; tx.commit()?; let ret = list_securities(&conn)?; let expt = Security { @@ -166,7 +180,7 @@ mod test { symbol: "B".to_string(), }; let tx = conn.transaction()?; - create_security(&tx, &sec)?; + create_security(&tx, &sec, &user)?; tx.commit()?; let ret = list_securities(&conn)?; assert_eq!(2, ret.len()); diff --git a/src/db/_test_utils.rs b/src/db/_test_utils.rs new file mode 100644 index 0000000..cf9f084 --- /dev/null +++ b/src/db/_test_utils.rs @@ -0,0 +1,49 @@ +use rusqlite::{Result, Transaction}; + +use crate::db::{_securities, _users}; +use crate::db::_accounts::create_account; +use crate::db::_securities::{NewSecurity, Security, SecurityType}; +use crate::db::_users::User; +use crate::db::accounts::Account; + +pub fn mock_currency_1() -> NewSecurity { + return NewSecurity { + security_type: SecurityType::Currency, + symbol: "P".to_string(), + name: "piastre".to_string(), + }; +} + +pub fn mock_currency_2() -> NewSecurity { + return NewSecurity { + security_type: SecurityType::Bond, + symbol: "B".to_string(), + name: "bong".to_string(), + }; +} + +pub fn setup_users(tx: &Transaction) -> Result> { + let user_1 = _users::create_user(tx, &"bernard".to_string(), &"123".to_string())?; + let user_2 = _users::create_user(tx, &"sam".to_string(), &"abc".to_string())?; + return Ok(vec![user_1, user_2]); +} + +pub fn setup_securities(tx: &Transaction, owner: &User) -> Result> { + let sec_1 = _securities::create_security(tx, &mock_currency_1(), owner)?; + let sec_2 = _securities::create_security(tx, &mock_currency_2(), owner)?; + return Ok(vec![sec_1, sec_2]); +} + +pub fn setup_users_and_securities(tx: &Transaction) -> Result<(Vec, Vec)>{ + let users = setup_users(tx)?; + let securities = setup_securities(tx, users.get(0).unwrap())?; + Ok((users, securities)) +} + +pub fn setup_basic_environment(tx: &Transaction) -> Result<(Vec, Vec, Vec)> { + let (users, securities) = setup_users_and_securities(tx)?; + + let acc1 = create_account(tx, securities.get(0).unwrap(), users.get(0).unwrap())?; + let acc2 = create_account(tx, securities.get(1).unwrap(), users.get(1).unwrap())?; + Ok((users, securities, vec![acc1, acc2])) +} \ No newline at end of file diff --git a/src/db/mod.rs b/src/db/mod.rs index 5177021..3bfa073 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -3,9 +3,10 @@ mod _securities; mod _accounts; mod _cheques; mod _external_id; +mod _test_utils; -pub mod securities{ - pub use super::_securities::{list_securities, get_security_by_id, Security}; +pub mod securities { + pub use super::_securities::{get_security_by_id, list_securities, Security}; } pub mod users { @@ -13,9 +14,15 @@ pub mod users { } pub mod accounts { - pub use super::_accounts::{get_account_by_id, list_accounts, create_account, Account}; + pub use super::_accounts::{Account, create_account, get_account_by_id, list_accounts}; } pub mod cheques { - pub use super::_cheques::{Cheque}; + pub use super::_cheques::SecurityEmitterCheque; +} + +pub mod test_utils { + pub use super::_test_utils::{mock_currency_1, + mock_currency_2, + setup_basic_environment}; } \ No newline at end of file diff --git a/src/services/_transactions.rs b/src/services/_transactions.rs index 4e757fb..510e054 100644 --- a/src/services/_transactions.rs +++ b/src/services/_transactions.rs @@ -1,20 +1,62 @@ -use rusqlite::{Transaction, Result}; +use rusqlite::{params, Result, Transaction}; + use crate::db::accounts::get_account_by_id; -use crate::db::cheques::Cheque; +use crate::db::cheques::SecurityEmitterCheque; use crate::db::securities::get_security_by_id; +fn _add_securities_to_account(tx: &Transaction, + cheque: &SecurityEmitterCheque) -> Result<()> { + let q = " + INSERT INTO accounts() VALUES() + "; + tx.execute(q, params![cheque.amount])?; + Ok(()) +} - -fn cash_cheque(cheque: &Cheque) -> Result<()> { - if !cheque.is_valid() { - - - } +fn cash_cheque(tx: &Transaction, + cheque: &SecurityEmitterCheque) -> Result<()> { + cheque.assert_valid(tx)?; + // move money to new account + _add_securities_to_account(tx, cheque)?; + // create transaction log + // create cheque log Ok(()) } fn update_account(tx: &Transaction, account_id: u32) -> Result<()> { let account = get_account_by_id(tx, account_id)?; - // let sec = get_security_by_id(tx, account.) return Ok(()); +} + + +#[cfg(test)] +mod test { + use rusqlite::{Connection, params, Result}; + + use crate::db::test_utils; + use crate::migration; + + use super::*; + + fn setup() -> Connection { + let mut conn = Connection::open_in_memory().unwrap(); + migration::apply_migrations(&mut conn).unwrap(); + return conn; + } + + #[test] + fn test_basic() -> Result<()> { + let mut conn = setup(); + let tx = conn.transaction()?; + let (users, securities, accounts) = test_utils::setup_basic_environment(&tx)?; + tx.commit()?; + + let tx = conn.transaction()?; + // let cheque = SecurityEmitterCheque{ + // drawer_user_id: users.get_unchecked(0).id + // }; + + Ok(()) + } + } \ No newline at end of file