mithril_aggregator/http_server/routes/artifact_routes/
cardano_transaction.rs

1use 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
11/// GET /artifact/cardano-transactions
12fn 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
22/// GET /artifact/cardano-transaction/:id
23fn 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    /// List Cardano Transactions set artifacts
49    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    /// Get Artifact by signed entity id
67    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}