going somewhere.

This commit is contained in:
didier amyot committed 2022-12-17 10:07:43 -05:00
1 parent 6294814f62
commit 494cdedfc6
9 files changed
+361 -127

No files matched your search

+36 -10
View File
@@ -9,21 +9,35 @@ CREATE TABLE IF NOT EXISTS users
CREATE TABLE IF NOT EXISTS security_types CREATE TABLE IF NOT EXISTS security_types
( (
name TEXT PRIMARY KEY name TEXT PRIMARY KEY
) STRICT ; ) STRICT;
INSERT INTO security_types(name) INSERT INTO security_types(name)
VALUES ('currency'), ('bond'); VALUES ('currency'),
('bond');
CREATE TABLE IF NOT EXISTS securities CREATE TABLE IF NOT EXISTS securities
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
security_type TEXT NOT NULL, security_type TEXT NOT NULL,
name TEXT NOT NULL UNIQUE, name TEXT NOT NULL UNIQUE,
symbol TEXT NOT NULL UNIQUE, symbol TEXT NOT NULL UNIQUE,
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP, creation_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (security_type) REFERENCES security_types (name) 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 CREATE TABLE IF NOT EXISTS accounts
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -37,7 +51,19 @@ CREATE TABLE IF NOT EXISTS accounts
FOREIGN KEY (owner) REFERENCES users (id) 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 id INTEGER PRIMARY KEY AUTOINCREMENT,
) STRICT; 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)
);
+11
View File
@@ -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
```
+16 -27
View File
@@ -11,8 +11,8 @@ pub struct Account {
} }
impl Account { impl Account {
pub fn from_row(row: &Row) -> Result<Account>{ pub fn from_row(row: &Row) -> Result<Account> {
return Ok(Account{ return Ok(Account {
id: row.get("acc_id")?, id: row.get("acc_id")?,
amount: row.get("acc_amount")?, amount: row.get("acc_amount")?,
timestamp: row.get("acc_timestamp")?, timestamp: row.get("acc_timestamp")?,
@@ -41,7 +41,7 @@ pub fn list_accounts<'a>(conn: &Connection) -> Result<Vec<Account>> {
return Ok(ret); return Ok(ret);
} }
pub fn get_account_by_id(tx: &Transaction, id: u32) -> Result<Account>{ pub fn get_account_by_id(tx: &Transaction, id: u32) -> Result<Account> {
let q = "SELECT acc.id AS acc_id, let q = "SELECT acc.id AS acc_id,
acc.amount AS acc_amount, acc.amount AS acc_amount,
UNIXEPOCH(acc.timestamp) AS acc_timestamp, UNIXEPOCH(acc.timestamp) AS acc_timestamp,
@@ -55,7 +55,7 @@ pub fn get_account_by_id(tx: &Transaction, id: u32) -> Result<Account>{
return tx.query_row(q, params![id], Account::from_row); return tx.query_row(q, params![id], Account::from_row);
} }
pub fn create_account(tx: &Transaction, sec: &Security, user: &User) -> Result<Account>{ pub fn create_account(tx: &Transaction, sec: &Security, user: &User) -> Result<Account> {
let q = "INSERT INTO accounts(security, owner, amount, timestamp) let q = "INSERT INTO accounts(security, owner, amount, timestamp)
VALUES (?1, ?2, 0, CURRENT_TIMESTAMP)"; VALUES (?1, ?2, 0, CURRENT_TIMESTAMP)";
tx.execute(q, (sec.id, user.id))?; tx.execute(q, (sec.id, user.id))?;
@@ -64,38 +64,27 @@ pub fn create_account(tx: &Transaction, sec: &Security, user: &User) -> Result<A
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use rusqlite::{Connection}; use rusqlite::Connection;
use crate::db::_users;
use crate::db::_test_utils::{setup_users_and_securities};
use crate::db::_securities; use crate::db::_securities;
use crate::db::_users;
use crate::migration; use crate::migration;
use super::*; use super::*;
fn setup_users(tx: &Transaction) -> Result<Vec<User>> {
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<Vec<Security>> {
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<User>, Vec<Security>) { fn setup() -> (Connection, Vec<User>, Vec<Security>) {
let mut conn = Connection::open_in_memory().unwrap(); let mut conn = Connection::open_in_memory().unwrap();
migration::apply_migrations(&mut conn).unwrap(); migration::apply_migrations(&mut conn).unwrap();
let tx = conn.transaction().unwrap(); let tx = conn.transaction().unwrap();
let users = setup_users(&tx).unwrap(); let (users, securities) = setup_users_and_securities(&tx).unwrap();
let securities = setup_securities(&tx).unwrap();
tx.commit().unwrap(); tx.commit().unwrap();
return (conn, users, securities); return (conn, users, securities);
} }
fn create_all_accounts(tx: & Transaction, users: & Vec<User>, secs: & Vec<Security>) -> Result<()>{ fn create_all_accounts(tx: &Transaction, users: &Vec<User>, secs: &Vec<Security>) -> Result<()> {
for sec in secs.iter() { for sec in secs.iter() {
for user in users.iter() { for user in users.iter() {
create_account(&tx, sec, user)?; create_account(&tx, sec, user)?;
@@ -105,12 +94,12 @@ mod test {
} }
#[test] #[test]
fn test_happy_path() -> Result<()>{ fn test_happy_path() -> Result<()> {
let (mut conn, users, secs) = setup(); let (mut conn, users, secs) = setup();
let tx = conn.transaction()?; let tx = conn.transaction()?;
create_all_accounts(& tx, &users, &secs)?; create_all_accounts(&tx, &users, &secs)?;
tx.commit()?; tx.commit()?;
let accs = list_accounts(& conn)?; let accs = list_accounts(&conn)?;
assert_eq!(4, accs.len()); assert_eq!(4, accs.len());
assert_eq!(1, accs.get(0).unwrap().id); assert_eq!(1, accs.get(0).unwrap().id);
assert_eq!(4, accs.get(3).unwrap().id); assert_eq!(4, accs.get(3).unwrap().id);
@@ -118,10 +107,10 @@ mod test {
} }
#[test] #[test]
fn test_happy_path_no_commit() -> Result<()>{ fn test_happy_path_no_commit() -> Result<()> {
let (mut conn, users, secs) = setup(); let (mut conn, users, secs) = setup();
create_all_accounts(& conn.transaction()?, &users, &secs)?; create_all_accounts(&conn.transaction()?, &users, &secs)?;
let accs = list_accounts(& conn)?; let accs = list_accounts(&conn)?;
assert_eq!(0, accs.len()); assert_eq!(0, accs.len());
return Ok(()); return Ok(());
} }
+31 -13
View File
@@ -1,21 +1,39 @@
use crate::db::_external_id::ExternalId; use rusqlite::{Result, Transaction};
use crate::db::_external_id::{AccountId, SecurityId};
pub struct SecurityEmitterCheque { pub struct SecurityEmitterCheque {
drawer_user_id: ExternalId, pub drawer_user_id: AccountId,
security_id: ExternalId, pub security_id: SecurityId,
cheque_number: u32, pub payee_user_id: AccountId,
amount: u32, pub cheque_number: u32,
pub amount: u32,
} }
impl Cheque { impl SecurityEmitterCheque {
pub fn is_valid(self: &Cheque) -> bool { fn new_safe(tx: &Transaction,
return true; drawer_user_id: AccountId,
security_id: SecurityId,
payee_user_id: AccountId,
amount: u32,
id_generator: fn() -> u32) -> Result<SecurityEmitterCheque> {
Ok(SecurityEmitterCheque {
drawer_user_id,
security_id,
payee_user_id,
cheque_number: id_generator(),
amount,
})
} }
pub fn assert_valid(&self, tx: &Transaction) -> Result<()> {
pub fn cash_cheque(&self){ Ok(())
} }
} }
pub mod test {
use super::*;
#[test]
fn test_foo() {}
}
+104 -26
View File
@@ -2,36 +2,34 @@ use std::io::Read;
use regex::{Captures, Regex}; use regex::{Captures, Regex};
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
struct SafeId { struct SafeId {
encrypted_id: [u8; 4], encrypted_id: [u8; 4],
signature: [u8; 4], signature: [u8; 4],
} }
#[derive(PartialEq, Debug)]
pub enum ExternalId { pub trait Id {
AccountId(SafeId), type Output;
UserId(SafeId), fn from_regex_captures(caps: &Captures) -> Option<Self::Output>;
SecurityId(SafeId), fn from_string(s: &String) -> Option<Self::Output>;
fn from_id(id: u32) -> Self::Output;
fn get_id(&self) -> u32;
} }
impl ToString for SafeId { impl ToString for SafeId {
fn to_string(&self) -> String { 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<SafeId> { fn from_regex_captures(caps: &Captures) -> Option<SafeId> {
let encrypted_id = caps.name("id")?; let encrypted_id = caps.name("id")?;
let signature = caps.name("sign")?; let signature = caps.name("sign")?;
@@ -42,13 +40,13 @@ impl SafeId {
}) })
} }
pub fn from_string(s: &String) -> Option<SafeId> { fn from_string(s: &String) -> Option<SafeId> {
let external_id_pattern = Regex::new(r"^(?P<id>\w+)-(?P<sign>\w+)$").unwrap(); let external_id_pattern = Regex::new(r"^(?P<id>\w+)-(?P<sign>\w+)$").unwrap();
let caps = external_id_pattern.captures(&s)?; let caps = external_id_pattern.captures(&s)?;
return Self::from_regex_captures(&caps); 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); let bytes = u32::to_be_bytes(id);
SafeId { SafeId {
encrypted_id: bytes, 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); 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<type>\w)-(?P<id>\w+)-(?P<sign>\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 { impl ToString for ExternalId {
fn to_string(&self) -> String { fn to_string(&self) -> String {
match self { match self {
ExternalId::UserId(sid) => format!("a-{}", sid.to_string()), ExternalId::UserId(sid) => sid.to_string(),
ExternalId::AccountId(sid) => format!("u-{}", sid.to_string()), ExternalId::AccountId(sid) => sid.to_string(),
ExternalId::SecurityId(sid) => format!("s-{}", sid.to_string()), ExternalId::SecurityId(sid) => sid.to_string(),
} }
} }
} }
@@ -79,9 +146,9 @@ impl ExternalId {
let caps = pattern.captures(&s)?; let caps = pattern.captures(&s)?;
match caps.name("type")?.as_str() { match caps.name("type")?.as_str() {
"a" => Some(ExternalId::AccountId(SafeId::from_regex_captures(&caps)?)), "a" => Some(ExternalId::AccountId(AccountId::from_regex_captures(&caps)?)),
"u" => Some(ExternalId::UserId(SafeId::from_regex_captures(&caps)?)), "u" => Some(ExternalId::UserId(UserId::from_regex_captures(&caps)?)),
"s" => Some(ExternalId::SecurityId(SafeId::from_regex_captures(&caps)?)), "s" => Some(ExternalId::SecurityId(SecurityId::from_regex_captures(&caps)?)),
_ => None _ => None
} }
} }
@@ -96,11 +163,19 @@ impl ExternalId {
} }
pub mod test_util { pub mod test {
use std::any::Any; use std::any::Any;
use super::*; 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] #[test]
fn safe_id_happy_path_1() { fn safe_id_happy_path_1() {
@@ -135,17 +210,20 @@ pub mod test_util {
ExternalId::UserId(_) => true, ExternalId::UserId(_) => true,
_ => false _ => false
}); });
assert_eq!(eid.to_string(), "u-01020304-0708090a".to_string());
let eid = ExternalId::from_string(&"a-01020304-0708090a".to_string()).unwrap(); let eid = ExternalId::from_string(&"a-01020304-0708090a".to_string()).unwrap();
assert!(match eid { assert!(match eid {
ExternalId::AccountId(_) => true, ExternalId::AccountId(_) => true,
_ => false _ => false
}); });
assert_eq!(eid.to_string(), "a-01020304-0708090a".to_string());
let eid = ExternalId::from_string(&"s-01020304-0708090a".to_string()).unwrap(); let eid = ExternalId::from_string(&"s-01020304-0708090a".to_string()).unwrap();
assert!(match eid { assert!(match eid {
ExternalId::SecurityId(_) => true, ExternalId::SecurityId(_) => true,
_ => false _ => false
}); });
assert_eq!(eid.to_string(), "s-01020304-0708090a".to_string());
} }
} }
+52 -38
View File
@@ -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 serde::Serialize;
use crate::db::users::User;
#[derive(Debug, Serialize, PartialEq)] #[derive(Debug, Serialize, PartialEq)]
@@ -48,31 +49,51 @@ impl Security {
} }
pub struct NewSecurity { pub struct NewSecurity {
security_type: SecurityType, pub security_type: SecurityType,
symbol: String, pub symbol: String,
name: String, pub name: String,
} }
pub fn create_security(tx: &Transaction, sec: &NewSecurity) -> Result<Security> { pub fn create_security(tx: &Transaction,
sec: &NewSecurity,
owner: &User) -> Result<Security> {
let q = "INSERT INTO let q = "INSERT INTO
securities(name, symbol, security_type) securities(name, symbol, security_type)
VALUES(?1, ?2, ?3)"; VALUES(?1, ?2, ?3)";
let params = (&sec.name, &sec.symbol, &sec.security_type); let params = (&sec.name, &sec.symbol, &sec.security_type);
tx.execute(q, params)?; 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<bool> {
let q = "SELECT id
FROM security_permissions
WHERE security_id = ?1 AND user_id = ?2
LIMIT 1";
let ret: Option<u32> = 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<Security> { pub fn get_security_by_id(tx: &Transaction, id: i32) -> Result<Security> {
let q = "SELECT id AS sec_id, security_type AS sec_security_type, symbol AS sec_symbol let q = "SELECT id AS sec_id, security_type AS sec_security_type, symbol AS sec_symbol
FROM securities FROM securities
WHERE id = ?1"; WHERE id = ?1";
tx.query_row(q, params![id], Security::from_row) tx.query_row(q, params![id], Security::from_row)
} }
pub fn list_securities(conn: &Connection) -> Result<Vec<Security>> { pub fn list_securities(conn: &Connection) -> Result<Vec<Security>> {
let q = "SELECT id AS sec_id, security_type AS sec_security_type, symbol AS sec_symbol let q = "SELECT id AS sec_id, security_type AS sec_security_type, symbol AS sec_symbol
FROM securities FROM securities
ORDER BY id ASC"; ORDER BY id ASC";
let mut stmt = conn.prepare(q)?; let mut stmt = conn.prepare(q)?;
let ret: Vec<Security> = stmt let ret: Vec<Security> = stmt
@@ -83,41 +104,27 @@ pub fn list_securities(conn: &Connection) -> Result<Vec<Security>> {
return Ok(ret); 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)] #[cfg(test)]
mod test { mod test {
use rusqlite::{Connection, params}; use rusqlite::{Connection, params};
use super::*; use super::*;
use super::test_util::*; use crate::db::_test_utils::*;
use crate::migration; use crate::migration;
fn setup() -> Connection { fn setup() -> (Connection, Vec<User>) {
let mut conn = Connection::open_in_memory().unwrap(); let mut conn = Connection::open_in_memory().unwrap();
migration::apply_migrations(&mut conn).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] #[test]
fn test_security_types() -> Result<(), rusqlite::Error> { fn test_security_types() -> Result<(), rusqlite::Error> {
let conn = setup(); let (mut conn, users) = setup();
let q = "CREATE TABLE test_security_types( let q = "CREATE TABLE test_security_types(
name TEXT, name TEXT,
FOREIGN KEY (name) REFERENCES security_types (name) FOREIGN KEY (name) REFERENCES security_types (name)
@@ -133,21 +140,28 @@ mod test {
#[test] #[test]
fn test_create_security() -> Result<(), rusqlite::Error> { 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 sec = mock_currency_1();
let tx = conn.transaction()?; let tx = conn.transaction()?;
create_security(&tx, &sec)?; let sec = create_security(&tx, &sec, &owner)?;
return tx.commit();
assert!(is_owner(&tx, &sec, &owner)?);
assert!(!is_owner(&tx, &sec, &not_owner)?);
tx.commit()
} }
#[test] #[test]
fn test_list_security() -> Result<(), rusqlite::Error> { 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()); assert_eq!(0, list_securities(&conn)?.len());
let sec = mock_currency_1(); let sec = mock_currency_1();
let tx = conn.transaction()?; let tx = conn.transaction()?;
create_security(&tx, &sec)?; create_security(&tx, &sec, &user)?;
tx.commit()?; tx.commit()?;
let ret = list_securities(&conn)?; let ret = list_securities(&conn)?;
let expt = Security { let expt = Security {
@@ -166,7 +180,7 @@ mod test {
symbol: "B".to_string(), symbol: "B".to_string(),
}; };
let tx = conn.transaction()?; let tx = conn.transaction()?;
create_security(&tx, &sec)?; create_security(&tx, &sec, &user)?;
tx.commit()?; tx.commit()?;
let ret = list_securities(&conn)?; let ret = list_securities(&conn)?;
assert_eq!(2, ret.len()); assert_eq!(2, ret.len());
+49
View File
@@ -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<Vec<User>> {
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<Vec<Security>> {
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<User>, Vec<Security>)>{
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<User>, Vec<Security>, Vec<Account>)> {
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]))
}
+11 -4
View File
@@ -3,9 +3,10 @@ mod _securities;
mod _accounts; mod _accounts;
mod _cheques; mod _cheques;
mod _external_id; mod _external_id;
mod _test_utils;
pub mod securities{ pub mod securities {
pub use super::_securities::{list_securities, get_security_by_id, Security}; pub use super::_securities::{get_security_by_id, list_securities, Security};
} }
pub mod users { pub mod users {
@@ -13,9 +14,15 @@ pub mod users {
} }
pub mod accounts { 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 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};
} }
+51 -9
View File
@@ -1,20 +1,62 @@
use rusqlite::{Transaction, Result}; use rusqlite::{params, Result, Transaction};
use crate::db::accounts::get_account_by_id; 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; 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(tx: &Transaction,
fn cash_cheque(cheque: &Cheque) -> Result<()> { cheque: &SecurityEmitterCheque) -> Result<()> {
if !cheque.is_valid() { cheque.assert_valid(tx)?;
// move money to new account
_add_securities_to_account(tx, cheque)?;
} // create transaction log
// create cheque log
Ok(()) Ok(())
} }
fn update_account(tx: &Transaction, account_id: u32) -> Result<()> { fn update_account(tx: &Transaction, account_id: u32) -> Result<()> {
let account = get_account_by_id(tx, account_id)?; let account = get_account_by_id(tx, account_id)?;
// let sec = get_security_by_id(tx, account.)
return Ok(()); 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(())
}
} }