mithril_aggregator/http_server/routes/artifact_routes/
cardano_transaction.rs1use crate::http_server::routes::middlewares;
2use crate::http_server::routes::router::RouterState;
3use warp::Filter;
4
5pub fn routes(
6 router_state: &RouterState,
7) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
8 artifact_cardano_transactions(router_state).or(artifact_cardano_transaction_by_id(router_state))
9}
10
11fn artifact_cardano_transactions(
13 router_state: &RouterState,
14) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
15 warp::path!("artifact" / "cardano-transactions")
16 .and(warp::get())
17 .and(middlewares::with_logger(router_state))
18 .and(middlewares::with_http_message_service(router_state))
19 .and_then(handlers::list_artifacts)
20}
21
22fn artifact_cardano_transaction_by_id(
24 router_state: &RouterState,
25) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
26 warp::path!("artifact" / "cardano-transaction" / String)
27 .and(warp::get())
28 .and(middlewares::with_client_metadata(router_state))
29 .and(middlewares::with_logger(router_state))
30 .and(middlewares::with_http_message_service(router_state))
31 .and(middlewares::with_metrics_service(router_state))
32 .and_then(handlers::get_artifact_by_signed_entity_id)
33}
34
35pub mod handlers {
36 use slog::{Logger, warn};
37 use std::convert::Infallible;
38 use std::sync::Arc;
39 use warp::http::StatusCode;
40
41 use crate::MetricsService;
42 use crate::http_server::routes::middlewares::ClientMetadata;
43 use crate::http_server::routes::reply;
44 use crate::services::MessageService;
45
46 pub const LIST_MAX_ITEMS: usize = 20;
47
48 pub async fn list_artifacts(
50 logger: Logger,
51 http_message_service: Arc<dyn MessageService>,
52 ) -> Result<impl warp::Reply, Infallible> {
53 match http_message_service
54 .get_cardano_transaction_list_message(LIST_MAX_ITEMS)
55 .await
56 {
57 Ok(message) => Ok(reply::json(&message, StatusCode::OK)),
58 Err(err) => {
59 warn!(logger, "list_artifacts_cardano_transactions"; "error" => ?err);
60
61 Ok(reply::server_error(err))
62 }
63 }
64 }
65
66 pub async fn get_artifact_by_signed_entity_id(
68 signed_entity_id: String,
69 client_metadata: ClientMetadata,
70 logger: Logger,
71 http_message_service: Arc<dyn MessageService>,
72 metrics_service: Arc<MetricsService>,
73 ) -> Result<impl warp::Reply, Infallible> {
74 metrics_service
75 .get_artifact_detail_cardano_transaction_total_served_since_startup()
76 .increment(&[
77 client_metadata.origin_tag.as_deref().unwrap_or_default(),
78 client_metadata.client_type.as_deref().unwrap_or_default(),
79 ]);
80
81 match http_message_service
82 .get_cardano_transaction_message(&signed_entity_id)
83 .await
84 {
85 Ok(Some(message)) => Ok(reply::json(&message, StatusCode::OK)),
86 Ok(None) => {
87 warn!(logger, "get_cardano_transaction_details::not_found");
88 Ok(reply::empty(StatusCode::NOT_FOUND))
89 }
90 Err(err) => {
91 warn!(logger, "get_cardano_transaction_details::error"; "error" => ?err);
92 Ok(reply::server_error(err))
93 }
94 }
95 }
96}
97
98#[cfg(test)]
99pub mod tests {
100 use serde_json::Value::Null;
101 use std::sync::Arc;
102 use warp::{
103 http::{Method, StatusCode},
104 test::request,
105 };
106
107 use mithril_api_spec::APISpec;
108 use mithril_common::{
109 MITHRIL_CLIENT_TYPE_HEADER, MITHRIL_ORIGIN_TAG_HEADER,
110 messages::{CardanoTransactionSnapshotListItemMessage, CardanoTransactionSnapshotMessage},
111 test::double::Dummy,
112 };
113 use mithril_persistence::sqlite::HydrationError;
114
115 use crate::{initialize_dependencies, services::MockMessageService};
116
117 use super::*;
118
119 fn setup_router(
120 state: RouterState,
121 ) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
122 let cors = warp::cors()
123 .allow_any_origin()
124 .allow_headers(vec!["content-type"])
125 .allow_methods(vec![Method::GET, Method::POST, Method::OPTIONS]);
126
127 warp::any().and(routes(&state).with(cors))
128 }
129
130 #[tokio::test]
131 async fn test_cardano_transactions_get_ok() {
132 let mut mock_http_message_service = MockMessageService::new();
133 mock_http_message_service
134 .expect_get_cardano_transaction_list_message()
135 .return_once(|_| Ok(vec![CardanoTransactionSnapshotListItemMessage::dummy()]))
136 .once();
137 let mut dependency_manager = initialize_dependencies!().await;
138 dependency_manager.message_service = Arc::new(mock_http_message_service);
139
140 let method = Method::GET.as_str();
141 let path = "/artifact/cardano-transactions";
142
143 let response = request()
144 .method(method)
145 .path(path)
146 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
147 dependency_manager,
148 ))))
149 .await;
150
151 APISpec::verify_conformity(
152 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
153 method,
154 path,
155 "application/json",
156 &Null,
157 &response,
158 &StatusCode::OK,
159 )
160 .unwrap();
161 }
162
163 #[tokio::test]
164 async fn test_cardano_transactions_get_ko() {
165 let mut mock_http_message_service = MockMessageService::new();
166 mock_http_message_service
167 .expect_get_cardano_transaction_list_message()
168 .return_once(|_| Err(HydrationError::InvalidData("invalid data".to_string()).into()))
169 .once();
170 let mut dependency_manager = initialize_dependencies!().await;
171 dependency_manager.message_service = Arc::new(mock_http_message_service);
172
173 let method = Method::GET.as_str();
174 let path = "/artifact/cardano-transactions";
175
176 let response = request()
177 .method(method)
178 .path(path)
179 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
180 dependency_manager,
181 ))))
182 .await;
183
184 APISpec::verify_conformity(
185 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
186 method,
187 path,
188 "application/json",
189 &Null,
190 &response,
191 &StatusCode::INTERNAL_SERVER_ERROR,
192 )
193 .unwrap();
194 }
195
196 #[tokio::test]
197 async fn test_cardano_transaction_increments_artifact_detail_total_served_since_startup_metric()
198 {
199 let method = Method::GET.as_str();
200 let path = "/artifact/cardano-transaction/{hash}";
201 let dependency_manager = Arc::new(initialize_dependencies!().await);
202 let initial_counter_value = dependency_manager
203 .metrics_service
204 .get_artifact_detail_cardano_transaction_total_served_since_startup()
205 .get(&["TEST", "CLI"]);
206
207 request()
208 .method(method)
209 .path(path)
210 .header(MITHRIL_ORIGIN_TAG_HEADER, "TEST")
211 .header(MITHRIL_CLIENT_TYPE_HEADER, "CLI")
212 .reply(&setup_router(RouterState::new_with_origin_tag_white_list(
213 dependency_manager.clone(),
214 &["TEST"],
215 )))
216 .await;
217
218 assert_eq!(
219 initial_counter_value + 1,
220 dependency_manager
221 .metrics_service
222 .get_artifact_detail_cardano_transaction_total_served_since_startup()
223 .get(&["TEST", "CLI"])
224 );
225 }
226
227 #[tokio::test]
228 async fn test_cardano_transaction_get_ok() {
229 let mut mock_http_message_service = MockMessageService::new();
230 mock_http_message_service
231 .expect_get_cardano_transaction_message()
232 .return_once(|_| Ok(Some(CardanoTransactionSnapshotMessage::dummy())))
233 .once();
234 let mut dependency_manager = initialize_dependencies!().await;
235 dependency_manager.message_service = Arc::new(mock_http_message_service);
236
237 let method = Method::GET.as_str();
238 let path = "/artifact/cardano-transaction/{hash}";
239
240 let response = request()
241 .method(method)
242 .path(path)
243 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
244 dependency_manager,
245 ))))
246 .await;
247
248 APISpec::verify_conformity(
249 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
250 method,
251 path,
252 "application/json",
253 &Null,
254 &response,
255 &StatusCode::OK,
256 )
257 .unwrap();
258 }
259
260 #[tokio::test]
261 async fn test_cardano_transaction_return_404_not_found_when_no_record() {
262 let mut mock_http_message_service = MockMessageService::new();
263 mock_http_message_service
264 .expect_get_cardano_transaction_message()
265 .return_once(|_| Ok(None))
266 .once();
267 let mut dependency_manager = initialize_dependencies!().await;
268 dependency_manager.message_service = Arc::new(mock_http_message_service);
269
270 let method = Method::GET.as_str();
271 let path = "/artifact/cardano-transaction/{hash}";
272
273 let response = request()
274 .method(method)
275 .path(path)
276 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
277 dependency_manager,
278 ))))
279 .await;
280
281 APISpec::verify_conformity(
282 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
283 method,
284 path,
285 "application/json",
286 &Null,
287 &response,
288 &StatusCode::NOT_FOUND,
289 )
290 .unwrap();
291 }
292
293 #[tokio::test]
294 async fn test_cardano_transaction_get_ko() {
295 let mut mock_http_message_service = MockMessageService::new();
296 mock_http_message_service
297 .expect_get_cardano_transaction_message()
298 .return_once(|_| Err(HydrationError::InvalidData("invalid data".to_string()).into()))
299 .once();
300 let mut dependency_manager = initialize_dependencies!().await;
301 dependency_manager.message_service = Arc::new(mock_http_message_service);
302
303 let method = Method::GET.as_str();
304 let path = "/artifact/cardano-transaction/{hash}";
305
306 let response = request()
307 .method(method)
308 .path(path)
309 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
310 dependency_manager,
311 ))))
312 .await;
313
314 APISpec::verify_conformity(
315 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
316 method,
317 path,
318 "application/json",
319 &Null,
320 &response,
321 &StatusCode::INTERNAL_SERVER_ERROR,
322 )
323 .unwrap();
324 }
325}