going somewhere.
This commit is contained in:
1 parent
6294814f62
commit
494cdedfc6
9 files changed
+337
-103
No files matched your search
@@ -12,7 +12,8 @@ CREATE TABLE IF NOT EXISTS security_types
|
|||||||
) 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
|
||||||
(
|
(
|
||||||
@@ -24,6 +25,19 @@ CREATE TABLE IF NOT EXISTS securities
|
|||||||
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)
|
||||||
|
);
|
||||||
@@ -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
|
||||||
|
```
|
||||||
+5
-16
@@ -64,32 +64,21 @@ 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);
|
||||||
|
|||||||
+29
-11
@@ -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,
|
||||||
|
pub cheque_number: u32,
|
||||||
|
pub amount: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SecurityEmitterCheque {
|
||||||
|
fn new_safe(tx: &Transaction,
|
||||||
|
drawer_user_id: AccountId,
|
||||||
|
security_id: SecurityId,
|
||||||
|
payee_user_id: AccountId,
|
||||||
amount: u32,
|
amount: u32,
|
||||||
|
id_generator: fn() -> u32) -> Result<SecurityEmitterCheque> {
|
||||||
|
Ok(SecurityEmitterCheque {
|
||||||
|
drawer_user_id,
|
||||||
|
security_id,
|
||||||
|
payee_user_id,
|
||||||
|
cheque_number: id_generator(),
|
||||||
|
amount,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cheque {
|
pub fn assert_valid(&self, tx: &Transaction) -> Result<()> {
|
||||||
pub fn is_valid(self: &Cheque) -> bool {
|
Ok(())
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn cash_cheque(&self){
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_foo() {}
|
||||||
}
|
}
|
||||||
+104
-26
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+48
-34
@@ -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,18 +49,38 @@ 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> {
|
||||||
@@ -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, ¬_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());
|
||||||
|
|||||||
@@ -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]))
|
||||||
|
}
|
||||||
+10
-3
@@ -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};
|
||||||
}
|
}
|
||||||
@@ -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<()> {
|
||||||
fn cash_cheque(cheque: &Cheque) -> Result<()> {
|
let q = "
|
||||||
if !cheque.is_valid() {
|
INSERT INTO accounts() VALUES()
|
||||||
|
";
|
||||||
|
tx.execute(q, params![cheque.amount])?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(())
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in new issue
Block a user