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