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    };
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}