mithril_client/cardano_database_client/
fetch.rs1use std::sync::Arc;
2
3use anyhow::Context;
4use serde::de::DeserializeOwned;
5
6use crate::{
7 CardanoDatabaseSnapshot, CardanoDatabaseSnapshotListItem, MithrilResult,
8 aggregator_client::{AggregatorClient, AggregatorClientError, AggregatorRequest},
9};
10
11pub struct InternalArtifactRetriever {
12 pub(super) aggregator_client: Arc<dyn AggregatorClient>,
13}
14
15impl InternalArtifactRetriever {
16 pub fn new(aggregator_client: Arc<dyn AggregatorClient>) -> Self {
18 Self { aggregator_client }
19 }
20
21 pub async fn list(&self) -> MithrilResult<Vec<CardanoDatabaseSnapshotListItem>> {
23 let response = self
24 .aggregator_client
25 .get_content(AggregatorRequest::ListCardanoDatabaseSnapshots)
26 .await
27 .with_context(|| "CardanoDatabase client can not get the artifact list")?;
28 let items = serde_json::from_str::<Vec<CardanoDatabaseSnapshotListItem>>(&response)
29 .with_context(|| "CardanoDatabase client can not deserialize artifact list")?;
30
31 Ok(items)
32 }
33
34 pub async fn get(&self, hash: &str) -> MithrilResult<Option<CardanoDatabaseSnapshot>> {
36 self.fetch_with_aggregator_request(AggregatorRequest::GetCardanoDatabaseSnapshot {
37 hash: hash.to_string(),
38 })
39 .await
40 }
41
42 async fn fetch_with_aggregator_request<T: DeserializeOwned>(
45 &self,
46 request: AggregatorRequest,
47 ) -> MithrilResult<Option<T>> {
48 match self.aggregator_client.get_content(request).await {
49 Ok(content) => {
50 let result = serde_json::from_str(&content)
51 .with_context(|| "CardanoDatabase client can not deserialize artifact")?;
52
53 Ok(Some(result))
54 }
55 Err(AggregatorClientError::RemoteServerLogical(_)) => Ok(None),
56 Err(e) => Err(e.into()),
57 }
58 }
59}
60
61#[cfg(test)]
62mod tests {
63
64 use anyhow::anyhow;
65 use chrono::{DateTime, Utc};
66 use mockall::predicate::eq;
67
68 use mithril_common::entities::{CardanoDbBeacon, Epoch};
69 use mithril_common::test::double::Dummy;
70
71 use crate::cardano_database_client::CardanoDatabaseClientDependencyInjector;
72
73 use super::*;
74
75 fn fake_messages() -> Vec<CardanoDatabaseSnapshotListItem> {
76 vec![
77 CardanoDatabaseSnapshotListItem {
78 hash: "hash-123".to_string(),
79 merkle_root: "mkroot-123".to_string(),
80 beacon: CardanoDbBeacon {
81 epoch: Epoch(1),
82 immutable_file_number: 123,
83 },
84 certificate_hash: "cert-hash-123".to_string(),
85 total_db_size_uncompressed: 800796318,
86 created_at: DateTime::parse_from_rfc3339("2025-01-19T13:43:05.618857482Z")
87 .unwrap()
88 .with_timezone(&Utc),
89 cardano_node_version: "0.0.1".to_string(),
90 },
91 CardanoDatabaseSnapshotListItem {
92 hash: "hash-456".to_string(),
93 merkle_root: "mkroot-456".to_string(),
94 beacon: CardanoDbBeacon {
95 epoch: Epoch(2),
96 immutable_file_number: 456,
97 },
98 certificate_hash: "cert-hash-456".to_string(),
99 total_db_size_uncompressed: 2960713808,
100 created_at: DateTime::parse_from_rfc3339("2025-01-27T15:22:05.618857482Z")
101 .unwrap()
102 .with_timezone(&Utc),
103 cardano_node_version: "0.0.1".to_string(),
104 },
105 ]
106 }
107
108 mod list {
109
110 use super::*;
111
112 #[tokio::test]
113 async fn list_cardano_database_snapshots_returns_messages() {
114 let message = fake_messages();
115 let client = CardanoDatabaseClientDependencyInjector::new()
116 .with_aggregator_client_mock_config(|http_client| {
117 http_client
118 .expect_get_content()
119 .with(eq(AggregatorRequest::ListCardanoDatabaseSnapshots))
120 .return_once(move |_| Ok(serde_json::to_string(&message).unwrap()));
121 })
122 .build_cardano_database_client();
123
124 let messages = client.list().await.unwrap();
125
126 assert_eq!(2, messages.len());
127 assert_eq!("hash-123".to_string(), messages[0].hash);
128 assert_eq!("hash-456".to_string(), messages[1].hash);
129 }
130
131 #[tokio::test]
132 async fn list_cardano_database_snapshots_returns_error_when_invalid_json_structure_in_response()
133 {
134 let client = CardanoDatabaseClientDependencyInjector::new()
135 .with_aggregator_client_mock_config(|http_client| {
136 http_client
137 .expect_get_content()
138 .return_once(move |_| Ok("invalid json structure".to_string()));
139 })
140 .build_cardano_database_client();
141
142 client
143 .list()
144 .await
145 .expect_err("List Cardano databases should return an error");
146 }
147 }
148
149 mod get {
150 use super::*;
151
152 #[tokio::test]
153 async fn get_cardano_database_snapshot_returns_message() {
154 let expected_cardano_database_snapshot = CardanoDatabaseSnapshot {
155 hash: "hash-123".to_string(),
156 ..CardanoDatabaseSnapshot::dummy()
157 };
158 let message = expected_cardano_database_snapshot.clone();
159 let client = CardanoDatabaseClientDependencyInjector::new()
160 .with_aggregator_client_mock_config(|http_client| {
161 http_client
162 .expect_get_content()
163 .with(eq(AggregatorRequest::GetCardanoDatabaseSnapshot {
164 hash: "hash-123".to_string(),
165 }))
166 .return_once(move |_| Ok(serde_json::to_string(&message).unwrap()));
167 })
168 .build_cardano_database_client();
169
170 let cardano_database = client
171 .get("hash-123")
172 .await
173 .unwrap()
174 .expect("This test returns a Cardano database");
175
176 assert_eq!(expected_cardano_database_snapshot, cardano_database);
177 }
178
179 #[tokio::test]
180 async fn get_cardano_database_snapshot_returns_error_when_invalid_json_structure_in_response()
181 {
182 let client = CardanoDatabaseClientDependencyInjector::new()
183 .with_aggregator_client_mock_config(|http_client| {
184 http_client
185 .expect_get_content()
186 .return_once(move |_| Ok("invalid json structure".to_string()));
187 })
188 .build_cardano_database_client();
189
190 client
191 .get("hash-123")
192 .await
193 .expect_err("Get Cardano database should return an error");
194 }
195
196 #[tokio::test]
197 async fn get_cardano_database_snapshot_returns_none_when_not_found_or_remote_server_logical_error()
198 {
199 let client = CardanoDatabaseClientDependencyInjector::new()
200 .with_aggregator_client_mock_config(|http_client| {
201 http_client.expect_get_content().return_once(move |_| {
202 Err(AggregatorClientError::RemoteServerLogical(anyhow!(
203 "not found"
204 )))
205 });
206 })
207 .build_cardano_database_client();
208
209 let result = client.get("hash-123").await.unwrap();
210
211 assert!(result.is_none());
212 }
213
214 #[tokio::test]
215 async fn get_cardano_database_snapshot_returns_error() {
216 let client = CardanoDatabaseClientDependencyInjector::new()
217 .with_aggregator_client_mock_config(|http_client| {
218 http_client.expect_get_content().return_once(move |_| {
219 Err(AggregatorClientError::SubsystemError(anyhow!("error")))
220 });
221 })
222 .build_cardano_database_client();
223
224 client
225 .get("hash-123")
226 .await
227 .expect_err("Get Cardano database should return an error");
228 }
229 }
230}