crypto_pay_api/api/
exchange.rs1use 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 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 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}