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
6
    pub async fn commit<E>(&self, conn: &mut E) -> Result<(), FinanceError>
22
6
    where
23
6
        E: Connection<Database = sqlx::Postgres>,
24
6
    {
25
6
        let mut tr = conn.begin().await?;
26

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

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

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

            
89
2
        COMMODITY_2
90
2
            .get_or_init(|| async { CommodityBuilder::new().id(Uuid::new_v4()).build().unwrap() })
91
2
            .await;
92
2
        let mut conn = pool.acquire().await.unwrap();
93
2
        COMMODITY_2.get().unwrap().commit(&mut *conn).await.unwrap();
94

            
95
2
        ACCOUNT_1
96
2
            .get_or_init(|| async { AccountBuilder::new().id(Uuid::new_v4()).build().unwrap() })
97
2
            .await;
98

            
99
2
        let mut conn = pool.acquire().await.unwrap();
100
2
        ACCOUNT_1.get().unwrap().commit(&mut *conn).await.unwrap();
101

            
102
2
        ACCOUNT_2
103
2
            .get_or_init(|| async { AccountBuilder::new().id(Uuid::new_v4()).build().unwrap() })
104
2
            .await;
105

            
106
2
        let mut conn = pool.acquire().await.unwrap();
107
2
        ACCOUNT_2.get().unwrap().commit(&mut *conn).await.unwrap();
108
2
    }
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
}