crypto_pay_api/api/
exchange.rs

1use async_trait::async_trait;
2
3use crate::{
4    client::CryptoBot,
5    error::CryptoBotResult,
6    models::{APIEndpoint, APIMethod, ExchangeRate, Method},
7};
8
9use super::ExchangeRateAPI;
10
11pub struct GetExchangeRatesBuilder<'a> {
12    client: &'a CryptoBot,
13}
14
15impl<'a> GetExchangeRatesBuilder<'a> {
16    pub fn new(client: &'a CryptoBot) -> Self {
17        Self { client }
18    }
19
20    /// Executes the request to get current exchange rates
21    pub async fn execute(self) -> CryptoBotResult<Vec<ExchangeRate>> {
22        #[cfg(test)]
23        if let Some(rates) = &self.client.test_rates {
24            return Ok(rates.clone());
25        }
26
27        self.client
28            .make_request(
29                &APIMethod {
30                    endpoint: APIEndpoint::GetExchangeRates,
31                    method: Method::GET,
32                },
33                None::<&()>,
34            )
35            .await
36    }
37}
38
39#[async_trait]
40impl ExchangeRateAPI for CryptoBot {
41    /// Gets current exchange rates for all supported cryptocurrencies
42    ///
43    /// This method returns exchange rates between supported cryptocurrencies and target currencies.
44    /// Exchange rates are updated regularly by CryptoBot.
45    ///
46    /// # Returns
47    /// * `GetExchangeRatesBuilder` - A builder to execute the request
48    fn get_exchange_rates(&self) -> GetExchangeRatesBuilder<'_> {
49        GetExchangeRatesBuilder::new(self)
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use mockito::Mock;
56    use rust_decimal_macros::dec;
57    use serde_json::json;
58
59    use crate::{
60        models::{CryptoCurrencyCode, FiatCurrencyCode},
61        utils::test_utils::TestContext,
62    };
63
64    use super::*;
65
66    impl TestContext {
67        pub fn mock_exchange_rates_response(&mut self) -> Mock {
68            self.server
69                .mock("GET", "/getExchangeRates")
70                .with_header("content-type", "application/json")
71                .with_header("Crypto-Pay-API-Token", "test_token")
72                .with_body(
73                    json!({
74                        "ok": true,
75                        "result": [
76                        {
77                            "is_valid": true,
78                            "is_crypto": true,
79                            "is_fiat": false,
80                            "source": "TON",
81                            "target": "USD",
82                            "rate": "3.70824926"
83                        },
84                        {
85                            "is_valid": true,
86                            "is_crypto": true,
87                            "is_fiat": false,
88                            "source": "DOGE",
89                            "target": "EUR",
90                            "rate": "0.24000835"
91                        },
92                        {
93                            "is_valid": true,
94                            "is_crypto": true,
95                            "is_fiat": false,
96                            "source": "USDT",
97                            "target": "RUB",
98                            "rate": "96.92078586"
99                        },
100                        {
101                            "is_valid": true,
102                            "is_crypto": true,
103                            "is_fiat": false,
104                            "source": "TON",
105                            "target": "EUR",
106                            "rate": "3.59048268"
107                        },
108                        ]
109                    })
110                    .to_string(),
111                )
112                .create()
113        }
114    }
115
116    #[test]
117    fn test_get_exchange_rates() {
118        let mut ctx = TestContext::new();
119        let _m = ctx.mock_exchange_rates_response();
120
121        let client = CryptoBot::builder()
122            .api_token("test_token")
123            .base_url(ctx.server.url())
124            .build()
125            .unwrap();
126
127        let result = ctx.run(async { client.get_exchange_rates().execute().await });
128
129        println!("result: {:?}", result);
130
131        assert!(result.is_ok());
132
133        let exchange_rates = result.unwrap();
134        assert_eq!(exchange_rates.len(), 4);
135        assert_eq!(exchange_rates[0].source, CryptoCurrencyCode::Ton);
136        assert_eq!(exchange_rates[0].target, FiatCurrencyCode::Usd);
137        assert_eq!(exchange_rates[0].rate, dec!(3.70824926));
138    }
139
140    #[test]
141    fn test_get_exchange_rates_from_test_client_cache() {
142        let client = CryptoBot::test_client();
143        let rt = tokio::runtime::Runtime::new().unwrap();
144        let result = rt.block_on(async { client.get_exchange_rates().execute().await });
145
146        assert!(result.is_ok());
147        let rates = result.unwrap();
148        assert_eq!(rates.len(), 2);
149        assert_eq!(rates[0].source, CryptoCurrencyCode::Ton);
150    }
151
152    #[test]
153    fn test_get_exchange_rates_cached_without_http() {
154        let client = CryptoBot::test_client();
155        let rt = tokio::runtime::Runtime::new().unwrap();
156
157        let result = rt.block_on(async { client.get_exchange_rates().execute().await });
158
159        assert!(result.is_ok());
160        assert_eq!(result.unwrap().len(), 2);
161    }
162}