One test fails :(

This commit is contained in:
didier amyot committed 2023-04-11 16:13:38 -04:00
1 parent 0a31f89501
commit 1f05ae1957
5 files changed
+168 -10

No files matched your search

+17 -2
View File
@@ -2,7 +2,6 @@ PRAGMA journal_mode= WAL;
CREATE TABLE IF NOT EXISTS ledger_internal
(
-- th
id INTEGER PRIMARY KEY AUTOINCREMENT,
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP,
cookie_secret TEXT NOT NULL
@@ -38,6 +37,20 @@ CREATE TABLE IF NOT EXISTS securities
FOREIGN KEY (security_type) REFERENCES security_types (name)
);
CREATE TABLE IF NOT EXISTS interest_rates
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
security_id INTEGER NOT NULL,
security_rate_id INTEGER NOT NULL,
start_date DATETIME NOT NULL,
end_date DATETIME,
rate REAL NOT NULL,
FOREIGN KEY (security_id) REFERENCES securities(id),
CONSTRAINT unique_security_rate_id UNIQUE (security_id, security_rate_id),
CHECK (end_date IS NULL OR start_date <= end_date),
CHECK ( rate >= 0 )
);
CREATE TABLE IF NOT EXISTS account_types
(
name TEXT PRIMARY KEY
@@ -60,7 +73,9 @@ CREATE TABLE IF NOT EXISTS accounts
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (security) REFERENCES securities (id),
FOREIGN KEY (account_type) REFERENCES account_types (name)
FOREIGN KEY (account_type) REFERENCES account_types (name),
CHECK (amount >= 0 OR (account_type = 'security_emitter'))
);
CREATE TABLE IF NOT EXISTS accounts_history
+11
View File
@@ -373,6 +373,13 @@ fn update_account_history(tx: &Connection, account_id: &AccountId) -> CustomResu
Ok(())
}
/// Super low level functions that needs to be wrapped in something else.
///
/// This method does NOT guarantee that all amount added into an account
/// comes from another account.
///
/// This method does NOT guarantee that checking account cannot go in the
/// negative.
pub fn add_to_account(
tx: &Connection,
account_id: &AccountId,
@@ -411,6 +418,10 @@ pub fn get_number_of_accounts(tx: &Connection,
tx.query_row(q, params, |row| row.get("count")).map_err(Error::SqlError)
}
/// Market cap is computer by summing the security emitter accounts.
/// We use this technique because the sum of the security
/// emitter accounts MUST equals (more or less very tiny interest
/// amounts) the sum of the checking accounts.
pub fn get_security_market_cap(tx: &Connection,
sec: &SecurityId) -> CustomResult<i64> {
let q = "
+29
View File
@@ -1 +1,30 @@
const _one_massive_fucking_query : &str = "
WITH RECURSIVE
effective_interest_rates
AS (SELECT ROW_NUMBER() OVER () AS id,
interest_rates.id AS shit_id,
max(13, start_date) AS start_date,
min(coalesce(end_date, 20), 20) AS end_date,
min(coalesce(end_date, 20), 20) - max(13, start_date) AS duration,
interest_rate
FROM interest_rates
WHERE ((start_date >= 13) AND NOT (start_date > 20))
OR ((start_date < 13) AND (end_date > 13 OR end_date IS NULL))
ORDER BY start_date ASC),
compound_value(compound_value_id, value, interest_rate, duration)
AS (SELECT 0, 100, 0, 0 -- the 100 is the initial value
UNION
SELECT compound_value.compound_value_id + 1,
compound_value.value *
pow(1 + effective_interest_rates.interest_rate, effective_interest_rates.duration),
effective_interest_rates.interest_rate,
effective_interest_rates.duration
FROM effective_interest_rates
JOIN compound_value ON compound_value.compound_value_id + 1 = effective_interest_rates.id)
SELECT value
FROM compound_value
ORDER BY compound_value_id DESC
LIMIT 1
";
+110 -8
View File
@@ -1,4 +1,4 @@
use rusqlite::{params, Connection, Result, Row, ToSql, Transaction, OptionalExtension};
use rusqlite::{params, Connection, OptionalExtension, Result, Row, ToSql, Transaction};
use serde::Serialize;
use crate::db::_external_id::{Id, SecurityId};
@@ -104,15 +104,93 @@ pub struct NewSecurity {
pub name: String,
}
struct InterestRate {
rate: f64,
start_date: i64,
end_date: Option<i64>,
}
impl InterestRate {
pub fn from_row(row: &Row) -> Result<InterestRate> {
Ok(InterestRate {
rate: row.get("rate")?,
start_date: row.get("start_date")?,
end_date: row.get("end_date")?,
})
}
}
fn set_initial_interest_rate(tx: &Connection, sec_id: &SecurityId) -> CustomResult<()> {
let q = "
INSERT INTO interest_rates(
security_id,
security_rate_id,
start_date,
end_date,
rate
)
SELECT id, 0, CURRENT_TIMESTAMP, NULL, 1.0
FROM securities
WHERE id = ?1 AND securities.security_type = ?2
";
tx.execute(q, params![sec_id, SecurityType::Currency]).map_err(Error::SqlError)?;
Ok(())
}
fn set_interest_rate(tx: &Connection, sec_id: &SecurityId, rate: f64) -> CustomResult<()> {
let q = "
INSERT INTO interest_rates(
security_id,
security_rate_id,
start_date,
end_date,
rate
)
SELECT ?1 AS security_id,
MAX(interest_rates.security_rate_id) + 1 AS security_rate_id,
CURRENT_TIMESTAMP AS start_date,
NULL AS end_date,
?2 AS rate
FROM interest_rates JOIN securities ON interest_rates.security_id = securities.id
WHERE interest_rates.security_id = ?1
AND securities.security_type = ?3
";
tx.execute(q, params![sec_id, rate, SecurityType::Currency])
.map_err(Error::SqlError)?;
Ok(())
}
fn get_rates(tx: &Connection, sec_id: &SecurityId) -> CustomResult<Vec<InterestRate>> {
let q = "
SELECT rate,
UNIXEPOCH(start_date) AS start_date,
UNIXEPOCH(end_date) AS end_date
FROM interest_rates
WHERE security_id = ?1
ORDER BY security_rate_id ASC";
let mut stmt = tx.prepare(q).map_err(Error::SqlError)?;
let ret: Vec<InterestRate> = stmt
.query_map(params![sec_id], InterestRate::from_row)
.map_err(Error::SqlError)?
.filter(Result::is_ok)
.map(Result::unwrap)
.collect();
return Ok(ret);
}
pub fn create_security(tx: &Connection, sec: &NewSecurity) -> CustomResult<Security> {
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).map_err(Error::SqlError)?;
let new_sec_id:i64 = tx.last_insert_rowid();
let new_sec_id = &SecurityId::from_id(tx.last_insert_rowid());
let ret = get_security_by_id(tx, &SecurityId::from_id(new_sec_id))?;
if sec.security_type == SecurityType::Currency {
set_initial_interest_rate(tx, new_sec_id)?;
}
let ret = get_security_by_id(tx, new_sec_id)?;
Ok(ret.unwrap())
}
@@ -126,7 +204,6 @@ pub fn get_security_by_id(tx: &Connection, id: & SecurityId) -> CustomResult<Opt
tx.query_row(q, params![id], Security::from_row)
.optional()
.map_err(Error::SqlError)
}
pub fn list_securities(conn: &Connection) -> CustomResult<Vec<Security>> {
@@ -184,14 +261,39 @@ mod test_securities {
}
#[test]
fn test_create_security() -> CustomResult<()> {
fn test_create_security_currency() -> CustomResult<()> {
let (mut conn, _users) = setup();
let new_sec = mock_currency_1();
let tx = conn.transaction().unwrap();
let sec = create_security(&tx, &new_sec)?;
let sec = create_security(&conn, &new_sec)?;
assert_eq!(sec.symbol, new_sec.symbol);
assert_eq!(sec.security_type, SecurityType::Currency);
tx.commit().unwrap();
let interest_rate = get_rates(&conn, &sec.id())?;
assert_eq!(interest_rate.len(), 1);
Ok(())
}
#[test]
fn test_create_security_bond() -> CustomResult<()> {
let (mut conn, _users) = setup();
let new_sec = mock_currency_2();
let sec = create_security(&conn, &new_sec)?;
assert_eq!(sec.symbol, new_sec.symbol);
assert_eq!(sec.security_type, SecurityType::Bond);
let interest_rate = get_rates(&conn, &sec.id())?;
assert_eq!(interest_rate.len(), 0);
Ok(())
}
#[test]
fn test_set_interest_rate() -> CustomResult<()> {
let (mut conn, _users) = setup();
let new_sec = mock_currency_1();
let sec = &create_security(&conn, &new_sec)?.id();
set_interest_rate(&conn, sec, 90.00)?;
let interest_rate = get_rates(&conn, sec)?;
assert_eq!(interest_rate.len(), 2);
Ok(())
}
+1
View File
@@ -459,6 +459,7 @@ mod test_caching_cheques {
let ret = cash_cheque(&mut tx, &cheque, &payee_account);
match ret {
Err(Error::InvalidAmount(_)) => Ok(()),
Err(x) => Err(Error::Testing(format!("{:?}", x))),
_ => Err(Error::Testing(
"Expected invalid amount error here.".to_string(),
)),