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