1
use crate::error::{FinanceError, PriceError};
2
use sqlx::types::Uuid;
3
use sqlx::types::chrono::{DateTime, Utc};
4
use sqlx::{Connection, query_file};
5
use supp_macro::Builder;
6

            
7
#[derive(Debug, sqlx::FromRow, Builder)]
8
#[builder(error_kind = "FinanceError")]
9
pub 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

            
20
impl Price {
21
12
    pub async fn commit<E>(&self, conn: &mut E) -> Result<(), FinanceError>
22
12
    where
23
12
        E: Connection<Database = sqlx::Postgres>,
24
12
    {
25
12
        let mut tr = conn.begin().await?;
26

            
27
12
        query_file!(
28
            "sql/price_insert.sql",
29
            &self.id,
30
            &self.commodity_id,
31
            &self.currency_id,
32
12
            match &self.commodity_split {
33
10
                &Some(s) => Some(s),
34
2
                _ => None,
35
            },
36
12
            match &self.currency_split {
37
10
                &Some(s) => Some(s),
38
2
                _ => None,
39
            },
40
            &self.date,
41
            &self.value_num,
42
            &self.value_denom,
43
        )
44
12
        .execute(&mut *tr)
45
12
        .await?;
46
12
        tr.commit().await?;
47

            
48
12
        Ok(())
49
12
    }
50
}
51

            
52
#[cfg(test)]
53
mod 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
6
    async fn setup(pool: &PgPool) {
73
4
        CONTEXT
74
4
            .get_or_init(|| async {
75
                #[cfg(feature = "testlog")]
76
2
                let _ = env_logger::builder()
77
2
                    .is_test(true)
78
2
                    .filter_level(log::LevelFilter::Trace)
79
2
                    .try_init();
80
4
            })
81
4
            .await;
82

            
83
4
        COMMODITY_1
84
4
            .get_or_init(|| async {
85
2
                CommodityBuilder::new()
86
2
                    .fraction(1000)
87
2
                    .id(Uuid::new_v4())
88
2
                    .build()
89
2
                    .unwrap()
90
4
            })
91
4
            .await;
92
4
        let mut conn = pool.acquire().await.unwrap();
93
4
        COMMODITY_1.get().unwrap().commit(&mut *conn).await.unwrap();
94

            
95
4
        COMMODITY_2
96
4
            .get_or_init(|| async {
97
2
                CommodityBuilder::new()
98
2
                    .fraction(100)
99
2
                    .id(Uuid::new_v4())
100
2
                    .build()
101
2
                    .unwrap()
102
4
            })
103
4
            .await;
104
4
        let mut conn = pool.acquire().await.unwrap();
105
4
        COMMODITY_2.get().unwrap().commit(&mut *conn).await.unwrap();
106

            
107
4
        ACCOUNT_1
108
4
            .get_or_init(|| async { AccountBuilder::new().id(Uuid::new_v4()).build().unwrap() })
109
4
            .await;
110

            
111
4
        let mut conn = pool.acquire().await.unwrap();
112
4
        ACCOUNT_1.get().unwrap().commit(&mut *conn).await.unwrap();
113

            
114
4
        ACCOUNT_2
115
4
            .get_or_init(|| async { AccountBuilder::new().id(Uuid::new_v4()).build().unwrap() })
116
4
            .await;
117

            
118
4
        let mut conn = pool.acquire().await.unwrap();
119
4
        ACCOUNT_2.get().unwrap().commit(&mut *conn).await.unwrap();
120
4
    }
121

            
122
    #[sqlx::test(migrations = "../migrations")]
123
    async fn test_price_store(pool: PgPool) -> anyhow::Result<()> {
124
        setup(&pool).await;
125
        let price = Price {
126
            id: Uuid::new_v4(),
127
            date: Local::now().into(),
128
            commodity_id: COMMODITY_1.get().unwrap().id,
129
            currency_id: COMMODITY_2.get().unwrap().id,
130
            commodity_split: None,
131
            currency_split: None,
132
            value_num: 100,
133
            value_denom: 1,
134
        };
135

            
136
        let mut conn = pool.acquire().await?;
137
        price.commit(&mut *conn).await?;
138

            
139
        let mut conn = pool.acquire().await.unwrap();
140

            
141
        let result = sqlx::query!("SELECT id FROM prices WHERE value_num = 100")
142
            .fetch_one(&mut *conn)
143
            .await
144
            .unwrap();
145

            
146
        assert_eq!(price.id, result.id);
147

            
148
        Ok(())
149
    }
150

            
151
    #[sqlx::test(migrations = "../migrations")]
152
    async fn test_price_builder(pool: PgPool) -> anyhow::Result<()> {
153
        setup(&pool).await;
154

            
155
        let price = Price::builder()
156
            .id(Uuid::new_v4())
157
            .date(Local::now().into())
158
            .commodity_id(COMMODITY_1.get().unwrap().id)
159
            .currency_id(COMMODITY_2.get().unwrap().id)
160
            .value_num(100)
161
            .value_denom(1)
162
            .build()?;
163

            
164
        assert_eq!(price.value_num, 100);
165
        Ok(())
166
    }
167
}