mithril_aggregator/http_server/routes/
signer_routes.rs

1use slog::warn;
2use warp::Filter;
3
4use crate::dependency_injection::EpochServiceWrapper;
5use crate::http_server::routes::middlewares;
6use crate::http_server::routes::router::RouterState;
7
8const MITHRIL_SIGNER_VERSION_HEADER: &str = "signer-node-version";
9
10pub fn routes(
11    router_state: &RouterState,
12) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
13    register_signer(router_state)
14        .or(registered_signers(router_state))
15        .or(signers_tickers(router_state))
16}
17
18/// POST /register-signer
19fn register_signer(
20    router_state: &RouterState,
21) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
22    warp::path!("register-signer")
23        .and(warp::post())
24        .and(middlewares::with_origin_tag(router_state))
25        .and(warp::header::optional::<String>(
26            MITHRIL_SIGNER_VERSION_HEADER,
27        ))
28        .and(warp::body::json())
29        .and(middlewares::with_logger(router_state))
30        .and(middlewares::with_signer_registerer(router_state))
31        .and(middlewares::with_event_transmitter(router_state))
32        .and(middlewares::with_epoch_service(router_state))
33        .and(middlewares::with_metrics_service(router_state))
34        .and_then(handlers::register_signer)
35}
36
37/// Get /signers/tickers
38fn signers_tickers(
39    router_state: &RouterState,
40) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
41    warp::path!("signers" / "tickers")
42        .and(warp::get())
43        .and(middlewares::with_logger(router_state))
44        .and(middlewares::extract_config(router_state, |config| {
45            config.network.to_string()
46        }))
47        .and(middlewares::with_signer_getter(router_state))
48        .and_then(handlers::signers_tickers)
49}
50
51/// Get /signers/registered/:epoch
52fn registered_signers(
53    router_state: &RouterState,
54) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
55    warp::path!("signers" / "registered" / String)
56        .and(warp::get())
57        .and(middlewares::with_logger(router_state))
58        .and(middlewares::with_epoch_service(router_state))
59        .and(middlewares::with_verification_key_store(router_state))
60        .and_then(handlers::registered_signers)
61}
62
63async fn fetch_epoch_header_value(
64    epoch_service: EpochServiceWrapper,
65    logger: &slog::Logger,
66) -> String {
67    match epoch_service.read().await.epoch_of_current_data() {
68        Ok(epoch) => format!("{epoch}"),
69        Err(e) => {
70            warn!(logger, "Could not fetch epoch header value from Epoch service"; "error" => ?e);
71            String::new()
72        }
73    }
74}
75
76mod handlers {
77    use mithril_common::messages::{RegisterSignerMessage, TryFromMessageAdapter};
78    use slog::{Logger, debug, warn};
79    use std::convert::Infallible;
80    use std::sync::Arc;
81    use warp::http::StatusCode;
82
83    use crate::{
84        FromRegisterSignerAdapter, MetricsService, SignerRegisterer, SignerRegistrationError,
85        VerificationKeyStorer,
86        database::repository::SignerGetter,
87        entities::{
88            SignerRegistrationsMessage, SignerTickerListItemMessage, SignersTickersMessage,
89        },
90        event_store::{EventMessage, TransmitterService},
91        http_server::{
92            parameters,
93            routes::{reply, signer_routes::fetch_epoch_header_value},
94        },
95    };
96
97    use super::*;
98
99    /// Register Signer
100    #[allow(clippy::too_many_arguments)]
101    pub async fn register_signer(
102        origin_tag: Option<String>,
103        signer_node_version: Option<String>,
104        register_signer_message: RegisterSignerMessage,
105        logger: Logger,
106        signer_registerer: Arc<dyn SignerRegisterer>,
107        event_transmitter: Arc<TransmitterService<EventMessage>>,
108        epoch_service: EpochServiceWrapper,
109        metrics_service: Arc<MetricsService>,
110    ) -> Result<impl warp::Reply, Infallible> {
111        debug!(logger, ">> register_signer"; "payload" => ?register_signer_message);
112
113        metrics_service
114            .get_signer_registration_total_received_since_startup()
115            .increment(&[origin_tag.as_deref().unwrap_or_default()]);
116
117        let payload = register_signer_message.clone();
118        let registration_epoch = register_signer_message.epoch;
119
120        let signer = match FromRegisterSignerAdapter::try_adapt(register_signer_message) {
121            Ok(signer) => signer,
122            Err(err) => {
123                warn!(logger,"register_signer::payload decoding error"; "full_payload" => #?payload, "error" => ?err);
124                return Ok(reply::bad_request(
125                    "Could not decode signer payload".to_string(),
126                    err.to_string(),
127                ));
128            }
129        };
130
131        let epoch_str = fetch_epoch_header_value(epoch_service, &logger).await;
132
133        match signer_registerer.register_signer(registration_epoch, &signer).await {
134            Ok(signer_with_stake) => {
135                event_transmitter.send(EventMessage::signer_registration(
136                    "HTTP::signer_register",
137                    &signer_with_stake,
138                    signer_node_version,
139                    epoch_str.as_str(),
140                ));
141
142                metrics_service
143                    .get_signer_registration_total_successful_since_startup()
144                    .increment(&[origin_tag.as_deref().unwrap_or_default()]);
145
146                Ok(reply::empty(StatusCode::CREATED))
147            }
148            Err(SignerRegistrationError::ExistingSigner(signer_with_stake)) => {
149                debug!(
150                    logger, "register_signer::already_registered";
151                    "registration_epoch" => ?registration_epoch, "party_id" => &signer.party_id,
152                );
153
154                event_transmitter.send(EventMessage::signer_registration(
155                    "HTTP::signer_register",
156                    &signer_with_stake,
157                    signer_node_version,
158                    &epoch_str,
159                ));
160
161                Ok(reply::empty(StatusCode::CREATED))
162            }
163            Err(SignerRegistrationError::InvalidSignerRegistration(
164                _party_id,
165                _registration_epoch,
166                err,
167            )) => {
168                warn!(logger,"register_signer::failed_signer_registration"; "full_payload" => #?payload, "error" => ?err);
169                Ok(reply::bad_request(
170                    "failed_signer_registration".to_string(),
171                    err.to_string(),
172                ))
173            }
174            Err(SignerRegistrationError::RegistrationRoundNotYetOpened) => {
175                warn!(
176                    logger, "register_signer::registration_round_not_yed_opened";
177                    "registration_epoch" => ?registration_epoch, "party_id" => &signer.party_id,
178                );
179                Ok(reply::server_error(
180                    SignerRegistrationError::RegistrationRoundNotYetOpened,
181                ))
182            }
183            Err(err) => {
184                warn!(logger,"register_signer::error"; "full_payload" => #?payload, "error" => ?err);
185                Ok(reply::server_error(err))
186            }
187        }
188    }
189
190    /// Get Registered Signers for a given epoch
191    pub async fn registered_signers(
192        registered_at: String,
193        logger: Logger,
194        epoch_service: EpochServiceWrapper,
195        verification_key_store: Arc<dyn VerificationKeyStorer>,
196    ) -> Result<impl warp::Reply, Infallible> {
197        let expanded_epoch = match parameters::expand_epoch(&registered_at, epoch_service).await {
198            Ok(epoch) => epoch,
199            Err(err) => {
200                warn!(logger,"registered_signers::invalid_epoch"; "error" => ?err);
201                return Ok(reply::bad_request(
202                    "invalid_epoch".to_string(),
203                    err.to_string(),
204                ));
205            }
206        };
207
208        // The given epoch is the epoch at which the signer registered, the store works on
209        // the recording epoch, so we need to offset.
210        match verification_key_store
211            .get_signers(expanded_epoch.offset_to_recording_epoch())
212            .await
213        {
214            Ok(Some(signers)) => {
215                let message = SignerRegistrationsMessage::new(*expanded_epoch, signers);
216                Ok(reply::json(&message, StatusCode::OK))
217            }
218            Ok(None) => {
219                warn!(logger, "registered_signers::not_found");
220                Ok(reply::empty(StatusCode::NOT_FOUND))
221            }
222            Err(err) => {
223                warn!(logger,"registered_signers::error"; "error" => ?err);
224                Ok(reply::server_error(err))
225            }
226        }
227    }
228
229    pub async fn signers_tickers(
230        logger: Logger,
231        network: String,
232        signer_getter: Arc<dyn SignerGetter>,
233    ) -> Result<impl warp::Reply, Infallible> {
234        match signer_getter.get_all().await {
235            Ok(signers) => {
236                let signers: Vec<_> = signers
237                    .into_iter()
238                    .map(|s| SignerTickerListItemMessage {
239                        party_id: s.signer_id,
240                        pool_ticker: s.pool_ticker,
241                        has_registered: s.last_registered_at.is_some(),
242                    })
243                    .collect();
244                Ok(reply::json(
245                    &SignersTickersMessage { network, signers },
246                    StatusCode::OK,
247                ))
248            }
249            Err(err) => {
250                warn!(logger,"registered_signers::error"; "error" => ?err);
251                Ok(reply::server_error(err))
252            }
253        }
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use anyhow::anyhow;
260    use mockall::predicate::eq;
261    use serde_json::Value::Null;
262    use std::sync::Arc;
263    use tokio::sync::RwLock;
264    use warp::{
265        http::{Method, StatusCode},
266        test::request,
267    };
268
269    use mithril_api_spec::APISpec;
270    use mithril_common::{
271        MITHRIL_ORIGIN_TAG_HEADER,
272        crypto_helper::ProtocolRegistrationError,
273        entities::Epoch,
274        messages::RegisterSignerMessage,
275        test::{
276            builder::MithrilFixtureBuilder,
277            double::{Dummy, fake_data},
278            mock_extensions::MockBuilder,
279        },
280    };
281
282    use crate::{
283        SignerRegistrationError,
284        database::{record::SignerRecord, repository::MockSignerGetter},
285        http_server::routes::reply::MithrilStatusCode,
286        initialize_dependencies,
287        services::{FakeEpochService, MockSignerRegisterer},
288        store::MockVerificationKeyStorer,
289        test::TestLogger,
290    };
291
292    use super::*;
293
294    fn setup_router(
295        state: RouterState,
296    ) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
297        let cors = warp::cors()
298            .allow_any_origin()
299            .allow_headers(vec!["content-type"])
300            .allow_methods(vec![Method::GET, Method::POST, Method::OPTIONS]);
301
302        warp::any().and(routes(&state).with(cors))
303    }
304
305    #[tokio::test]
306    async fn test_register_signer_post_ok() {
307        let signer_with_stake = fake_data::signers_with_stakes(1).pop().unwrap();
308        let mut mock_signer_registerer = MockSignerRegisterer::new();
309        mock_signer_registerer
310            .expect_register_signer()
311            .return_once(|_, _| Ok(signer_with_stake));
312        let mut dependency_manager = initialize_dependencies!().await;
313        dependency_manager.signer_registerer = Arc::new(mock_signer_registerer);
314
315        let signer: RegisterSignerMessage = RegisterSignerMessage::dummy();
316
317        let method = Method::POST.as_str();
318        let path = "/register-signer";
319
320        let response = request()
321            .method(method)
322            .path(path)
323            .json(&signer)
324            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
325                dependency_manager,
326            ))))
327            .await;
328
329        APISpec::verify_conformity(
330            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
331            method,
332            path,
333            "application/json",
334            &signer,
335            &response,
336            &StatusCode::CREATED,
337        )
338        .unwrap();
339    }
340
341    #[tokio::test]
342    async fn test_register_signer_increments_signer_registration_total_received_and_successful_since_startup_metric_if_successful()
343     {
344        let method = Method::POST.as_str();
345        let path = "/register-signer";
346        let dependency_manager = {
347            let mut deps = initialize_dependencies!().await;
348            deps.signer_registerer = MockBuilder::<MockSignerRegisterer>::configure(|mock| {
349                mock.expect_register_signer()
350                    .returning(|_, _| Ok(fake_data::signers_with_stakes(1).pop().unwrap()));
351            });
352            Arc::new(deps)
353        };
354
355        let initial_received_counter_value = dependency_manager
356            .metrics_service
357            .get_signer_registration_total_received_since_startup()
358            .get(&["TEST"]);
359        let initial_successful_counter_value = dependency_manager
360            .metrics_service
361            .get_signer_registration_total_successful_since_startup()
362            .get(&["HTTP"]);
363
364        request()
365            .method(method)
366            .path(path)
367            .json(&RegisterSignerMessage::dummy())
368            .header(MITHRIL_ORIGIN_TAG_HEADER, "TEST")
369            .reply(&setup_router(RouterState::new_with_origin_tag_white_list(
370                dependency_manager.clone(),
371                &["TEST"],
372            )))
373            .await;
374
375        assert_eq!(
376            initial_received_counter_value + 1,
377            dependency_manager
378                .metrics_service
379                .get_signer_registration_total_received_since_startup()
380                .get(&["TEST"])
381        );
382        assert_eq!(
383            initial_successful_counter_value + 1,
384            dependency_manager
385                .metrics_service
386                .get_signer_registration_total_successful_since_startup()
387                .get(&["TEST"])
388        );
389    }
390
391    #[tokio::test]
392    async fn test_register_signer_only_increments_signer_registration_total_received_since_startup_metric_if_failure()
393     {
394        let method = Method::POST.as_str();
395        let path = "/register-signer";
396        let dependency_manager = {
397            let mut deps = initialize_dependencies!().await;
398            deps.signer_registerer = MockBuilder::<MockSignerRegisterer>::configure(|mock| {
399                mock.expect_register_signer()
400                    .returning(|_, _| Err(SignerRegistrationError::RegistrationRoundNotYetOpened));
401            });
402            Arc::new(deps)
403        };
404
405        let initial_received_counter_value = dependency_manager
406            .metrics_service
407            .get_signer_registration_total_received_since_startup()
408            .get(&["TEST"]);
409        let initial_successful_counter_value = dependency_manager
410            .metrics_service
411            .get_signer_registration_total_successful_since_startup()
412            .get(&["HTTP"]);
413
414        request()
415            .method(method)
416            .path(path)
417            .json(&RegisterSignerMessage::dummy())
418            .header(MITHRIL_ORIGIN_TAG_HEADER, "TEST")
419            .reply(&setup_router(RouterState::new_with_origin_tag_white_list(
420                dependency_manager.clone(),
421                &["TEST"],
422            )))
423            .await;
424
425        assert_eq!(
426            initial_received_counter_value + 1,
427            dependency_manager
428                .metrics_service
429                .get_signer_registration_total_received_since_startup()
430                .get(&["TEST"])
431        );
432        assert_eq!(
433            initial_successful_counter_value,
434            dependency_manager
435                .metrics_service
436                .get_signer_registration_total_successful_since_startup()
437                .get(&["TEST"])
438        );
439    }
440
441    #[tokio::test]
442    async fn test_register_signer_post_ok_existing() {
443        let signer_with_stake = fake_data::signers_with_stakes(1).pop().unwrap();
444        let mut mock_signer_registerer = MockSignerRegisterer::new();
445        mock_signer_registerer.expect_register_signer().return_once(|_, _| {
446            Err(SignerRegistrationError::ExistingSigner(Box::new(
447                signer_with_stake,
448            )))
449        });
450        let mut dependency_manager = initialize_dependencies!().await;
451        dependency_manager.signer_registerer = Arc::new(mock_signer_registerer);
452
453        let signer: RegisterSignerMessage = RegisterSignerMessage::dummy();
454
455        let method = Method::POST.as_str();
456        let path = "/register-signer";
457
458        let response = request()
459            .method(method)
460            .path(path)
461            .json(&signer)
462            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
463                dependency_manager,
464            ))))
465            .await;
466
467        APISpec::verify_conformity(
468            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
469            method,
470            path,
471            "application/json",
472            &signer,
473            &response,
474            &StatusCode::CREATED,
475        )
476        .unwrap();
477    }
478
479    #[tokio::test]
480    async fn test_register_signer_post_ko_400() {
481        let mut mock_signer_registerer = MockSignerRegisterer::new();
482        mock_signer_registerer.expect_register_signer().return_once(|_, _| {
483            Err(SignerRegistrationError::InvalidSignerRegistration(
484                "party_2".to_string(),
485                Epoch(4),
486                anyhow!(ProtocolRegistrationError::OpCertInvalid),
487            ))
488        });
489        let mut dependency_manager = initialize_dependencies!().await;
490        dependency_manager.signer_registerer = Arc::new(mock_signer_registerer);
491
492        let signer: RegisterSignerMessage = RegisterSignerMessage::dummy();
493
494        let method = Method::POST.as_str();
495        let path = "/register-signer";
496
497        let response = request()
498            .method(method)
499            .path(path)
500            .json(&signer)
501            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
502                dependency_manager,
503            ))))
504            .await;
505
506        APISpec::verify_conformity(
507            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
508            method,
509            path,
510            "application/json",
511            &signer,
512            &response,
513            &StatusCode::BAD_REQUEST,
514        )
515        .unwrap();
516    }
517
518    #[tokio::test]
519    async fn test_register_signer_post_ko_500() {
520        let mut mock_signer_registerer = MockSignerRegisterer::new();
521        mock_signer_registerer.expect_register_signer().return_once(|_, _| {
522            Err(SignerRegistrationError::FailedSignerRecorder(
523                "party_1".to_string(),
524                Epoch(4),
525                anyhow!("an error occurred"),
526            ))
527        });
528        let mut dependency_manager = initialize_dependencies!().await;
529        dependency_manager.signer_registerer = Arc::new(mock_signer_registerer);
530
531        let signer: RegisterSignerMessage = RegisterSignerMessage::dummy();
532        let method = Method::POST.as_str();
533        let path = "/register-signer";
534
535        let response = request()
536            .method(method)
537            .path(path)
538            .json(&signer)
539            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
540                dependency_manager,
541            ))))
542            .await;
543
544        APISpec::verify_conformity(
545            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
546            method,
547            path,
548            "application/json",
549            &signer,
550            &response,
551            &StatusCode::INTERNAL_SERVER_ERROR,
552        )
553        .unwrap();
554    }
555
556    #[tokio::test]
557    async fn test_register_signer_post_ko_550() {
558        let mut mock_signer_registerer = MockSignerRegisterer::new();
559        mock_signer_registerer
560            .expect_register_signer()
561            .return_once(|_, _| Err(SignerRegistrationError::RegistrationRoundNotYetOpened));
562        let mut dependency_manager = initialize_dependencies!().await;
563        dependency_manager.signer_registerer = Arc::new(mock_signer_registerer);
564
565        let signer: RegisterSignerMessage = RegisterSignerMessage::dummy();
566        let method = Method::POST.as_str();
567        let path = "/register-signer";
568
569        let response = request()
570            .method(method)
571            .path(path)
572            .json(&signer)
573            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
574                dependency_manager,
575            ))))
576            .await;
577
578        APISpec::verify_conformity(
579            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
580            method,
581            path,
582            "application/json",
583            &signer,
584            &response,
585            &MithrilStatusCode::registration_round_not_yet_opened(),
586        )
587        .unwrap();
588    }
589
590    #[tokio::test]
591    async fn test_registered_signers_get_offset_given_epoch_to_registration_epoch() {
592        let asked_epoch = Epoch(1);
593        let expected_retrieval_epoch = asked_epoch.offset_to_recording_epoch();
594        let mut mock_verification_key_store = MockVerificationKeyStorer::new();
595        mock_verification_key_store
596            .expect_get_signers()
597            .with(eq(expected_retrieval_epoch))
598            .return_once(|_| Ok(Some(fake_data::signers_with_stakes(3))))
599            .once();
600        let mut dependency_manager = initialize_dependencies!().await;
601        dependency_manager.verification_key_store = Arc::new(mock_verification_key_store);
602
603        let method = Method::GET.as_str();
604        let base_path = "/signers/registered";
605
606        let response = request()
607            .method(method)
608            .path(&format!("{base_path}/{asked_epoch}"))
609            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
610                dependency_manager,
611            ))))
612            .await;
613
614        assert!(
615            response.status().is_success(),
616            "expected the response to succeed, was: {response:#?}"
617        );
618    }
619
620    #[tokio::test]
621    async fn test_registered_signers_get_ok() {
622        let mut mock_verification_key_store = MockVerificationKeyStorer::new();
623        mock_verification_key_store
624            .expect_get_signers()
625            .return_once(|_| Ok(Some(fake_data::signers_with_stakes(3))))
626            .once();
627        let mut dependency_manager = initialize_dependencies!().await;
628        dependency_manager.verification_key_store = Arc::new(mock_verification_key_store);
629
630        let base_path = "/signers/registered";
631        let method = Method::GET.as_str();
632
633        let response = request()
634            .method(method)
635            .path(&format!("{base_path}/1"))
636            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
637                dependency_manager,
638            ))))
639            .await;
640
641        APISpec::verify_conformity(
642            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
643            method,
644            &format!("{base_path}/{{epoch}}"),
645            "application/json",
646            &Null,
647            &response,
648            &StatusCode::OK,
649        )
650        .unwrap();
651    }
652
653    #[tokio::test]
654    async fn test_registered_signers_returns_404_not_found_when_no_registration() {
655        let mut mock_verification_key_store = MockVerificationKeyStorer::new();
656        mock_verification_key_store
657            .expect_get_signers()
658            .return_once(|_| Ok(None))
659            .once();
660        let mut dependency_manager = initialize_dependencies!().await;
661        dependency_manager.verification_key_store = Arc::new(mock_verification_key_store);
662
663        let method = Method::GET.as_str();
664        let base_path = "/signers/registered";
665
666        let response = request()
667            .method(method)
668            .path(&format!("{base_path}/3"))
669            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
670                dependency_manager,
671            ))))
672            .await;
673
674        APISpec::verify_conformity(
675            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
676            method,
677            &format!("{base_path}/{{epoch}}"),
678            "application/json",
679            &Null,
680            &response,
681            &StatusCode::NOT_FOUND,
682        )
683        .unwrap();
684    }
685
686    #[tokio::test]
687    async fn test_registered_signers_get_ko() {
688        let mut mock_verification_key_store = MockVerificationKeyStorer::new();
689        mock_verification_key_store
690            .expect_get_signers()
691            .return_once(|_| Err(anyhow!("invalid query")));
692        let mut dependency_manager = initialize_dependencies!().await;
693        dependency_manager.verification_key_store = Arc::new(mock_verification_key_store);
694
695        let method = Method::GET.as_str();
696        let base_path = "/signers/registered";
697
698        let response = request()
699            .method(method)
700            .path(&format!("{base_path}/1"))
701            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
702                dependency_manager,
703            ))))
704            .await;
705
706        APISpec::verify_conformity(
707            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
708            method,
709            &format!("{base_path}/{{epoch}}"),
710            "application/json",
711            &Null,
712            &response,
713            &StatusCode::INTERNAL_SERVER_ERROR,
714        )
715        .unwrap();
716    }
717
718    #[tokio::test]
719    async fn test_signers_tickers_get_ok() {
720        let mut mock_signer_getter = MockSignerGetter::new();
721        mock_signer_getter
722            .expect_get_all()
723            .return_once(|| {
724                Ok(vec![
725                    SignerRecord {
726                        signer_id: "pool_without_ticker".to_string(),
727                        pool_ticker: None,
728                        created_at: Default::default(),
729                        updated_at: Default::default(),
730                        last_registered_at: None,
731                    },
732                    SignerRecord {
733                        signer_id: "pool_with_ticker".to_string(),
734                        pool_ticker: Some("pool_ticker".to_string()),
735                        created_at: Default::default(),
736                        updated_at: Default::default(),
737                        last_registered_at: None,
738                    },
739                ])
740            })
741            .once();
742        let mut dependency_manager = initialize_dependencies!().await;
743        dependency_manager.signer_getter = Arc::new(mock_signer_getter);
744
745        let method = Method::GET.as_str();
746        let path = "/signers/tickers";
747
748        let response = request()
749            .method(method)
750            .path(path)
751            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
752                dependency_manager,
753            ))))
754            .await;
755
756        APISpec::verify_conformity(
757            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
758            method,
759            path,
760            "application/json",
761            &Null,
762            &response,
763            &StatusCode::OK,
764        )
765        .unwrap();
766    }
767
768    #[tokio::test]
769    async fn test_signers_tickers_get_ko() {
770        let mut mock_signer_getter = MockSignerGetter::new();
771        mock_signer_getter
772            .expect_get_all()
773            .return_once(|| Err(anyhow!("an error")))
774            .once();
775        let mut dependency_manager = initialize_dependencies!().await;
776        dependency_manager.signer_getter = Arc::new(mock_signer_getter);
777
778        let method = Method::GET.as_str();
779        let path = "/signers/tickers";
780
781        let response = request()
782            .method(method)
783            .path(path)
784            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
785                dependency_manager,
786            ))))
787            .await;
788
789        APISpec::verify_conformity(
790            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
791            method,
792            path,
793            "application/json",
794            &Null,
795            &response,
796            &StatusCode::INTERNAL_SERVER_ERROR,
797        )
798        .unwrap();
799    }
800
801    #[tokio::test]
802    async fn test_fetch_epoch_header_value_when_epoch_service_return_epoch() {
803        let fixture = MithrilFixtureBuilder::default().build();
804        let epoch_service = Arc::new(RwLock::new(FakeEpochService::from_fixture(
805            Epoch(84),
806            &fixture,
807        )));
808
809        let epoch_str = fetch_epoch_header_value(epoch_service, &TestLogger::stdout()).await;
810
811        assert_eq!(epoch_str, "84".to_string());
812    }
813
814    #[tokio::test]
815    async fn test_fetch_epoch_header_value_when_epoch_service_error_return_empty_string() {
816        let epoch_service = Arc::new(RwLock::new(FakeEpochService::without_data()));
817
818        let epoch_str = fetch_epoch_header_value(epoch_service, &TestLogger::stdout()).await;
819
820        assert_eq!(epoch_str, "".to_string());
821    }
822}