mithril_client/
cardano_stake_distribution_client.rs1use anyhow::Context;
71use std::sync::Arc;
72
73use crate::aggregator_client::{AggregatorClient, AggregatorClientError, AggregatorRequest};
74use crate::common::Epoch;
75use crate::{CardanoStakeDistribution, CardanoStakeDistributionListItem, MithrilResult};
76
77pub struct CardanoStakeDistributionClient {
79 aggregator_client: Arc<dyn AggregatorClient>,
80}
81
82impl CardanoStakeDistributionClient {
83 pub fn new(aggregator_client: Arc<dyn AggregatorClient>) -> Self {
85 Self { aggregator_client }
86 }
87
88 pub async fn list(&self) -> MithrilResult<Vec<CardanoStakeDistributionListItem>> {
90 let response = self
91 .aggregator_client
92 .get_content(AggregatorRequest::ListCardanoStakeDistributions)
93 .await
94 .with_context(|| "CardanoStakeDistribution client can not get the artifact list")?;
95 let items = serde_json::from_str::<Vec<CardanoStakeDistributionListItem>>(&response)
96 .with_context(|| "CardanoStakeDistribution client can not deserialize artifact list")?;
97
98 Ok(items)
99 }
100
101 pub async fn get(&self, hash: &str) -> MithrilResult<Option<CardanoStakeDistribution>> {
103 self.fetch_with_aggregator_request(AggregatorRequest::GetCardanoStakeDistribution {
104 hash: hash.to_string(),
105 })
106 .await
107 }
108
109 pub async fn get_by_epoch(
111 &self,
112 epoch: Epoch,
113 ) -> MithrilResult<Option<CardanoStakeDistribution>> {
114 self.fetch_with_aggregator_request(AggregatorRequest::GetCardanoStakeDistributionByEpoch {
115 epoch,
116 })
117 .await
118 }
119
120 async fn fetch_with_aggregator_request(
123 &self,
124 request: AggregatorRequest,
125 ) -> MithrilResult<Option<CardanoStakeDistribution>> {
126 match self.aggregator_client.get_content(request).await {
127 Ok(content) => {
128 let cardano_stake_distribution: CardanoStakeDistribution =
129 serde_json::from_str(&content).with_context(|| {
130 "CardanoStakeDistribution client can not deserialize artifact"
131 })?;
132
133 Ok(Some(cardano_stake_distribution))
134 }
135 Err(AggregatorClientError::RemoteServerLogical(_)) => Ok(None),
136 Err(e) => Err(e.into()),
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use anyhow::anyhow;
144 use chrono::{DateTime, Utc};
145 use mockall::predicate::eq;
146
147 use crate::aggregator_client::MockAggregatorClient;
148 use crate::common::StakeDistribution;
149
150 use super::*;
151
152 fn fake_messages() -> Vec<CardanoStakeDistributionListItem> {
153 vec![
154 CardanoStakeDistributionListItem {
155 epoch: Epoch(1),
156 hash: "hash-123".to_string(),
157 certificate_hash: "cert-hash-123".to_string(),
158 created_at: DateTime::parse_from_rfc3339("2024-08-06T12:13:05.618857482Z")
159 .unwrap()
160 .with_timezone(&Utc),
161 },
162 CardanoStakeDistributionListItem {
163 epoch: Epoch(2),
164 hash: "hash-456".to_string(),
165 certificate_hash: "cert-hash-456".to_string(),
166 created_at: DateTime::parse_from_rfc3339("2024-08-06T12:13:05.618857482Z")
167 .unwrap()
168 .with_timezone(&Utc),
169 },
170 ]
171 }
172
173 #[tokio::test]
174 async fn list_cardano_stake_distributions_returns_messages() {
175 let message = fake_messages();
176 let mut http_client = MockAggregatorClient::new();
177 http_client
178 .expect_get_content()
179 .with(eq(AggregatorRequest::ListCardanoStakeDistributions))
180 .return_once(move |_| Ok(serde_json::to_string(&message).unwrap()));
181 let client = CardanoStakeDistributionClient::new(Arc::new(http_client));
182
183 let messages = client.list().await.unwrap();
184
185 assert_eq!(2, messages.len());
186 assert_eq!("hash-123".to_string(), messages[0].hash);
187 assert_eq!("hash-456".to_string(), messages[1].hash);
188 }
189
190 #[tokio::test]
191 async fn list_cardano_stake_distributions_returns_error_when_invalid_json_structure_in_response(
192 ) {
193 let mut http_client = MockAggregatorClient::new();
194 http_client
195 .expect_get_content()
196 .return_once(move |_| Ok("invalid json structure".to_string()));
197 let client = CardanoStakeDistributionClient::new(Arc::new(http_client));
198
199 client
200 .list()
201 .await
202 .expect_err("List Cardano stake distributions should return an error");
203 }
204
205 #[tokio::test]
206 async fn get_cardano_stake_distribution_returns_message() {
207 let expected_stake_distribution = StakeDistribution::from([("pool123".to_string(), 123)]);
208 let message = CardanoStakeDistribution {
209 epoch: Epoch(3),
210 hash: "hash-123".to_string(),
211 certificate_hash: "certificate-hash-123".to_string(),
212 stake_distribution: expected_stake_distribution.clone(),
213 created_at: DateTime::<Utc>::default(),
214 };
215 let mut http_client = MockAggregatorClient::new();
216 http_client
217 .expect_get_content()
218 .with(eq(AggregatorRequest::GetCardanoStakeDistribution {
219 hash: "hash-123".to_string(),
220 }))
221 .return_once(move |_| Ok(serde_json::to_string(&message).unwrap()));
222 let client = CardanoStakeDistributionClient::new(Arc::new(http_client));
223
224 let cardano_stake_distribution = client
225 .get("hash-123")
226 .await
227 .unwrap()
228 .expect("This test returns a Cardano stake distribution");
229
230 assert_eq!("hash-123".to_string(), cardano_stake_distribution.hash);
231 assert_eq!(Epoch(3), cardano_stake_distribution.epoch);
232 assert_eq!(
233 expected_stake_distribution,
234 cardano_stake_distribution.stake_distribution
235 );
236 }
237
238 #[tokio::test]
239 async fn get_cardano_stake_distribution_returns_error_when_invalid_json_structure_in_response()
240 {
241 let mut http_client = MockAggregatorClient::new();
242 http_client
243 .expect_get_content()
244 .return_once(move |_| Ok("invalid json structure".to_string()));
245 let client = CardanoStakeDistributionClient::new(Arc::new(http_client));
246
247 client
248 .get("hash-123")
249 .await
250 .expect_err("Get Cardano stake distribution should return an error");
251 }
252
253 #[tokio::test]
254 async fn get_cardano_stake_distribution_returns_none_when_not_found_or_remote_server_logical_error(
255 ) {
256 let mut http_client = MockAggregatorClient::new();
257 http_client.expect_get_content().return_once(move |_| {
258 Err(AggregatorClientError::RemoteServerLogical(anyhow!(
259 "not found"
260 )))
261 });
262 let client = CardanoStakeDistributionClient::new(Arc::new(http_client));
263
264 let result = client.get("hash-123").await.unwrap();
265
266 assert!(result.is_none());
267 }
268
269 #[tokio::test]
270 async fn get_cardano_stake_distribution_returns_error() {
271 let mut http_client = MockAggregatorClient::new();
272 http_client
273 .expect_get_content()
274 .return_once(move |_| Err(AggregatorClientError::SubsystemError(anyhow!("error"))));
275 let client = CardanoStakeDistributionClient::new(Arc::new(http_client));
276
277 client
278 .get("hash-123")
279 .await
280 .expect_err("Get Cardano stake distribution should return an error");
281 }
282
283 #[tokio::test]
284 async fn get_cardano_stake_distribution_by_epoch_returns_message() {
285 let expected_stake_distribution = StakeDistribution::from([("pool123".to_string(), 123)]);
286 let message = CardanoStakeDistribution {
287 epoch: Epoch(3),
288 hash: "hash-123".to_string(),
289 certificate_hash: "certificate-hash-123".to_string(),
290 stake_distribution: expected_stake_distribution.clone(),
291 created_at: DateTime::<Utc>::default(),
292 };
293 let mut http_client = MockAggregatorClient::new();
294 http_client
295 .expect_get_content()
296 .with(eq(AggregatorRequest::GetCardanoStakeDistributionByEpoch {
297 epoch: Epoch(3),
298 }))
299 .return_once(move |_| Ok(serde_json::to_string(&message).unwrap()));
300 let client = CardanoStakeDistributionClient::new(Arc::new(http_client));
301
302 let cardano_stake_distribution = client
303 .get_by_epoch(Epoch(3))
304 .await
305 .unwrap()
306 .expect("This test returns a Cardano stake distribution");
307
308 assert_eq!("hash-123".to_string(), cardano_stake_distribution.hash);
309 assert_eq!(Epoch(3), cardano_stake_distribution.epoch);
310 assert_eq!(
311 expected_stake_distribution,
312 cardano_stake_distribution.stake_distribution
313 );
314 }
315
316 #[tokio::test]
317 async fn get_cardano_stake_distribution_by_epoch_returns_error_when_invalid_json_structure_in_response(
318 ) {
319 let mut http_client = MockAggregatorClient::new();
320 http_client
321 .expect_get_content()
322 .return_once(move |_| Ok("invalid json structure".to_string()));
323 let client = CardanoStakeDistributionClient::new(Arc::new(http_client));
324
325 client
326 .get_by_epoch(Epoch(3))
327 .await
328 .expect_err("Get Cardano stake distribution by epoch should return an error");
329 }
330
331 #[tokio::test]
332 async fn get_cardano_stake_distribution_by_epoch_returns_none_when_not_found_or_remote_server_logical_error(
333 ) {
334 let mut http_client = MockAggregatorClient::new();
335 http_client.expect_get_content().return_once(move |_| {
336 Err(AggregatorClientError::RemoteServerLogical(anyhow!(
337 "not found"
338 )))
339 });
340 let client = CardanoStakeDistributionClient::new(Arc::new(http_client));
341
342 let result = client.get_by_epoch(Epoch(3)).await.unwrap();
343
344 assert!(result.is_none());
345 }
346
347 #[tokio::test]
348 async fn get_cardano_stake_distribution_by_epoch_returns_error() {
349 let mut http_client = MockAggregatorClient::new();
350 http_client
351 .expect_get_content()
352 .return_once(move |_| Err(AggregatorClientError::SubsystemError(anyhow!("error"))));
353 let client = CardanoStakeDistributionClient::new(Arc::new(http_client));
354
355 client
356 .get_by_epoch(Epoch(3))
357 .await
358 .expect_err("Get Cardano stake distribution by epoch should return an error");
359 }
360}