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 };
112 use mithril_persistence::sqlite::HydrationError;
113
114 use crate::{initialize_dependencies, services::MockMessageService};
115
116 use super::*;
117
118 fn setup_router(
119 state: RouterState,
120 ) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
121 let cors = warp::cors()
122 .allow_any_origin()
123 .allow_headers(vec!["content-type"])
124 .allow_methods(vec![Method::GET, Method::POST, Method::OPTIONS]);
125
126 warp::any().and(routes(&state).with(cors))
127 }
128
129 #[tokio::test]
130 async fn test_cardano_transactions_get_ok() {
131 let mut mock_http_message_service = MockMessageService::new();
132 mock_http_message_service
133 .expect_get_cardano_transaction_list_message()
134 .return_once(|_| Ok(vec![CardanoTransactionSnapshotListItemMessage::dummy()]))
135 .once();
136 let mut dependency_manager = initialize_dependencies!().await;
137 dependency_manager.message_service = Arc::new(mock_http_message_service);
138
139 let method = Method::GET.as_str();
140 let path = "/artifact/cardano-transactions";
141
142 let response = request()
143 .method(method)
144 .path(path)
145 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
146 dependency_manager,
147 ))))
148 .await;
149
150 APISpec::verify_conformity(
151 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
152 method,
153 path,
154 "application/json",
155 &Null,
156 &response,
157 &StatusCode::OK,
158 )
159 .unwrap();
160 }
161
162 #[tokio::test]
163 async fn test_cardano_transactions_get_ko() {
164 let mut mock_http_message_service = MockMessageService::new();
165 mock_http_message_service
166 .expect_get_cardano_transaction_list_message()
167 .return_once(|_| Err(HydrationError::InvalidData("invalid data".to_string()).into()))
168 .once();
169 let mut dependency_manager = initialize_dependencies!().await;
170 dependency_manager.message_service = Arc::new(mock_http_message_service);
171
172 let method = Method::GET.as_str();
173 let path = "/artifact/cardano-transactions";
174
175 let response = request()
176 .method(method)
177 .path(path)
178 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
179 dependency_manager,
180 ))))
181 .await;
182
183 APISpec::verify_conformity(
184 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
185 method,
186 path,
187 "application/json",
188 &Null,
189 &response,
190 &StatusCode::INTERNAL_SERVER_ERROR,
191 )
192 .unwrap();
193 }
194
195 #[tokio::test]
196 async fn test_cardano_transaction_increments_artifact_detail_total_served_since_startup_metric()
197 {
198 let method = Method::GET.as_str();
199 let path = "/artifact/cardano-transaction/{hash}";
200 let dependency_manager = Arc::new(initialize_dependencies!().await);
201 let initial_counter_value = dependency_manager
202 .metrics_service
203 .get_artifact_detail_cardano_transaction_total_served_since_startup()
204 .get(&["TEST", "CLI"]);
205
206 request()
207 .method(method)
208 .path(path)
209 .header(MITHRIL_ORIGIN_TAG_HEADER, "TEST")
210 .header(MITHRIL_CLIENT_TYPE_HEADER, "CLI")
211 .reply(&setup_router(RouterState::new_with_origin_tag_white_list(
212 dependency_manager.clone(),
213 &["TEST"],
214 )))
215 .await;
216
217 assert_eq!(
218 initial_counter_value + 1,
219 dependency_manager
220 .metrics_service
221 .get_artifact_detail_cardano_transaction_total_served_since_startup()
222 .get(&["TEST", "CLI"])
223 );
224 }
225
226 #[tokio::test]
227 async fn test_cardano_transaction_get_ok() {
228 let mut mock_http_message_service = MockMessageService::new();
229 mock_http_message_service
230 .expect_get_cardano_transaction_message()
231 .return_once(|_| Ok(Some(CardanoTransactionSnapshotMessage::dummy())))
232 .once();
233 let mut dependency_manager = initialize_dependencies!().await;
234 dependency_manager.message_service = Arc::new(mock_http_message_service);
235
236 let method = Method::GET.as_str();
237 let path = "/artifact/cardano-transaction/{hash}";
238
239 let response = request()
240 .method(method)
241 .path(path)
242 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
243 dependency_manager,
244 ))))
245 .await;
246
247 APISpec::verify_conformity(
248 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
249 method,
250 path,
251 "application/json",
252 &Null,
253 &response,
254 &StatusCode::OK,
255 )
256 .unwrap();
257 }
258
259 #[tokio::test]
260 async fn test_cardano_transaction_return_404_not_found_when_no_record() {
261 let mut mock_http_message_service = MockMessageService::new();
262 mock_http_message_service
263 .expect_get_cardano_transaction_message()
264 .return_once(|_| Ok(None))
265 .once();
266 let mut dependency_manager = initialize_dependencies!().await;
267 dependency_manager.message_service = Arc::new(mock_http_message_service);
268
269 let method = Method::GET.as_str();
270 let path = "/artifact/cardano-transaction/{hash}";
271
272 let response = request()
273 .method(method)
274 .path(path)
275 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
276 dependency_manager,
277 ))))
278 .await;
279
280 APISpec::verify_conformity(
281 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
282 method,
283 path,
284 "application/json",
285 &Null,
286 &response,
287 &StatusCode::NOT_FOUND,
288 )
289 .unwrap();
290 }
291
292 #[tokio::test]
293 async fn test_cardano_transaction_get_ko() {
294 let mut mock_http_message_service = MockMessageService::new();
295 mock_http_message_service
296 .expect_get_cardano_transaction_message()
297 .return_once(|_| Err(HydrationError::InvalidData("invalid data".to_string()).into()))
298 .once();
299 let mut dependency_manager = initialize_dependencies!().await;
300 dependency_manager.message_service = Arc::new(mock_http_message_service);
301
302 let method = Method::GET.as_str();
303 let path = "/artifact/cardano-transaction/{hash}";
304
305 let response = request()
306 .method(method)
307 .path(path)
308 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
309 dependency_manager,
310 ))))
311 .await;
312
313 APISpec::verify_conformity(
314 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
315 method,
316 path,
317 "application/json",
318 &Null,
319 &response,
320 &StatusCode::INTERNAL_SERVER_ERROR,
321 )
322 .unwrap();
323 }
324}