Skip to main content

finance/
price.rs

1use crate::error::{FinanceError, PriceError};
2use sqlx::types::Uuid;
3use sqlx::types::chrono::{DateTime, Utc};
4use sqlx::{Connection, query_file};
5use supp_macro::Builder;
6
7#[derive(Debug, sqlx::FromRow, Builder)]
8#[builder(error_kind = "FinanceError")]
9pub struct Price {
10    pub id: Uuid,
11    pub date: DateTime<Utc>,
12    pub commodity_id: Uuid,
13    pub currency_id: Uuid,
14    pub commodity_split: Option<Uuid>,
15    pub currency_split: Option<Uuid>,
16    pub value_num: i64,
17    pub value_denom: i64,
18}
19
20impl Price {
21    pub async fn commit<E>(&self, conn: &mut E) -> Result<(), FinanceError>
22    where
23        E: Connection<Database = sqlx::Postgres>,
24    {
25        let mut tr = conn.begin().await?;
26
27        query_file!(
28            "sql/price_insert.sql",
29            &self.id,
30            &self.commodity_id,
31            &self.currency_id,
32            match &self.commodity_split {
33                &Some(s) => Some(s),
34                _ => None,
35            },
36            match &self.currency_split {
37                &Some(s) => Some(s),
38                _ => None,
39            },
40            &self.date,
41            &self.value_num,
42            &self.value_denom,
43        )
44        .execute(&mut *tr)
45        .await?;
46        tr.commit().await?;
47
48        Ok(())
49    }
50}
51
52#[cfg(test)]
53mod price_tests {
54    use super::*;
55    use crate::account::{Account, AccountBuilder};
56    use crate::commodity::{Commodity, CommodityBuilder};
57    #[cfg(feature = "testlog")]
58    use env_logger;
59    #[cfg(feature = "testlog")]
60    use log;
61    use sqlx::PgPool;
62    use sqlx::types::chrono::Local;
63    use tokio::sync::OnceCell;
64
65    /// Context for keeping environment intact
66    static CONTEXT: OnceCell<()> = OnceCell::const_new();
67    static COMMODITY_1: OnceCell<Commodity> = OnceCell::const_new();
68    static COMMODITY_2: OnceCell<Commodity> = OnceCell::const_new();
69    static ACCOUNT_1: OnceCell<Account> = OnceCell::const_new();
70    static ACCOUNT_2: OnceCell<Account> = OnceCell::const_new();
71
72    async fn setup(pool: &PgPool) {
73        CONTEXT
74            .get_or_init(|| async {
75                #[cfg(feature = "testlog")]
76                let _ = env_logger::builder()
77                    .is_test(true)
78                    .filter_level(log::LevelFilter::Trace)
79                    .try_init();
80            })
81            .await;
82
83        COMMODITY_1
84            .get_or_init(|| async { CommodityBuilder::new().id(Uuid::new_v4()).build().unwrap() })
85            .await;
86        let mut conn = pool.acquire().await.unwrap();
87        COMMODITY_1.get().unwrap().commit(&mut *conn).await.unwrap();
88
89        COMMODITY_2
90            .get_or_init(|| async { CommodityBuilder::new().id(Uuid::new_v4()).build().unwrap() })
91            .await;
92        let mut conn = pool.acquire().await.unwrap();
93        COMMODITY_2.get().unwrap().commit(&mut *conn).await.unwrap();
94
95        ACCOUNT_1
96            .get_or_init(|| async { AccountBuilder::new().id(Uuid::new_v4()).build().unwrap() })
97            .await;
98
99        let mut conn = pool.acquire().await.unwrap();
100        ACCOUNT_1.get().unwrap().commit(&mut *conn).await.unwrap();
101
102        ACCOUNT_2
103            .get_or_init(|| async { AccountBuilder::new().id(Uuid::new_v4()).build().unwrap() })
104            .await;
105
106        let mut conn = pool.acquire().await.unwrap();
107        ACCOUNT_2.get().unwrap().commit(&mut *conn).await.unwrap();
108    }
109
110    #[sqlx::test(migrations = "../migrations")]
111    async fn test_price_store(pool: PgPool) -> anyhow::Result<()> {
112        setup(&pool).await;
113        let price = Price {
114            id: Uuid::new_v4(),
115            date: Local::now().into(),
116            commodity_id: COMMODITY_1.get().unwrap().id,
117            currency_id: COMMODITY_2.get().unwrap().id,
118            commodity_split: None,
119            currency_split: None,
120            value_num: 100,
121            value_denom: 1,
122        };
123
124        let mut conn = pool.acquire().await?;
125        price.commit(&mut *conn).await?;
126
127        let mut conn = pool.acquire().await.unwrap();
128
129        let result = sqlx::query!("SELECT id FROM prices WHERE value_num = 100")
130            .fetch_one(&mut *conn)
131            .await
132            .unwrap();
133
134        assert_eq!(price.id, result.id);
135
136        Ok(())
137    }
138
139    #[sqlx::test(migrations = "../migrations")]
140    async fn test_price_builder(pool: PgPool) -> anyhow::Result<()> {
141        setup(&pool).await;
142
143        let price = Price::builder()
144            .id(Uuid::new_v4())
145            .date(Local::now().into())
146            .commodity_id(COMMODITY_1.get().unwrap().id)
147            .currency_id(COMMODITY_2.get().unwrap().id)
148            .value_num(100)
149            .value_denom(1)
150            .build()?;
151
152        assert_eq!(price.value_num, 100);
153        Ok(())
154    }
155}