1use std::sync::Arc;
4
5use crate::{
6 config::{Config, RepositoryStorageType, ServerConfig},
7 jobs::JobProducerTrait,
8 models::{
9 NetworkRepoModel, NotificationRepoModel, PluginModel, Relayer, RelayerRepoModel,
10 Signer as SignerDomainModel, SignerFileConfig, SignerRepoModel, ThinDataAppState,
11 TransactionRepoModel,
12 },
13 repositories::{
14 NetworkRepository, PluginRepositoryTrait, RelayerRepository, Repository,
15 TransactionCounterTrait, TransactionRepository,
16 },
17 services::{Signer as SignerService, SignerFactory},
18};
19use color_eyre::{eyre::WrapErr, Report, Result};
20use futures::future::try_join_all;
21use log::info;
22
23async fn process_plugins<J, RR, TR, NR, NFR, SR, TCR, PR>(
25 config_file: &Config,
26 app_state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
27) -> Result<()>
28where
29 J: JobProducerTrait + Send + Sync + 'static,
30 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
31 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
32 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
33 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
34 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
35 TCR: TransactionCounterTrait + Send + Sync + 'static,
36 PR: PluginRepositoryTrait + Send + Sync + 'static,
37{
38 if let Some(plugins) = &config_file.plugins {
39 let plugin_futures = plugins.iter().map(|plugin| async {
40 let plugin_model = PluginModel::try_from(plugin.clone())
41 .wrap_err("Failed to convert plugin config")?;
42 app_state
43 .plugin_repository
44 .add(plugin_model)
45 .await
46 .wrap_err("Failed to create plugin repository entry")?;
47 Ok::<(), Report>(())
48 });
49
50 try_join_all(plugin_futures)
51 .await
52 .wrap_err("Failed to initialize plugin repository")?;
53 Ok(())
54 } else {
55 Ok(())
56 }
57}
58
59async fn process_signer(signer: &SignerFileConfig) -> Result<SignerRepoModel> {
61 let domain_signer = SignerDomainModel::try_from(signer.clone())
63 .wrap_err("Failed to convert signer config to domain model")?;
64
65 let signer_repo_model = SignerRepoModel::from(domain_signer);
67
68 Ok(signer_repo_model)
69}
70
71async fn process_signers<J, RR, TR, NR, NFR, SR, TCR, PR>(
79 config_file: &Config,
80 app_state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
81) -> Result<()>
82where
83 J: JobProducerTrait + Send + Sync + 'static,
84 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
85 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
86 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
87 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
88 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
89 TCR: TransactionCounterTrait + Send + Sync + 'static,
90 PR: PluginRepositoryTrait + Send + Sync + 'static,
91{
92 let signer_futures = config_file.signers.iter().map(|signer| async {
93 let signer_repo_model = process_signer(signer).await?;
94
95 app_state
96 .signer_repository
97 .create(signer_repo_model)
98 .await
99 .wrap_err("Failed to create signer repository entry")?;
100 Ok::<(), Report>(())
101 });
102
103 try_join_all(signer_futures)
104 .await
105 .wrap_err("Failed to initialize signer repository")?;
106 Ok(())
107}
108
109async fn process_notifications<J, RR, TR, NR, NFR, SR, TCR, PR>(
117 config_file: &Config,
118 app_state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
119) -> Result<()>
120where
121 J: JobProducerTrait + Send + Sync + 'static,
122 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
123 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
124 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
125 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
126 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
127 TCR: TransactionCounterTrait + Send + Sync + 'static,
128 PR: PluginRepositoryTrait + Send + Sync + 'static,
129{
130 let notification_futures = config_file.notifications.iter().map(|notification| async {
131 let notification_repo_model = NotificationRepoModel::try_from(notification.clone())
132 .wrap_err("Failed to convert notification config")?;
133
134 app_state
135 .notification_repository
136 .create(notification_repo_model)
137 .await
138 .wrap_err("Failed to create notification repository entry")?;
139 Ok::<(), Report>(())
140 });
141
142 try_join_all(notification_futures)
143 .await
144 .wrap_err("Failed to initialize notification repository")?;
145 Ok(())
146}
147
148async fn process_networks<J, RR, TR, NR, NFR, SR, TCR, PR>(
156 config_file: &Config,
157 app_state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
158) -> Result<()>
159where
160 J: JobProducerTrait + Send + Sync + 'static,
161 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
162 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
163 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
164 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
165 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
166 TCR: TransactionCounterTrait + Send + Sync + 'static,
167 PR: PluginRepositoryTrait + Send + Sync + 'static,
168{
169 let network_futures = config_file.networks.iter().map(|network| async move {
170 let network_repo_model = NetworkRepoModel::try_from(network.clone())?;
171
172 app_state
173 .network_repository
174 .create(network_repo_model)
175 .await
176 .wrap_err("Failed to create network repository entry")?;
177 Ok::<(), Report>(())
178 });
179
180 try_join_all(network_futures)
181 .await
182 .wrap_err("Failed to initialize network repository")?;
183 Ok(())
184}
185
186async fn process_relayers<J, RR, TR, NR, NFR, SR, TCR, PR>(
197 config_file: &Config,
198 app_state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
199) -> Result<()>
200where
201 J: JobProducerTrait + Send + Sync + 'static,
202 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
203 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
204 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
205 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
206 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
207 TCR: TransactionCounterTrait + Send + Sync + 'static,
208 PR: PluginRepositoryTrait + Send + Sync + 'static,
209{
210 let signers = app_state.signer_repository.list_all().await?;
211
212 let relayer_futures = config_file.relayers.iter().map(|relayer| async {
213 let domain_relayer = Relayer::try_from(relayer.clone())
215 .wrap_err("Failed to convert relayer config to domain model")?;
216 let mut repo_model = RelayerRepoModel::from(domain_relayer);
217 let signer_model = signers
218 .iter()
219 .find(|s| s.id == repo_model.signer_id)
220 .ok_or_else(|| eyre::eyre!("Signer not found"))?;
221
222 let network_type = repo_model.network_type;
223 let signer_service = SignerFactory::create_signer(
224 &network_type,
225 &SignerDomainModel::from(signer_model.clone()),
226 )
227 .await
228 .wrap_err("Failed to create signer service")?;
229
230 let address = signer_service.address().await?;
231 repo_model.address = address.to_string();
232
233 app_state
234 .relayer_repository
235 .create(repo_model)
236 .await
237 .wrap_err("Failed to create relayer repository entry")?;
238 Ok::<(), Report>(())
239 });
240
241 try_join_all(relayer_futures)
242 .await
243 .wrap_err("Failed to initialize relayer repository")?;
244 Ok(())
245}
246
247async fn is_redis_populated<J, RR, TR, NR, NFR, SR, TCR, PR>(
252 app_state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
253) -> Result<bool>
254where
255 J: JobProducerTrait + Send + Sync + 'static,
256 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
257 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
258 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
259 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
260 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
261 TCR: TransactionCounterTrait + Send + Sync + 'static,
262 PR: PluginRepositoryTrait + Send + Sync + 'static,
263{
264 if app_state.relayer_repository.has_entries().await? {
265 return Ok(true);
266 }
267
268 if app_state.transaction_repository.has_entries().await? {
269 return Ok(true);
270 }
271
272 if app_state.signer_repository.has_entries().await? {
273 return Ok(true);
274 }
275
276 if app_state.notification_repository.has_entries().await? {
277 return Ok(true);
278 }
279
280 if app_state.network_repository.has_entries().await? {
281 return Ok(true);
282 }
283
284 if app_state.plugin_repository.has_entries().await? {
285 return Ok(true);
286 }
287
288 Ok(false)
289}
290
291pub async fn process_config_file<J, RR, TR, NR, NFR, SR, TCR, PR>(
299 config_file: Config,
300 server_config: Arc<ServerConfig>,
301 app_state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR>,
302) -> Result<()>
303where
304 J: JobProducerTrait + Send + Sync + 'static,
305 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
306 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
307 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
308 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
309 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
310 TCR: TransactionCounterTrait + Send + Sync + 'static,
311 PR: PluginRepositoryTrait + Send + Sync + 'static,
312{
313 let should_process_config_file = match server_config.repository_storage_type {
314 RepositoryStorageType::InMemory => true,
315 RepositoryStorageType::Redis => {
316 server_config.reset_storage_on_start || !is_redis_populated(app_state).await?
317 }
318 };
319
320 if !should_process_config_file {
321 info!("Skipping config file processing");
322 return Ok(());
323 }
324
325 if server_config.reset_storage_on_start {
326 info!("Resetting storage on start due to server config flag RESET_STORAGE_ON_START = true");
327 app_state.relayer_repository.drop_all_entries().await?;
328 app_state.transaction_repository.drop_all_entries().await?;
329 app_state.signer_repository.drop_all_entries().await?;
330 app_state.notification_repository.drop_all_entries().await?;
331 app_state.network_repository.drop_all_entries().await?;
332 app_state.plugin_repository.drop_all_entries().await?;
333 }
334
335 if should_process_config_file {
336 info!("Processing config file");
337 process_plugins(&config_file, app_state).await?;
338 process_signers(&config_file, app_state).await?;
339 process_notifications(&config_file, app_state).await?;
340 process_networks(&config_file, app_state).await?;
341 process_relayers(&config_file, app_state).await?;
342 }
343 Ok(())
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349 use crate::{
350 config::{ConfigFileNetworkType, NetworksFileConfig, PluginFileConfig},
351 constants::DEFAULT_PLUGIN_TIMEOUT_SECONDS,
352 jobs::MockJobProducerTrait,
353 models::{
354 relayer::RelayerFileConfig, AppState, AwsKmsSignerFileConfig,
355 GoogleCloudKmsKeyFileConfig, GoogleCloudKmsServiceAccountFileConfig,
356 GoogleCloudKmsSignerFileConfig, LocalSignerFileConfig, NetworkType, NotificationConfig,
357 NotificationType, PlainOrEnvValue, SecretString, SignerConfigStorage, SignerFileConfig,
358 SignerFileConfigEnum, VaultSignerFileConfig, VaultTransitSignerFileConfig,
359 },
360 repositories::{
361 InMemoryNetworkRepository, InMemoryNotificationRepository, InMemoryPluginRepository,
362 InMemorySignerRepository, InMemoryTransactionCounter, InMemoryTransactionRepository,
363 NetworkRepositoryStorage, NotificationRepositoryStorage, PluginRepositoryStorage,
364 RelayerRepositoryStorage, SignerRepositoryStorage, TransactionCounterRepositoryStorage,
365 TransactionRepositoryStorage,
366 },
367 utils::mocks::mockutils::{
368 create_mock_network, create_mock_notification, create_mock_relayer, create_mock_signer,
369 create_test_server_config,
370 },
371 };
372 use actix_web::web::ThinData;
373 use serde_json::json;
374 use std::{sync::Arc, time::Duration};
375 use wiremock::matchers::{body_json, header, method, path};
376 use wiremock::{Mock, MockServer, ResponseTemplate};
377
378 fn create_test_app_state() -> AppState<
379 MockJobProducerTrait,
380 RelayerRepositoryStorage,
381 TransactionRepositoryStorage,
382 NetworkRepositoryStorage,
383 NotificationRepositoryStorage,
384 SignerRepositoryStorage,
385 TransactionCounterRepositoryStorage,
386 PluginRepositoryStorage,
387 > {
388 let mut mock_job_producer = MockJobProducerTrait::new();
390
391 mock_job_producer
393 .expect_produce_transaction_request_job()
394 .returning(|_, _| Box::pin(async { Ok(()) }));
395
396 mock_job_producer
397 .expect_produce_submit_transaction_job()
398 .returning(|_, _| Box::pin(async { Ok(()) }));
399
400 mock_job_producer
401 .expect_produce_check_transaction_status_job()
402 .returning(|_, _| Box::pin(async { Ok(()) }));
403
404 mock_job_producer
405 .expect_produce_send_notification_job()
406 .returning(|_, _| Box::pin(async { Ok(()) }));
407
408 AppState {
409 relayer_repository: Arc::new(RelayerRepositoryStorage::new_in_memory()),
410 transaction_repository: Arc::new(TransactionRepositoryStorage::new_in_memory()),
411 signer_repository: Arc::new(SignerRepositoryStorage::new_in_memory()),
412 notification_repository: Arc::new(NotificationRepositoryStorage::new_in_memory()),
413 network_repository: Arc::new(NetworkRepositoryStorage::new_in_memory()),
414 transaction_counter_store: Arc::new(
415 TransactionCounterRepositoryStorage::new_in_memory(),
416 ),
417 job_producer: Arc::new(mock_job_producer),
418 plugin_repository: Arc::new(PluginRepositoryStorage::new_in_memory()),
419 }
420 }
421
422 #[tokio::test]
423 async fn test_process_signer_test() {
424 let signer = SignerFileConfig {
425 id: "test-signer".to_string(),
426 config: SignerFileConfigEnum::Local(LocalSignerFileConfig {
427 path: "tests/utils/test_keys/unit-test-local-signer.json".to_string(),
428 passphrase: PlainOrEnvValue::Plain {
429 value: SecretString::new("test"),
430 },
431 }),
432 };
433
434 let result = process_signer(&signer).await;
435
436 assert!(
437 result.is_ok(),
438 "Failed to process test signer: {:?}",
439 result.err()
440 );
441 let model = result.unwrap();
442
443 assert_eq!(model.id, "test-signer");
444
445 match model.config {
446 SignerConfigStorage::Local(config) => {
447 assert!(!config.raw_key.is_empty());
448 assert_eq!(config.raw_key.len(), 32);
449 }
450 _ => panic!("Expected Local config"),
451 }
452 }
453
454 #[tokio::test]
455 async fn test_process_signer_vault_transit() -> Result<()> {
456 let signer = SignerFileConfig {
457 id: "vault-transit-signer".to_string(),
458 config: SignerFileConfigEnum::VaultTransit(VaultTransitSignerFileConfig {
459 key_name: "test-transit-key".to_string(),
460 address: "https://vault.example.com".to_string(),
461 namespace: Some("test-namespace".to_string()),
462 role_id: PlainOrEnvValue::Plain {
463 value: SecretString::new("test-role"),
464 },
465 secret_id: PlainOrEnvValue::Plain {
466 value: SecretString::new("test-secret"),
467 },
468 pubkey: "test-pubkey".to_string(),
469 mount_point: Some("transit".to_string()),
470 }),
471 };
472
473 let result = process_signer(&signer).await;
474
475 assert!(
476 result.is_ok(),
477 "Failed to process vault transit signer: {:?}",
478 result.err()
479 );
480 let model = result.unwrap();
481
482 assert_eq!(model.id, "vault-transit-signer");
483
484 match model.config {
485 SignerConfigStorage::VaultTransit(config) => {
486 assert_eq!(config.key_name, "test-transit-key");
487 assert_eq!(config.address, "https://vault.example.com");
488 assert_eq!(config.namespace, Some("test-namespace".to_string()));
489 assert_eq!(config.role_id.to_str().as_str(), "test-role");
490 assert_eq!(config.secret_id.to_str().as_str(), "test-secret");
491 assert_eq!(config.pubkey, "test-pubkey");
492 assert_eq!(config.mount_point, Some("transit".to_string()));
493 }
494 _ => panic!("Expected VaultTransit config"),
495 }
496
497 Ok(())
498 }
499
500 #[tokio::test]
501 async fn test_process_signer_aws_kms() -> Result<()> {
502 let signer = SignerFileConfig {
503 id: "aws-kms-signer".to_string(),
504 config: SignerFileConfigEnum::AwsKms(AwsKmsSignerFileConfig {
505 region: "us-east-1".to_string(),
506 key_id: "test-key-id".to_string(),
507 }),
508 };
509
510 let result = process_signer(&signer).await;
511
512 assert!(
513 result.is_ok(),
514 "Failed to process AWS KMS signer: {:?}",
515 result.err()
516 );
517 let model = result.unwrap();
518
519 assert_eq!(model.id, "aws-kms-signer");
520
521 match model.config {
522 SignerConfigStorage::AwsKms(_) => {}
523 _ => panic!("Expected AwsKms config"),
524 }
525
526 Ok(())
527 }
528
529 async fn setup_mock_approle_login(
531 mock_server: &MockServer,
532 role_id: &str,
533 secret_id: &str,
534 token: &str,
535 ) {
536 Mock::given(method("POST"))
537 .and(path("/v1/auth/approle/login"))
538 .and(body_json(json!({
539 "role_id": role_id,
540 "secret_id": secret_id
541 })))
542 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
543 "request_id": "test-request-id",
544 "lease_id": "",
545 "renewable": false,
546 "lease_duration": 0,
547 "data": null,
548 "wrap_info": null,
549 "warnings": null,
550 "auth": {
551 "client_token": token,
552 "accessor": "test-accessor",
553 "policies": ["default"],
554 "token_policies": ["default"],
555 "metadata": {
556 "role_name": "test-role"
557 },
558 "lease_duration": 3600,
559 "renewable": true,
560 "entity_id": "test-entity-id",
561 "token_type": "service",
562 "orphan": true
563 }
564 })))
565 .mount(mock_server)
566 .await;
567 }
568
569 #[tokio::test]
570 async fn test_process_signer_vault() -> Result<()> {
571 let mock_server = MockServer::start().await;
572
573 setup_mock_approle_login(&mock_server, "test-role-id", "test-secret-id", "test-token")
574 .await;
575
576 Mock::given(method("GET"))
577 .and(path("/v1/secret/data/test-key"))
578 .and(header("X-Vault-Token", "test-token"))
579 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
580 "request_id": "test-request-id",
581 "lease_id": "",
582 "renewable": false,
583 "lease_duration": 0,
584 "data": {
585 "data": {
586 "value": "C5ACE14AB163556747F02C1110911537578FBE335FB74D18FBF82990AD70C3B9"
587 },
588 "metadata": {
589 "created_time": "2024-01-01T00:00:00Z",
590 "deletion_time": "",
591 "destroyed": false,
592 "version": 1
593 }
594 },
595 "wrap_info": null,
596 "warnings": null,
597 "auth": null
598 })))
599 .mount(&mock_server)
600 .await;
601
602 let signer = SignerFileConfig {
603 id: "vault-signer".to_string(),
604 config: SignerFileConfigEnum::Vault(VaultSignerFileConfig {
605 key_name: "test-key".to_string(),
606 address: mock_server.uri(),
607 namespace: Some("test-namespace".to_string()),
608 role_id: PlainOrEnvValue::Plain {
609 value: SecretString::new("test-role-id"),
610 },
611 secret_id: PlainOrEnvValue::Plain {
612 value: SecretString::new("test-secret-id"),
613 },
614 mount_point: Some("secret".to_string()),
615 }),
616 };
617
618 let result = process_signer(&signer).await;
619
620 assert!(
621 result.is_ok(),
622 "Failed to process Vault signer: {:?}",
623 result.err()
624 );
625 let model = result.unwrap();
626
627 assert_eq!(model.id, "vault-signer");
628
629 match model.config {
630 SignerConfigStorage::Vault(_) => {}
631 _ => panic!("Expected Vault config"),
632 }
633
634 Ok(())
635 }
636
637 #[tokio::test]
638 async fn test_process_signers() -> Result<()> {
639 let signers = vec![
641 SignerFileConfig {
642 id: "test-signer-1".to_string(),
643 config: SignerFileConfigEnum::Local(LocalSignerFileConfig {
644 path: "tests/utils/test_keys/unit-test-local-signer.json".to_string(),
645 passphrase: PlainOrEnvValue::Plain {
646 value: SecretString::new("test"),
647 },
648 }),
649 },
650 SignerFileConfig {
651 id: "test-signer-2".to_string(),
652 config: SignerFileConfigEnum::Local(LocalSignerFileConfig {
653 path: "tests/utils/test_keys/unit-test-local-signer.json".to_string(),
654 passphrase: PlainOrEnvValue::Plain {
655 value: SecretString::new("test"),
656 },
657 }),
658 },
659 ];
660
661 let config = Config {
663 signers,
664 relayers: vec![],
665 notifications: vec![],
666 networks: NetworksFileConfig::new(vec![]).unwrap(),
667 plugins: Some(vec![]),
668 };
669
670 let app_state = ThinData(create_test_app_state());
672
673 process_signers(&config, &app_state).await?;
675
676 let stored_signers = app_state.signer_repository.list_all().await?;
678 assert_eq!(stored_signers.len(), 2);
679 assert!(stored_signers.iter().any(|s| s.id == "test-signer-1"));
680 assert!(stored_signers.iter().any(|s| s.id == "test-signer-2"));
681
682 Ok(())
683 }
684
685 #[tokio::test]
686 async fn test_process_notifications() -> Result<()> {
687 let notifications = vec![
689 NotificationConfig {
690 id: "test-notification-1".to_string(),
691 r#type: NotificationType::Webhook,
692 url: "https://hooks.slack.com/test1".to_string(),
693 signing_key: None,
694 },
695 NotificationConfig {
696 id: "test-notification-2".to_string(),
697 r#type: NotificationType::Webhook,
698 url: "https://hooks.slack.com/test2".to_string(),
699 signing_key: None,
700 },
701 ];
702
703 let config = Config {
705 signers: vec![],
706 relayers: vec![],
707 notifications,
708 networks: NetworksFileConfig::new(vec![]).unwrap(),
709 plugins: Some(vec![]),
710 };
711
712 let app_state = ThinData(create_test_app_state());
714
715 process_notifications(&config, &app_state).await?;
717
718 let stored_notifications = app_state.notification_repository.list_all().await?;
720 assert_eq!(stored_notifications.len(), 2);
721 assert!(stored_notifications
722 .iter()
723 .any(|n| n.id == "test-notification-1"));
724 assert!(stored_notifications
725 .iter()
726 .any(|n| n.id == "test-notification-2"));
727
728 Ok(())
729 }
730
731 #[tokio::test]
732 async fn test_process_networks_empty() -> Result<()> {
733 let config = Config {
734 signers: vec![],
735 relayers: vec![],
736 notifications: vec![],
737 networks: NetworksFileConfig::new(vec![]).unwrap(),
738 plugins: Some(vec![]),
739 };
740
741 let app_state = ThinData(create_test_app_state());
742
743 process_networks(&config, &app_state).await?;
744
745 let stored_networks = app_state.network_repository.list_all().await?;
746 assert_eq!(stored_networks.len(), 0);
747
748 Ok(())
749 }
750
751 #[tokio::test]
752 async fn test_process_networks_single_evm() -> Result<()> {
753 use crate::config::network::test_utils::*;
754
755 let networks = vec![create_evm_network_wrapped("mainnet")];
756
757 let config = Config {
758 signers: vec![],
759 relayers: vec![],
760 notifications: vec![],
761 networks: NetworksFileConfig::new(networks).unwrap(),
762 plugins: Some(vec![]),
763 };
764
765 let app_state = ThinData(create_test_app_state());
766
767 process_networks(&config, &app_state).await?;
768
769 let stored_networks = app_state.network_repository.list_all().await?;
770 assert_eq!(stored_networks.len(), 1);
771 assert_eq!(stored_networks[0].name, "mainnet");
772 assert_eq!(stored_networks[0].network_type, NetworkType::Evm);
773
774 Ok(())
775 }
776
777 #[tokio::test]
778 async fn test_process_networks_single_solana() -> Result<()> {
779 use crate::config::network::test_utils::*;
780
781 let networks = vec![create_solana_network_wrapped("devnet")];
782
783 let config = Config {
784 signers: vec![],
785 relayers: vec![],
786 notifications: vec![],
787 networks: NetworksFileConfig::new(networks).unwrap(),
788 plugins: Some(vec![]),
789 };
790
791 let app_state = ThinData(create_test_app_state());
792
793 process_networks(&config, &app_state).await?;
794
795 let stored_networks = app_state.network_repository.list_all().await?;
796 assert_eq!(stored_networks.len(), 1);
797 assert_eq!(stored_networks[0].name, "devnet");
798 assert_eq!(stored_networks[0].network_type, NetworkType::Solana);
799
800 Ok(())
801 }
802
803 #[tokio::test]
804 async fn test_process_networks_multiple_mixed() -> Result<()> {
805 use crate::config::network::test_utils::*;
806
807 let networks = vec![
808 create_evm_network_wrapped("mainnet"),
809 create_solana_network_wrapped("devnet"),
810 create_evm_network_wrapped("sepolia"),
811 create_solana_network_wrapped("testnet"),
812 ];
813
814 let config = Config {
815 signers: vec![],
816 relayers: vec![],
817 notifications: vec![],
818 networks: NetworksFileConfig::new(networks).unwrap(),
819 plugins: Some(vec![]),
820 };
821
822 let app_state = ThinData(create_test_app_state());
823
824 process_networks(&config, &app_state).await?;
825
826 let stored_networks = app_state.network_repository.list_all().await?;
827 assert_eq!(stored_networks.len(), 4);
828
829 let evm_networks: Vec<_> = stored_networks
830 .iter()
831 .filter(|n| n.network_type == NetworkType::Evm)
832 .collect();
833 assert_eq!(evm_networks.len(), 2);
834 assert!(evm_networks.iter().any(|n| n.name == "mainnet"));
835 assert!(evm_networks.iter().any(|n| n.name == "sepolia"));
836
837 let solana_networks: Vec<_> = stored_networks
838 .iter()
839 .filter(|n| n.network_type == NetworkType::Solana)
840 .collect();
841 assert_eq!(solana_networks.len(), 2);
842 assert!(solana_networks.iter().any(|n| n.name == "devnet"));
843 assert!(solana_networks.iter().any(|n| n.name == "testnet"));
844
845 Ok(())
846 }
847
848 #[tokio::test]
849 async fn test_process_networks_many_networks() -> Result<()> {
850 use crate::config::network::test_utils::*;
851
852 let networks = (0..10)
853 .map(|i| create_evm_network_wrapped(&format!("network-{}", i)))
854 .collect();
855
856 let config = Config {
857 signers: vec![],
858 relayers: vec![],
859 notifications: vec![],
860 networks: NetworksFileConfig::new(networks).unwrap(),
861 plugins: Some(vec![]),
862 };
863
864 let app_state = ThinData(create_test_app_state());
865
866 process_networks(&config, &app_state).await?;
867
868 let stored_networks = app_state.network_repository.list_all().await?;
869 assert_eq!(stored_networks.len(), 10);
870
871 for i in 0..10 {
872 let expected_name = format!("network-{}", i);
873 assert!(
874 stored_networks.iter().any(|n| n.name == expected_name),
875 "Network {} not found",
876 expected_name
877 );
878 }
879
880 Ok(())
881 }
882
883 #[tokio::test]
884 async fn test_process_networks_duplicate_names() -> Result<()> {
885 use crate::config::network::test_utils::*;
886
887 let networks = vec![
888 create_evm_network_wrapped("mainnet"),
889 create_solana_network_wrapped("mainnet"),
890 ];
891
892 let config = Config {
893 signers: vec![],
894 relayers: vec![],
895 notifications: vec![],
896 networks: NetworksFileConfig::new(networks).unwrap(),
897 plugins: Some(vec![]),
898 };
899
900 let app_state = ThinData(create_test_app_state());
901
902 process_networks(&config, &app_state).await?;
903
904 let stored_networks = app_state.network_repository.list_all().await?;
905 assert_eq!(stored_networks.len(), 2);
906
907 let mainnet_networks: Vec<_> = stored_networks
908 .iter()
909 .filter(|n| n.name == "mainnet")
910 .collect();
911 assert_eq!(mainnet_networks.len(), 2);
912 assert!(mainnet_networks
913 .iter()
914 .any(|n| n.network_type == NetworkType::Evm));
915 assert!(mainnet_networks
916 .iter()
917 .any(|n| n.network_type == NetworkType::Solana));
918
919 Ok(())
920 }
921
922 #[tokio::test]
923 async fn test_process_networks() -> Result<()> {
924 use crate::config::network::test_utils::*;
925
926 let networks = vec![
927 create_evm_network_wrapped("mainnet"),
928 create_solana_network_wrapped("devnet"),
929 ];
930
931 let config = Config {
932 signers: vec![],
933 relayers: vec![],
934 notifications: vec![],
935 networks: NetworksFileConfig::new(networks).unwrap(),
936 plugins: Some(vec![]),
937 };
938
939 let app_state = ThinData(create_test_app_state());
940
941 process_networks(&config, &app_state).await?;
942
943 let stored_networks = app_state.network_repository.list_all().await?;
944 assert_eq!(stored_networks.len(), 2);
945 assert!(stored_networks
946 .iter()
947 .any(|n| n.name == "mainnet" && n.network_type == NetworkType::Evm));
948 assert!(stored_networks
949 .iter()
950 .any(|n| n.name == "devnet" && n.network_type == NetworkType::Solana));
951
952 Ok(())
953 }
954
955 #[tokio::test]
956 async fn test_process_relayers() -> Result<()> {
957 let signers = vec![SignerFileConfig {
959 id: "test-signer-1".to_string(),
960 config: SignerFileConfigEnum::Local(LocalSignerFileConfig {
961 path: "tests/utils/test_keys/unit-test-local-signer.json".to_string(),
962 passphrase: PlainOrEnvValue::Plain {
963 value: SecretString::new("test"),
964 },
965 }),
966 }];
967
968 let relayers = vec![RelayerFileConfig {
970 id: "test-relayer-1".to_string(),
971 network_type: ConfigFileNetworkType::Evm,
972 signer_id: "test-signer-1".to_string(),
973 name: "test-relayer-1".to_string(),
974 network: "test-network".to_string(),
975 paused: false,
976 policies: None,
977 notification_id: None,
978 custom_rpc_urls: None,
979 }];
980
981 let config = Config {
983 signers: signers.clone(),
984 relayers,
985 notifications: vec![],
986 networks: NetworksFileConfig::new(vec![]).unwrap(),
987 plugins: Some(vec![]),
988 };
989
990 let app_state = ThinData(create_test_app_state());
992
993 process_signers(&config, &app_state).await?;
995
996 process_relayers(&config, &app_state).await?;
998
999 let stored_relayers = app_state.relayer_repository.list_all().await?;
1001 assert_eq!(stored_relayers.len(), 1);
1002 assert_eq!(stored_relayers[0].id, "test-relayer-1");
1003 assert_eq!(stored_relayers[0].signer_id, "test-signer-1");
1004 assert!(!stored_relayers[0].address.is_empty()); Ok(())
1007 }
1008
1009 #[tokio::test]
1010 async fn test_process_plugins() -> Result<()> {
1011 let plugins = vec![
1013 PluginFileConfig {
1014 id: "test-plugin-1".to_string(),
1015 path: "/app/plugins/test.ts".to_string(),
1016 timeout: None,
1017 },
1018 PluginFileConfig {
1019 id: "test-plugin-2".to_string(),
1020 path: "/app/plugins/test2.ts".to_string(),
1021 timeout: Some(12),
1022 },
1023 ];
1024
1025 let config = Config {
1027 signers: vec![],
1028 relayers: vec![],
1029 notifications: vec![],
1030 networks: NetworksFileConfig::new(vec![]).unwrap(),
1031 plugins: Some(plugins),
1032 };
1033
1034 let app_state = ThinData(create_test_app_state());
1036
1037 process_plugins(&config, &app_state).await?;
1039
1040 let plugin_1 = app_state
1042 .plugin_repository
1043 .get_by_id("test-plugin-1")
1044 .await?;
1045 let plugin_2 = app_state
1046 .plugin_repository
1047 .get_by_id("test-plugin-2")
1048 .await?;
1049
1050 assert!(plugin_1.is_some());
1051 assert!(plugin_2.is_some());
1052
1053 let plugin_1 = plugin_1.unwrap();
1054 let plugin_2 = plugin_2.unwrap();
1055
1056 assert_eq!(plugin_1.path, "/app/plugins/test.ts");
1057 assert_eq!(plugin_2.path, "/app/plugins/test2.ts");
1058
1059 assert_eq!(
1061 plugin_1.timeout.as_secs(),
1062 Duration::from_secs(DEFAULT_PLUGIN_TIMEOUT_SECONDS).as_secs()
1063 );
1064 assert_eq!(
1065 plugin_2.timeout.as_secs(),
1066 Duration::from_secs(12).as_secs()
1067 );
1068
1069 Ok(())
1070 }
1071
1072 #[tokio::test]
1073 async fn test_process_config_file() -> Result<()> {
1074 let signers = vec![SignerFileConfig {
1076 id: "test-signer-1".to_string(),
1077 config: SignerFileConfigEnum::Local(LocalSignerFileConfig {
1078 path: "tests/utils/test_keys/unit-test-local-signer.json".to_string(),
1079 passphrase: PlainOrEnvValue::Plain {
1080 value: SecretString::new("test"),
1081 },
1082 }),
1083 }];
1084
1085 let relayers = vec![RelayerFileConfig {
1086 id: "test-relayer-1".to_string(),
1087 network_type: ConfigFileNetworkType::Evm,
1088 signer_id: "test-signer-1".to_string(),
1089 name: "test-relayer-1".to_string(),
1090 network: "test-network".to_string(),
1091 paused: false,
1092 policies: None,
1093 notification_id: None,
1094 custom_rpc_urls: None,
1095 }];
1096
1097 let notifications = vec![NotificationConfig {
1098 id: "test-notification-1".to_string(),
1099 r#type: NotificationType::Webhook,
1100 url: "https://hooks.slack.com/test1".to_string(),
1101 signing_key: None,
1102 }];
1103
1104 let plugins = vec![PluginFileConfig {
1105 id: "test-plugin-1".to_string(),
1106 path: "/app/plugins/test.ts".to_string(),
1107 timeout: None,
1108 }];
1109
1110 let config = Config {
1112 signers,
1113 relayers,
1114 notifications,
1115 networks: NetworksFileConfig::new(vec![]).unwrap(),
1116 plugins: Some(plugins),
1117 };
1118
1119 let signer_repo = Arc::new(InMemorySignerRepository::default());
1121 let relayer_repo = Arc::new(RelayerRepositoryStorage::new_in_memory());
1122 let notification_repo = Arc::new(InMemoryNotificationRepository::default());
1123 let network_repo = Arc::new(InMemoryNetworkRepository::default());
1124 let transaction_repo = Arc::new(TransactionRepositoryStorage::InMemory(
1125 InMemoryTransactionRepository::new(),
1126 ));
1127 let transaction_counter = Arc::new(InMemoryTransactionCounter::default());
1128 let plugin_repo = Arc::new(InMemoryPluginRepository::default());
1129
1130 let mut mock_job_producer = MockJobProducerTrait::new();
1132 mock_job_producer
1133 .expect_produce_transaction_request_job()
1134 .returning(|_, _| Box::pin(async { Ok(()) }));
1135 mock_job_producer
1136 .expect_produce_submit_transaction_job()
1137 .returning(|_, _| Box::pin(async { Ok(()) }));
1138 mock_job_producer
1139 .expect_produce_check_transaction_status_job()
1140 .returning(|_, _| Box::pin(async { Ok(()) }));
1141 mock_job_producer
1142 .expect_produce_send_notification_job()
1143 .returning(|_, _| Box::pin(async { Ok(()) }));
1144 let job_producer = Arc::new(mock_job_producer);
1145
1146 let app_state = ThinData(AppState {
1148 signer_repository: signer_repo.clone(),
1149 relayer_repository: relayer_repo.clone(),
1150 notification_repository: notification_repo.clone(),
1151 network_repository: network_repo.clone(),
1152 transaction_repository: transaction_repo.clone(),
1153 transaction_counter_store: transaction_counter.clone(),
1154 job_producer: job_producer.clone(),
1155 plugin_repository: plugin_repo.clone(),
1156 });
1157
1158 let server_config = Arc::new(crate::utils::mocks::mockutils::create_test_server_config(
1160 RepositoryStorageType::InMemory,
1161 ));
1162 process_config_file(config, server_config, &app_state).await?;
1163
1164 let stored_signers = signer_repo.list_all().await?;
1166 assert_eq!(stored_signers.len(), 1);
1167 assert_eq!(stored_signers[0].id, "test-signer-1");
1168
1169 let stored_relayers = relayer_repo.list_all().await?;
1170 assert_eq!(stored_relayers.len(), 1);
1171 assert_eq!(stored_relayers[0].id, "test-relayer-1");
1172 assert_eq!(stored_relayers[0].signer_id, "test-signer-1");
1173
1174 let stored_notifications = notification_repo.list_all().await?;
1175 assert_eq!(stored_notifications.len(), 1);
1176 assert_eq!(stored_notifications[0].id, "test-notification-1");
1177
1178 let stored_plugin = plugin_repo.get_by_id("test-plugin-1").await?;
1179 assert_eq!(stored_plugin.unwrap().path, "/app/plugins/test.ts");
1180
1181 Ok(())
1182 }
1183
1184 #[tokio::test]
1185 async fn test_process_signer_google_cloud_kms() {
1186 use crate::models::SecretString;
1187
1188 let signer = SignerFileConfig {
1189 id: "gcp-kms-signer".to_string(),
1190 config: SignerFileConfigEnum::GoogleCloudKms(GoogleCloudKmsSignerFileConfig {
1191 service_account: GoogleCloudKmsServiceAccountFileConfig {
1192 private_key: PlainOrEnvValue::Plain {
1193 value: SecretString::new("-----BEGIN EXAMPLE PRIVATE KEY-----\nFAKEKEYDATA\n-----END EXAMPLE PRIVATE KEY-----\n"),
1194 },
1195 client_email: PlainOrEnvValue::Plain {
1196 value: SecretString::new("test-service-account@example.com"),
1197 },
1198 private_key_id: PlainOrEnvValue::Plain {
1199 value: SecretString::new("fake-private-key-id"),
1200 },
1201 client_id: "fake-client-id".to_string(),
1202 project_id: "fake-project-id".to_string(),
1203 auth_uri: "https://accounts.google.com/o/oauth2/auth".to_string(),
1204 token_uri: "https://oauth2.googleapis.com/token".to_string(),
1205 client_x509_cert_url: "https://www.googleapis.com/robot/v1/metadata/x509/test-service-account%40example.com".to_string(),
1206 auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs".to_string(),
1207 universe_domain: "googleapis.com".to_string(),
1208 },
1209 key: GoogleCloudKmsKeyFileConfig {
1210 location: "global".to_string(),
1211 key_id: "fake-key-id".to_string(),
1212 key_ring_id: "fake-key-ring-id".to_string(),
1213 key_version: 1,
1214 },
1215 }),
1216 };
1217
1218 let result = process_signer(&signer).await;
1219
1220 assert!(
1221 result.is_ok(),
1222 "Failed to process Google Cloud KMS signer: {:?}",
1223 result.err()
1224 );
1225 let model = result.unwrap();
1226
1227 assert_eq!(model.id, "gcp-kms-signer");
1228 }
1229
1230 #[tokio::test]
1231 async fn test_is_redis_populated_empty_repositories() -> Result<()> {
1232 let app_state = ThinData(create_test_app_state());
1234
1235 assert!(!app_state.relayer_repository.has_entries().await?);
1237 assert!(!app_state.transaction_repository.has_entries().await?);
1238 assert!(!app_state.signer_repository.has_entries().await?);
1239 assert!(!app_state.notification_repository.has_entries().await?);
1240 assert!(!app_state.network_repository.has_entries().await?);
1241
1242 let result = is_redis_populated(&app_state).await?;
1244 assert!(!result, "Expected false when all repositories are empty");
1245
1246 Ok(())
1247 }
1248
1249 #[tokio::test]
1250 async fn test_is_redis_populated_relayer_repository_has_entries() -> Result<()> {
1251 let app_state = ThinData(create_test_app_state());
1252
1253 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1255 app_state.relayer_repository.create(relayer).await?;
1256
1257 assert!(app_state.relayer_repository.has_entries().await?);
1259
1260 let result = is_redis_populated(&app_state).await?;
1262 assert!(result, "Expected true when relayer repository has entries");
1263
1264 Ok(())
1265 }
1266
1267 #[tokio::test]
1268 async fn test_is_redis_populated_transaction_repository_has_entries() -> Result<()> {
1269 let app_state = ThinData(create_test_app_state());
1270
1271 let transaction = TransactionRepoModel::default();
1273 app_state.transaction_repository.create(transaction).await?;
1274
1275 assert!(app_state.transaction_repository.has_entries().await?);
1277
1278 let result = is_redis_populated(&app_state).await?;
1280 assert!(
1281 result,
1282 "Expected true when transaction repository has entries"
1283 );
1284
1285 Ok(())
1286 }
1287
1288 #[tokio::test]
1289 async fn test_is_redis_populated_signer_repository_has_entries() -> Result<()> {
1290 let app_state = ThinData(create_test_app_state());
1291
1292 let signer = create_mock_signer();
1294 app_state.signer_repository.create(signer).await?;
1295
1296 assert!(app_state.signer_repository.has_entries().await?);
1298
1299 let result = is_redis_populated(&app_state).await?;
1301 assert!(result, "Expected true when signer repository has entries");
1302
1303 Ok(())
1304 }
1305
1306 #[tokio::test]
1307 async fn test_is_redis_populated_notification_repository_has_entries() -> Result<()> {
1308 let app_state = ThinData(create_test_app_state());
1309
1310 let notification = create_mock_notification("test-notification".to_string());
1312 app_state
1313 .notification_repository
1314 .create(notification)
1315 .await?;
1316
1317 assert!(app_state.notification_repository.has_entries().await?);
1319
1320 let result = is_redis_populated(&app_state).await?;
1322 assert!(
1323 result,
1324 "Expected true when notification repository has entries"
1325 );
1326
1327 Ok(())
1328 }
1329
1330 #[tokio::test]
1331 async fn test_is_redis_populated_network_repository_has_entries() -> Result<()> {
1332 let app_state = ThinData(create_test_app_state());
1333
1334 let network = create_mock_network();
1336 app_state.network_repository.create(network).await?;
1337
1338 assert!(app_state.network_repository.has_entries().await?);
1340
1341 let result = is_redis_populated(&app_state).await?;
1343 assert!(result, "Expected true when network repository has entries");
1344
1345 Ok(())
1346 }
1347
1348 #[tokio::test]
1349 async fn test_is_redis_populated_multiple_repositories_have_entries() -> Result<()> {
1350 let app_state = ThinData(create_test_app_state());
1351
1352 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1354 let signer = create_mock_signer();
1355 let notification = create_mock_notification("test-notification".to_string());
1356 let network = create_mock_network();
1357
1358 app_state.relayer_repository.create(relayer).await?;
1359 app_state.signer_repository.create(signer).await?;
1360 app_state
1361 .notification_repository
1362 .create(notification)
1363 .await?;
1364 app_state.network_repository.create(network).await?;
1365
1366 assert!(app_state.relayer_repository.has_entries().await?);
1368 assert!(app_state.signer_repository.has_entries().await?);
1369 assert!(app_state.notification_repository.has_entries().await?);
1370 assert!(app_state.network_repository.has_entries().await?);
1371
1372 let result = is_redis_populated(&app_state).await?;
1374 assert!(
1375 result,
1376 "Expected true when multiple repositories have entries"
1377 );
1378
1379 Ok(())
1380 }
1381
1382 #[tokio::test]
1383 async fn test_is_redis_populated_comprehensive_scenario() -> Result<()> {
1384 let app_state = ThinData(create_test_app_state());
1385
1386 let result = is_redis_populated(&app_state).await?;
1388 assert!(!result, "Expected false when all repositories are empty");
1389
1390 let relayer = create_mock_relayer("test-relayer".to_string(), false);
1392 app_state.relayer_repository.create(relayer).await?;
1393 let result = is_redis_populated(&app_state).await?;
1394 assert!(result, "Expected true after adding one entry");
1395
1396 app_state.relayer_repository.drop_all_entries().await?;
1398 let result = is_redis_populated(&app_state).await?;
1399 assert!(!result, "Expected false after clearing all repositories");
1400
1401 let signer = create_mock_signer();
1403 app_state.signer_repository.create(signer).await?;
1404 let result = is_redis_populated(&app_state).await?;
1405 assert!(result, "Expected true after adding signer");
1406
1407 let notification = create_mock_notification("test-notification".to_string());
1408 app_state
1409 .notification_repository
1410 .create(notification)
1411 .await?;
1412 let result = is_redis_populated(&app_state).await?;
1413 assert!(result, "Expected true after adding notification");
1414
1415 Ok(())
1416 }
1417
1418 fn create_test_server_config_with_settings(
1420 storage_type: RepositoryStorageType,
1421 reset_storage_on_start: bool,
1422 ) -> ServerConfig {
1423 ServerConfig {
1424 repository_storage_type: storage_type.clone(),
1425 reset_storage_on_start,
1426 ..create_test_server_config(storage_type)
1427 }
1428 }
1429
1430 fn create_minimal_test_config() -> Config {
1432 Config {
1433 signers: vec![SignerFileConfig {
1434 id: "test-signer-1".to_string(),
1435 config: SignerFileConfigEnum::Local(LocalSignerFileConfig {
1436 path: "tests/utils/test_keys/unit-test-local-signer.json".to_string(),
1437 passphrase: PlainOrEnvValue::Plain {
1438 value: SecretString::new("test"),
1439 },
1440 }),
1441 }],
1442 relayers: vec![RelayerFileConfig {
1443 id: "test-relayer-1".to_string(),
1444 network_type: ConfigFileNetworkType::Evm,
1445 signer_id: "test-signer-1".to_string(),
1446 name: "test-relayer-1".to_string(),
1447 network: "test-network".to_string(),
1448 paused: false,
1449 policies: None,
1450 notification_id: None,
1451 custom_rpc_urls: None,
1452 }],
1453 notifications: vec![NotificationConfig {
1454 id: "test-notification-1".to_string(),
1455 r#type: NotificationType::Webhook,
1456 url: "https://hooks.slack.com/test1".to_string(),
1457 signing_key: None,
1458 }],
1459 networks: NetworksFileConfig::new(vec![]).unwrap(),
1460 plugins: None,
1461 }
1462 }
1463
1464 #[tokio::test]
1465 async fn test_should_process_config_file_inmemory_storage() -> Result<()> {
1466 let config = create_minimal_test_config();
1467
1468 let server_config = Arc::new(create_test_server_config_with_settings(
1470 RepositoryStorageType::InMemory,
1471 false,
1472 ));
1473
1474 let app_state = ThinData(create_test_app_state());
1475 process_config_file(config.clone(), server_config.clone(), &app_state).await?;
1476
1477 let stored_relayers = app_state.relayer_repository.list_all().await?;
1478 assert_eq!(stored_relayers.len(), 1);
1479 assert_eq!(stored_relayers[0].id, "test-relayer-1");
1480
1481 let server_config2 = Arc::new(create_test_server_config_with_settings(
1483 RepositoryStorageType::InMemory,
1484 true,
1485 ));
1486
1487 let app_state2 = ThinData(create_test_app_state());
1488 process_config_file(config.clone(), server_config2, &app_state2).await?;
1489
1490 let stored_relayers = app_state2.relayer_repository.list_all().await?;
1491 assert_eq!(stored_relayers.len(), 1);
1492 assert_eq!(stored_relayers[0].id, "test-relayer-1");
1493
1494 Ok(())
1495 }
1496
1497 #[tokio::test]
1498 async fn test_should_process_config_file_redis_storage_empty_repositories() -> Result<()> {
1499 let config = create_minimal_test_config();
1500 let server_config = Arc::new(create_test_server_config_with_settings(
1501 RepositoryStorageType::Redis,
1502 false,
1503 ));
1504
1505 let app_state = ThinData(create_test_app_state());
1506 process_config_file(config, server_config, &app_state).await?;
1507
1508 let stored_relayers = app_state.relayer_repository.list_all().await?;
1509 assert_eq!(stored_relayers.len(), 1);
1510 assert_eq!(stored_relayers[0].id, "test-relayer-1");
1511
1512 Ok(())
1513 }
1514
1515 #[tokio::test]
1516 async fn test_should_not_process_config_file_redis_storage_populated_repositories() -> Result<()>
1517 {
1518 let config = create_minimal_test_config();
1519 let server_config = Arc::new(create_test_server_config_with_settings(
1520 RepositoryStorageType::Redis,
1521 false,
1522 ));
1523
1524 let app_state1 = ThinData(create_test_app_state());
1526 let app_state2 = ThinData(create_test_app_state());
1527
1528 let existing_relayer1 = create_mock_relayer("existing-relayer".to_string(), false);
1530 let existing_relayer2 = create_mock_relayer("existing-relayer".to_string(), false);
1531 app_state1
1532 .relayer_repository
1533 .create(existing_relayer1)
1534 .await?;
1535 app_state2
1536 .relayer_repository
1537 .create(existing_relayer2)
1538 .await?;
1539
1540 assert!(app_state1.relayer_repository.has_entries().await?);
1542 assert!(!app_state1.signer_repository.has_entries().await?);
1543
1544 process_config_file(config, server_config, &app_state2).await?;
1546
1547 let relayer_from_config = app_state2
1548 .relayer_repository
1549 .get_by_id("test-relayer-1".to_string())
1550 .await;
1551 assert!(
1552 relayer_from_config.is_err(),
1553 "Relayer from config should not be found"
1554 );
1555
1556 let existing_relayer = app_state2
1557 .relayer_repository
1558 .get_by_id("existing-relayer".to_string())
1559 .await?;
1560 assert_eq!(existing_relayer.id, "existing-relayer");
1561
1562 Ok(())
1564 }
1565
1566 #[tokio::test]
1567 async fn test_should_process_config_file_redis_storage_with_reset_flag() -> Result<()> {
1568 let config = create_minimal_test_config();
1569 let server_config = Arc::new(create_test_server_config_with_settings(
1570 RepositoryStorageType::Redis,
1571 true, ));
1573
1574 let app_state = ThinData(create_test_app_state());
1575
1576 let existing_relayer = create_mock_relayer("existing-relayer".to_string(), false);
1578 let existing_signer = create_mock_signer();
1579 app_state
1580 .relayer_repository
1581 .create(existing_relayer)
1582 .await?;
1583 app_state.signer_repository.create(existing_signer).await?;
1584
1585 process_config_file(config, server_config, &app_state).await?;
1587
1588 let stored_relayer = app_state
1589 .relayer_repository
1590 .get_by_id("existing-relayer".to_string())
1591 .await;
1592 assert!(
1593 stored_relayer.is_err(),
1594 "Existing relayer should not be found"
1595 );
1596
1597 let stored_signer = app_state
1598 .signer_repository
1599 .get_by_id("existing-signer".to_string())
1600 .await;
1601 assert!(
1602 stored_signer.is_err(),
1603 "Existing signer should not be found"
1604 );
1605
1606 Ok(())
1607 }
1608}