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