openzeppelin_relayer/bootstrap/
initialize_app_state.rs1use crate::{
6 config::{RepositoryStorageType, ServerConfig},
7 jobs,
8 models::{AppState, DefaultAppState},
9 queues::create_queue_backend,
10 repositories::{
11 ApiKeyRepositoryStorage, NetworkRepositoryStorage, NotificationRepositoryStorage,
12 PluginRepositoryStorage, RelayerRepositoryStorage, SignerRepositoryStorage,
13 TransactionCounterRepositoryStorage, TransactionRepositoryStorage,
14 },
15 utils::{initialize_redis_connections, RedisConnections},
16};
17use actix_web::web;
18use color_eyre::Result;
19use std::sync::Arc;
20use tracing::warn;
21
22pub struct RepositoryCollection {
23 pub relayer: Arc<RelayerRepositoryStorage>,
24 pub transaction: Arc<TransactionRepositoryStorage>,
25 pub signer: Arc<SignerRepositoryStorage>,
26 pub notification: Arc<NotificationRepositoryStorage>,
27 pub network: Arc<NetworkRepositoryStorage>,
28 pub transaction_counter: Arc<TransactionCounterRepositoryStorage>,
29 pub plugin: Arc<PluginRepositoryStorage>,
30 pub api_key: Arc<ApiKeyRepositoryStorage>,
31}
32
33pub async fn initialize_repositories(
46 config: &ServerConfig,
47 connections: Option<Arc<RedisConnections>>,
48) -> eyre::Result<RepositoryCollection> {
49 let repositories = match config.repository_storage_type {
50 RepositoryStorageType::InMemory => RepositoryCollection {
51 relayer: Arc::new(RelayerRepositoryStorage::new_in_memory()),
52 transaction: Arc::new(TransactionRepositoryStorage::new_in_memory()),
53 signer: Arc::new(SignerRepositoryStorage::new_in_memory()),
54 notification: Arc::new(NotificationRepositoryStorage::new_in_memory()),
55 network: Arc::new(NetworkRepositoryStorage::new_in_memory()),
56 transaction_counter: Arc::new(TransactionCounterRepositoryStorage::new_in_memory()),
57 plugin: Arc::new(PluginRepositoryStorage::new_in_memory()),
58 api_key: Arc::new(ApiKeyRepositoryStorage::new_in_memory()),
59 },
60 RepositoryStorageType::Redis => {
61 if config.storage_encryption_key.is_none() {
62 warn!("⚠️ Storage encryption key is not set. Please set the STORAGE_ENCRYPTION_KEY environment variable.");
63 return Err(eyre::eyre!("Storage encryption key is not set. Please set the STORAGE_ENCRYPTION_KEY environment variable."));
64 }
65
66 let connections = connections
67 .ok_or_else(|| eyre::eyre!("Redis connections required for Redis storage type"))?;
68
69 RepositoryCollection {
71 relayer: Arc::new(RelayerRepositoryStorage::new_redis(
72 connections.clone(),
73 config.redis_key_prefix.clone(),
74 )?),
75 transaction: Arc::new(TransactionRepositoryStorage::new_redis(
76 connections.clone(),
77 config.redis_key_prefix.clone(),
78 )?),
79 signer: Arc::new(SignerRepositoryStorage::new_redis(
80 connections.clone(),
81 config.redis_key_prefix.clone(),
82 )?),
83 notification: Arc::new(NotificationRepositoryStorage::new_redis(
84 connections.clone(),
85 config.redis_key_prefix.clone(),
86 )?),
87 network: Arc::new(NetworkRepositoryStorage::new_redis(
88 connections.clone(),
89 config.redis_key_prefix.clone(),
90 )?),
91 transaction_counter: Arc::new(TransactionCounterRepositoryStorage::new_redis(
92 connections.clone(),
93 config.redis_key_prefix.clone(),
94 )?),
95 plugin: Arc::new(PluginRepositoryStorage::new_redis(
96 connections.clone(),
97 config.redis_key_prefix.clone(),
98 )?),
99 api_key: Arc::new(ApiKeyRepositoryStorage::new_redis(
100 connections,
101 config.redis_key_prefix.clone(),
102 )?),
103 }
104 }
105 };
106
107 Ok(repositories)
108}
109
110pub async fn initialize_app_state(
122 server_config: Arc<ServerConfig>,
123) -> Result<web::ThinData<DefaultAppState>> {
124 let redis_connections = initialize_redis_connections(&server_config).await?;
128
129 let repo_connections = match server_config.repository_storage_type {
131 RepositoryStorageType::Redis => Some(redis_connections.clone()),
132 RepositoryStorageType::InMemory => None,
133 };
134
135 let repositories = initialize_repositories(&server_config, repo_connections).await?;
136
137 let queue_backend = create_queue_backend(redis_connections).await?;
138 let job_producer = Arc::new(jobs::JobProducer::new(queue_backend));
139
140 let app_state = web::ThinData(AppState {
141 relayer_repository: repositories.relayer,
142 transaction_repository: repositories.transaction,
143 signer_repository: repositories.signer,
144 network_repository: repositories.network,
145 notification_repository: repositories.notification,
146 transaction_counter_store: repositories.transaction_counter,
147 job_producer,
148 plugin_repository: repositories.plugin,
149 api_key_repository: repositories.api_key,
150 });
151
152 Ok(app_state)
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use crate::{
159 config::RepositoryStorageType,
160 models::SecretString,
161 repositories::{ApiKeyRepositoryTrait, Repository},
162 utils::mocks::mockutils::{
163 create_mock_api_key, create_mock_network, create_mock_relayer, create_mock_signer,
164 create_test_server_config,
165 },
166 };
167 use std::sync::Arc;
168
169 #[tokio::test]
170 async fn test_initialize_repositories_in_memory() {
171 let config = create_test_server_config(RepositoryStorageType::InMemory);
172 let result = initialize_repositories(&config, None).await;
174
175 assert!(result.is_ok());
176 let repositories = result.unwrap();
177
178 assert!(Arc::strong_count(&repositories.relayer) >= 1);
180 assert!(Arc::strong_count(&repositories.transaction) >= 1);
181 assert!(Arc::strong_count(&repositories.signer) >= 1);
182 assert!(Arc::strong_count(&repositories.notification) >= 1);
183 assert!(Arc::strong_count(&repositories.network) >= 1);
184 assert!(Arc::strong_count(&repositories.transaction_counter) >= 1);
185 assert!(Arc::strong_count(&repositories.plugin) >= 1);
186 assert!(Arc::strong_count(&repositories.api_key) >= 1);
187 }
188
189 #[tokio::test]
190 async fn test_repository_collection_functionality() {
191 let config = create_test_server_config(RepositoryStorageType::InMemory);
192 let repositories = initialize_repositories(&config, None).await.unwrap();
194
195 let relayer = create_mock_relayer("test-relayer".to_string(), false);
197 let signer = create_mock_signer();
198 let network = create_mock_network();
199 let api_key = create_mock_api_key();
200
201 repositories.relayer.create(relayer.clone()).await.unwrap();
203 repositories.signer.create(signer.clone()).await.unwrap();
204 repositories.network.create(network.clone()).await.unwrap();
205 repositories.api_key.create(api_key.clone()).await.unwrap();
206
207 let retrieved_relayer = repositories
208 .relayer
209 .get_by_id("test-relayer".to_string())
210 .await
211 .unwrap();
212 let retrieved_signer = repositories
213 .signer
214 .get_by_id("test".to_string())
215 .await
216 .unwrap();
217 let retrieved_network = repositories
218 .network
219 .get_by_id("test".to_string())
220 .await
221 .unwrap();
222 let retrieved_api_key = repositories
223 .api_key
224 .get_by_id("test-api-key")
225 .await
226 .unwrap();
227
228 assert_eq!(retrieved_relayer.id, "test-relayer");
229 assert_eq!(retrieved_signer.id, "test");
230 assert_eq!(retrieved_network.id, "test");
231 assert_eq!(retrieved_api_key.unwrap().id, "test-api-key");
232 }
233
234 #[tokio::test]
235 async fn test_initialize_app_state_repository_error() {
236 let mut config = create_test_server_config(RepositoryStorageType::Redis);
237 config.redis_url = "redis://invalid_url".to_string();
238
239 let result = initialize_app_state(Arc::new(config)).await;
240
241 assert!(result.is_err());
243 let error = result.unwrap_err();
244 assert!(error.to_string().contains("Redis") || error.to_string().contains("connection"));
245 }
246
247 #[tokio::test]
248 async fn test_initialize_repositories_redis_without_encryption_key() {
249 let mut config = create_test_server_config(RepositoryStorageType::Redis);
250 config.storage_encryption_key = None;
252
253 let result = initialize_repositories(&config, None).await;
256
257 assert!(result.is_err());
258 let error = match result {
259 Err(e) => e,
260 Ok(_) => panic!("Expected error for missing encryption key"),
261 };
262 assert!(
263 error.to_string().contains("encryption key"),
264 "Expected error about encryption key, got: {error}"
265 );
266 }
267
268 #[tokio::test]
269 async fn test_initialize_repositories_redis_without_pool() {
270 let mut config = create_test_server_config(RepositoryStorageType::Redis);
271 config.storage_encryption_key = Some(SecretString::new("test-encryption-key-32-bytes!!!"));
273
274 let result = initialize_repositories(&config, None).await;
276
277 assert!(result.is_err());
278 let error = match result {
279 Err(e) => e,
280 Ok(_) => panic!("Expected error for missing pool"),
281 };
282 assert!(
283 error
284 .to_string()
285 .contains("Redis connections required for Redis storage type"),
286 "Expected error about Redis pool being required, got: {error}"
287 );
288 }
289
290 #[tokio::test]
291 async fn test_initialize_repositories_in_memory_ignores_pool() {
292 let config = create_test_server_config(RepositoryStorageType::InMemory);
295
296 let result = initialize_repositories(&config, None).await;
298 assert!(result.is_ok());
299
300 let repositories = result.unwrap();
302 let relayer = create_mock_relayer("test-relayer".to_string(), false);
303 repositories.relayer.create(relayer).await.unwrap();
304 let retrieved = repositories
305 .relayer
306 .get_by_id("test-relayer".to_string())
307 .await
308 .unwrap();
309 assert_eq!(retrieved.id, "test-relayer");
310 }
311
312 #[tokio::test]
313 async fn test_repository_collection_all_eight_repositories() {
314 let config = create_test_server_config(RepositoryStorageType::InMemory);
316 let repositories = initialize_repositories(&config, None).await.unwrap();
317
318 let repo_refs = [
321 Arc::strong_count(&repositories.relayer),
322 Arc::strong_count(&repositories.transaction),
323 Arc::strong_count(&repositories.signer),
324 Arc::strong_count(&repositories.notification),
325 Arc::strong_count(&repositories.network),
326 Arc::strong_count(&repositories.transaction_counter),
327 Arc::strong_count(&repositories.plugin),
328 Arc::strong_count(&repositories.api_key),
329 ];
330
331 assert_eq!(repo_refs.len(), 8, "Expected exactly 8 repositories");
332 for (i, count) in repo_refs.iter().enumerate() {
333 assert!(*count >= 1, "Repository {i} has invalid Arc count: {count}");
334 }
335 }
336
337 #[tokio::test]
338 async fn test_repository_delete_operations() {
339 let config = create_test_server_config(RepositoryStorageType::InMemory);
340 let repositories = initialize_repositories(&config, None).await.unwrap();
341
342 let relayer = create_mock_relayer("delete-test".to_string(), false);
344 repositories.relayer.create(relayer).await.unwrap();
345
346 let exists = repositories
348 .relayer
349 .get_by_id("delete-test".to_string())
350 .await;
351 assert!(exists.is_ok());
352
353 let delete_result = repositories
355 .relayer
356 .delete_by_id("delete-test".to_string())
357 .await;
358 assert!(delete_result.is_ok());
359
360 let after_delete = repositories
362 .relayer
363 .get_by_id("delete-test".to_string())
364 .await;
365 assert!(after_delete.is_err() || after_delete.unwrap().id != "delete-test");
366 }
367
368 #[tokio::test]
369 async fn test_repository_update_operations() {
370 let config = create_test_server_config(RepositoryStorageType::InMemory);
371 let repositories = initialize_repositories(&config, None).await.unwrap();
372
373 let relayer = create_mock_relayer("update-test".to_string(), false);
375 repositories.relayer.create(relayer.clone()).await.unwrap();
376
377 let mut updated_relayer = relayer.clone();
379 updated_relayer.system_disabled = true;
380
381 let update_result = repositories
382 .relayer
383 .update("update-test".to_string(), updated_relayer)
384 .await;
385 assert!(update_result.is_ok());
386
387 let retrieved = repositories
389 .relayer
390 .get_by_id("update-test".to_string())
391 .await
392 .unwrap();
393 assert!(retrieved.system_disabled);
394 }
395
396 #[tokio::test]
397 async fn test_repository_list_operations() {
398 let config = create_test_server_config(RepositoryStorageType::InMemory);
399 let repositories = initialize_repositories(&config, None).await.unwrap();
400
401 for i in 0..5 {
403 let relayer = create_mock_relayer(format!("list-test-{i}"), false);
404 repositories.relayer.create(relayer).await.unwrap();
405 }
406
407 let all_relayers = repositories.relayer.list_all().await.unwrap();
409 assert_eq!(all_relayers.len(), 5);
410
411 for i in 0..5 {
413 let found = all_relayers
414 .iter()
415 .any(|r| r.id == format!("list-test-{i}"));
416 assert!(found, "Expected to find relayer list-test-{i}");
417 }
418 }
419
420 #[tokio::test]
421 async fn test_repository_collection_struct_fields() {
422 let config = create_test_server_config(RepositoryStorageType::InMemory);
424 let repos = initialize_repositories(&config, None).await.unwrap();
425
426 let _ = &repos.relayer;
428 let _ = &repos.transaction;
429 let _ = &repos.signer;
430 let _ = &repos.notification;
431 let _ = &repos.network;
432 let _ = &repos.transaction_counter;
433 let _ = &repos.plugin;
434 let _ = &repos.api_key;
435
436 assert!(true);
438 }
439}