1use crate::domain::{
9 get_network_relayer, get_relayer_by_id, get_transaction_by_id, Relayer, SignTransactionRequest,
10};
11use crate::jobs::JobProducerTrait;
12use crate::models::{
13 AppState, NetworkRepoModel, NetworkTransactionRequest, NotificationRepoModel, RelayerRepoModel,
14 SignerRepoModel, ThinDataAppState, TransactionRepoModel, TransactionResponse,
15};
16use crate::observability::request_id::set_request_id;
17use crate::repositories::{
18 ApiKeyRepositoryTrait, NetworkRepository, PluginRepositoryTrait, RelayerRepository, Repository,
19 TransactionCounterTrait, TransactionRepository,
20};
21use crate::services::plugins::PluginError;
22use actix_web::web;
23use async_trait::async_trait;
24use serde::{Deserialize, Serialize};
25use strum::Display;
26use tracing::instrument;
27
28#[cfg(test)]
29use mockall::automock;
30
31#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Display)]
32pub enum PluginMethod {
33 #[serde(rename = "sendTransaction")]
34 SendTransaction,
35 #[serde(rename = "getTransaction")]
36 GetTransaction,
37 #[serde(rename = "getRelayerStatus")]
38 GetRelayerStatus,
39 #[serde(rename = "signTransaction")]
40 SignTransaction,
41 #[serde(rename = "getRelayer")]
42 GetRelayer,
43}
44
45#[derive(Deserialize, Serialize, Clone, Debug)]
46#[serde(rename_all = "camelCase")]
47pub struct Request {
48 pub request_id: String,
49 pub relayer_id: String,
50 pub method: PluginMethod,
51 pub payload: serde_json::Value,
52 pub http_request_id: Option<String>,
53}
54
55#[derive(Deserialize, Serialize, Clone, Debug)]
56#[serde(rename_all = "camelCase")]
57pub struct GetTransactionRequest {
58 pub transaction_id: String,
59}
60
61#[derive(Serialize, Deserialize, Clone, Debug, Default)]
62#[serde(rename_all = "camelCase")]
63pub struct Response {
64 pub request_id: String,
65 pub result: Option<serde_json::Value>,
66 pub error: Option<String>,
67}
68
69#[async_trait]
70#[cfg_attr(test, automock)]
71pub trait RelayerApiTrait<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>: Send + Sync
72where
73 J: JobProducerTrait + 'static,
74 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
75 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
76 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
77 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
78 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
79 TCR: TransactionCounterTrait + Send + Sync + 'static,
80 PR: PluginRepositoryTrait + Send + Sync + 'static,
81 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
82{
83 async fn handle_request(
84 &self,
85 request: Request,
86 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
87 ) -> Response;
88
89 async fn process_request(
90 &self,
91 request: Request,
92 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
93 ) -> Result<Response, PluginError>;
94
95 async fn handle_send_transaction(
96 &self,
97 request: Request,
98 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
99 ) -> Result<Response, PluginError>;
100
101 async fn handle_get_transaction(
102 &self,
103 request: Request,
104 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
105 ) -> Result<Response, PluginError>;
106
107 async fn handle_get_relayer_status(
108 &self,
109 request: Request,
110 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
111 ) -> Result<Response, PluginError>;
112
113 async fn handle_sign_transaction(
114 &self,
115 request: Request,
116 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
117 ) -> Result<Response, PluginError>;
118 async fn handle_get_relayer_info(
119 &self,
120 request: Request,
121 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
122 ) -> Result<Response, PluginError>;
123}
124
125#[derive(Default)]
126pub struct RelayerApi;
127
128impl RelayerApi {
129 #[instrument(name = "Plugin::handle_request", skip_all, fields(method = %request.method, relayer_id = %request.relayer_id, plugin_req_id = %request.request_id))]
130 pub async fn handle_request<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
131 &self,
132 request: Request,
133 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
134 ) -> Response
135 where
136 J: JobProducerTrait + 'static,
137 TR: TransactionRepository
138 + Repository<TransactionRepoModel, String>
139 + Send
140 + Sync
141 + 'static,
142 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
143 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
144 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
145 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
146 TCR: TransactionCounterTrait + Send + Sync + 'static,
147 PR: PluginRepositoryTrait + Send + Sync + 'static,
148 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
149 {
150 if let Some(http_rid) = request.http_request_id.clone() {
152 set_request_id(http_rid);
153 }
154
155 match self.process_request(request.clone(), state).await {
156 Ok(response) => response,
157 Err(e) => Response {
158 request_id: request.request_id,
159 result: None,
160 error: Some(e.to_string()),
161 },
162 }
163 }
164
165 async fn process_request<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
166 &self,
167 request: Request,
168 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
169 ) -> Result<Response, PluginError>
170 where
171 J: JobProducerTrait + 'static,
172 TR: TransactionRepository
173 + Repository<TransactionRepoModel, String>
174 + Send
175 + Sync
176 + 'static,
177 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
178 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
179 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
180 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
181 TCR: TransactionCounterTrait + Send + Sync + 'static,
182 PR: PluginRepositoryTrait + Send + Sync + 'static,
183 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
184 {
185 match request.method {
186 PluginMethod::SendTransaction => self.handle_send_transaction(request, state).await,
187 PluginMethod::GetTransaction => self.handle_get_transaction(request, state).await,
188 PluginMethod::GetRelayerStatus => self.handle_get_relayer_status(request, state).await,
189 PluginMethod::SignTransaction => self.handle_sign_transaction(request, state).await,
190 PluginMethod::GetRelayer => self.handle_get_relayer_info(request, state).await,
191 }
192 }
193
194 async fn handle_send_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
195 &self,
196 request: Request,
197 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
198 ) -> Result<Response, PluginError>
199 where
200 J: JobProducerTrait + 'static,
201 TR: TransactionRepository
202 + Repository<TransactionRepoModel, String>
203 + Send
204 + Sync
205 + 'static,
206 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
207 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
208 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
209 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
210 TCR: TransactionCounterTrait + Send + Sync + 'static,
211 PR: PluginRepositoryTrait + Send + Sync + 'static,
212 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
213 {
214 let relayer_repo_model = get_relayer_by_id(request.relayer_id.clone(), state)
215 .await
216 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
217
218 relayer_repo_model
219 .validate_active_state()
220 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
221
222 let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
223 .await
224 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
225
226 let tx_request = NetworkTransactionRequest::from_json(
227 &relayer_repo_model.network_type,
228 request.payload.clone(),
229 )
230 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
231
232 tx_request
233 .validate(&relayer_repo_model)
234 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
235
236 let transaction = network_relayer
237 .process_transaction_request(tx_request)
238 .await
239 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
240
241 let transaction_response: TransactionResponse = transaction.into();
242 let result = serde_json::to_value(transaction_response)
243 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
244
245 Ok(Response {
246 request_id: request.request_id,
247 result: Some(result),
248 error: None,
249 })
250 }
251
252 async fn handle_get_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
253 &self,
254 request: Request,
255 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
256 ) -> Result<Response, PluginError>
257 where
258 J: JobProducerTrait + 'static,
259 TR: TransactionRepository
260 + Repository<TransactionRepoModel, String>
261 + Send
262 + Sync
263 + 'static,
264 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
265 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
266 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
267 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
268 TCR: TransactionCounterTrait + Send + Sync + 'static,
269 PR: PluginRepositoryTrait + Send + Sync + 'static,
270 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
271 {
272 get_relayer_by_id(request.relayer_id.clone(), state)
274 .await
275 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
276
277 let get_transaction_request: GetTransactionRequest =
278 serde_json::from_value(request.payload)
279 .map_err(|e| PluginError::InvalidPayload(e.to_string()))?;
280
281 let transaction = get_transaction_by_id(get_transaction_request.transaction_id, state)
282 .await
283 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
284
285 let transaction_response: TransactionResponse = transaction.into();
286
287 let result = serde_json::to_value(transaction_response)
288 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
289
290 Ok(Response {
291 request_id: request.request_id,
292 result: Some(result),
293 error: None,
294 })
295 }
296
297 async fn handle_get_relayer_status<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
298 &self,
299 request: Request,
300 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
301 ) -> Result<Response, PluginError>
302 where
303 J: JobProducerTrait + 'static,
304 TR: TransactionRepository
305 + Repository<TransactionRepoModel, String>
306 + Send
307 + Sync
308 + 'static,
309 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
310 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
311 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
312 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
313 TCR: TransactionCounterTrait + Send + Sync + 'static,
314 PR: PluginRepositoryTrait + Send + Sync + 'static,
315 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
316 {
317 let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
318 .await
319 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
320
321 let status = network_relayer
322 .get_status()
323 .await
324 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
325
326 let result =
327 serde_json::to_value(status).map_err(|e| PluginError::RelayerError(e.to_string()))?;
328
329 Ok(Response {
330 request_id: request.request_id,
331 result: Some(result),
332 error: None,
333 })
334 }
335
336 async fn handle_sign_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
337 &self,
338 request: Request,
339 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
340 ) -> Result<Response, PluginError>
341 where
342 J: JobProducerTrait + 'static,
343 TR: TransactionRepository
344 + Repository<TransactionRepoModel, String>
345 + Send
346 + Sync
347 + 'static,
348 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
349 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
350 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
351 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
352 TCR: TransactionCounterTrait + Send + Sync + 'static,
353 PR: PluginRepositoryTrait + Send + Sync + 'static,
354 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
355 {
356 let sign_request: SignTransactionRequest = serde_json::from_value(request.payload)
357 .map_err(|e| PluginError::InvalidPayload(e.to_string()))?;
358
359 let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
360 .await
361 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
362
363 let response = network_relayer
364 .sign_transaction(&sign_request)
365 .await
366 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
367
368 let result =
369 serde_json::to_value(response).map_err(|e| PluginError::RelayerError(e.to_string()))?;
370
371 Ok(Response {
372 request_id: request.request_id,
373 result: Some(result),
374 error: None,
375 })
376 }
377
378 async fn handle_get_relayer_info<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
379 &self,
380 request: Request,
381 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
382 ) -> Result<Response, PluginError>
383 where
384 J: JobProducerTrait + 'static,
385 TR: TransactionRepository
386 + Repository<TransactionRepoModel, String>
387 + Send
388 + Sync
389 + 'static,
390 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
391 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
392 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
393 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
394 TCR: TransactionCounterTrait + Send + Sync + 'static,
395 PR: PluginRepositoryTrait + Send + Sync + 'static,
396 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
397 {
398 let relayer = get_relayer_by_id(request.relayer_id.clone(), state)
399 .await
400 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
401 let relayer_response: crate::models::RelayerResponse = relayer.into();
402 let result = serde_json::to_value(relayer_response)
403 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
404 Ok(Response {
405 request_id: request.request_id,
406 result: Some(result),
407 error: None,
408 })
409 }
410}
411
412#[async_trait]
413impl<J, RR, TR, NR, NFR, SR, TCR, PR, AKR> RelayerApiTrait<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>
414 for RelayerApi
415where
416 J: JobProducerTrait + 'static,
417 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
418 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
419 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
420 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
421 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
422 TCR: TransactionCounterTrait + Send + Sync + 'static,
423 PR: PluginRepositoryTrait + Send + Sync + 'static,
424 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
425{
426 async fn handle_request(
427 &self,
428 request: Request,
429 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
430 ) -> Response {
431 self.handle_request(request, state).await
432 }
433
434 async fn process_request(
435 &self,
436 request: Request,
437 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
438 ) -> Result<Response, PluginError> {
439 self.process_request(request, state).await
440 }
441
442 async fn handle_send_transaction(
443 &self,
444 request: Request,
445 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
446 ) -> Result<Response, PluginError> {
447 self.handle_send_transaction(request, state).await
448 }
449
450 async fn handle_get_transaction(
451 &self,
452 request: Request,
453 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
454 ) -> Result<Response, PluginError> {
455 self.handle_get_transaction(request, state).await
456 }
457
458 async fn handle_get_relayer_status(
459 &self,
460 request: Request,
461 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
462 ) -> Result<Response, PluginError> {
463 self.handle_get_relayer_status(request, state).await
464 }
465
466 async fn handle_sign_transaction(
467 &self,
468 request: Request,
469 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
470 ) -> Result<Response, PluginError> {
471 self.handle_sign_transaction(request, state).await
472 }
473
474 async fn handle_get_relayer_info(
475 &self,
476 request: Request,
477 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
478 ) -> Result<Response, PluginError> {
479 self.handle_get_relayer_info(request, state).await
480 }
481}
482
483#[cfg(test)]
484mod tests {
485 use std::env;
486
487 use crate::utils::mocks::mockutils::{
488 create_mock_app_state, create_mock_evm_transaction_request, create_mock_network,
489 create_mock_relayer, create_mock_signer, create_mock_transaction,
490 };
491
492 use super::*;
493
494 fn setup_test_env() {
495 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D"); env::set_var("REDIS_URL", "redis://localhost:6379");
497 env::set_var("RPC_TIMEOUT_MS", "5000");
498 }
499
500 #[tokio::test]
501 async fn test_handle_request() {
502 setup_test_env();
503 let state = create_mock_app_state(
504 None,
505 Some(vec![create_mock_relayer("test".to_string(), false)]),
506 Some(vec![create_mock_signer()]),
507 Some(vec![create_mock_network()]),
508 None,
509 None,
510 )
511 .await;
512
513 let request = Request {
514 request_id: "test".to_string(),
515 relayer_id: "test".to_string(),
516 method: PluginMethod::SendTransaction,
517 payload: serde_json::json!(create_mock_evm_transaction_request()),
518 http_request_id: None,
519 };
520
521 let relayer_api = RelayerApi;
522 let response = relayer_api
523 .handle_request(request.clone(), &web::ThinData(state))
524 .await;
525
526 assert!(response.error.is_none());
527 assert!(response.result.is_some());
528 }
529
530 #[tokio::test]
531 async fn test_handle_request_error_paused_relayer() {
532 setup_test_env();
533 let paused = true;
534 let state = create_mock_app_state(
535 None,
536 Some(vec![create_mock_relayer("test".to_string(), paused)]),
537 Some(vec![create_mock_signer()]),
538 Some(vec![create_mock_network()]),
539 None,
540 None,
541 )
542 .await;
543
544 let request = Request {
545 request_id: "test".to_string(),
546 relayer_id: "test".to_string(),
547 method: PluginMethod::SendTransaction,
548 payload: serde_json::json!(create_mock_evm_transaction_request()),
549 http_request_id: None,
550 };
551
552 let relayer_api = RelayerApi;
553 let response = relayer_api
554 .handle_request(request.clone(), &web::ThinData(state))
555 .await;
556
557 assert!(response.error.is_some());
558 assert!(response.result.is_none());
559 assert_eq!(response.error.unwrap(), "Relayer error: Relayer is paused");
560 }
561
562 #[tokio::test]
563 async fn test_handle_request_using_trait() {
564 setup_test_env();
565 let state = create_mock_app_state(
566 None,
567 Some(vec![create_mock_relayer("test".to_string(), false)]),
568 Some(vec![create_mock_signer()]),
569 Some(vec![create_mock_network()]),
570 None,
571 None,
572 )
573 .await;
574
575 let request = Request {
576 request_id: "test".to_string(),
577 relayer_id: "test".to_string(),
578 method: PluginMethod::SendTransaction,
579 payload: serde_json::json!(create_mock_evm_transaction_request()),
580 http_request_id: None,
581 };
582
583 let relayer_api = RelayerApi;
584
585 let state = web::ThinData(state);
586
587 let response = RelayerApiTrait::handle_request(&relayer_api, request.clone(), &state).await;
588
589 assert!(response.error.is_none());
590 assert!(response.result.is_some());
591
592 let response =
593 RelayerApiTrait::process_request(&relayer_api, request.clone(), &state).await;
594
595 assert!(response.is_ok());
596
597 let response =
598 RelayerApiTrait::handle_send_transaction(&relayer_api, request.clone(), &state).await;
599
600 assert!(response.is_ok());
601 }
602
603 #[tokio::test]
604 async fn test_handle_get_transaction() {
605 setup_test_env();
606 let state = create_mock_app_state(
607 None,
608 Some(vec![create_mock_relayer("test".to_string(), false)]),
609 Some(vec![create_mock_signer()]),
610 Some(vec![create_mock_network()]),
611 None,
612 Some(vec![create_mock_transaction()]),
613 )
614 .await;
615
616 let request = Request {
617 request_id: "test".to_string(),
618 relayer_id: "test".to_string(),
619 method: PluginMethod::GetTransaction,
620 payload: serde_json::json!(GetTransactionRequest {
621 transaction_id: "test".to_string(),
622 }),
623 http_request_id: None,
624 };
625
626 let relayer_api = RelayerApi;
627 let response = relayer_api
628 .handle_request(request.clone(), &web::ThinData(state))
629 .await;
630
631 assert!(response.error.is_none());
632 assert!(response.result.is_some());
633 }
634
635 #[tokio::test]
636 async fn test_handle_get_transaction_error_relayer_not_found() {
637 setup_test_env();
638 let state = create_mock_app_state(
639 None,
640 None,
641 Some(vec![create_mock_signer()]),
642 Some(vec![create_mock_network()]),
643 None,
644 Some(vec![create_mock_transaction()]),
645 )
646 .await;
647
648 let request = Request {
649 request_id: "test".to_string(),
650 relayer_id: "test".to_string(),
651 method: PluginMethod::GetTransaction,
652 payload: serde_json::json!(GetTransactionRequest {
653 transaction_id: "test".to_string(),
654 }),
655 http_request_id: None,
656 };
657
658 let relayer_api = RelayerApi;
659 let response = relayer_api
660 .handle_request(request.clone(), &web::ThinData(state))
661 .await;
662
663 assert!(response.error.is_some());
664 let error = response.error.unwrap();
665 assert!(error.contains("Relayer with ID test not found"));
666 }
667
668 #[tokio::test]
669 async fn test_handle_get_transaction_error_transaction_not_found() {
670 setup_test_env();
671 let state = create_mock_app_state(
672 None,
673 Some(vec![create_mock_relayer("test".to_string(), false)]),
674 Some(vec![create_mock_signer()]),
675 Some(vec![create_mock_network()]),
676 None,
677 None,
678 )
679 .await;
680
681 let request = Request {
682 request_id: "test".to_string(),
683 relayer_id: "test".to_string(),
684 method: PluginMethod::GetTransaction,
685 payload: serde_json::json!(GetTransactionRequest {
686 transaction_id: "test".to_string(),
687 }),
688 http_request_id: None,
689 };
690
691 let relayer_api = RelayerApi;
692 let response = relayer_api
693 .handle_request(request.clone(), &web::ThinData(state))
694 .await;
695
696 assert!(response.error.is_some());
697 let error = response.error.unwrap();
698 assert!(error.contains("Transaction with ID test not found"));
699 }
700
701 #[tokio::test]
702 async fn test_handle_get_relayer_status_relayer_not_found() {
703 setup_test_env();
704 let state = create_mock_app_state(
705 None,
706 None,
707 Some(vec![create_mock_signer()]),
708 Some(vec![create_mock_network()]),
709 None,
710 None,
711 )
712 .await;
713
714 let request = Request {
715 request_id: "test".to_string(),
716 relayer_id: "test".to_string(),
717 method: PluginMethod::GetRelayerStatus,
718 payload: serde_json::json!({}),
719 http_request_id: None,
720 };
721
722 let relayer_api = RelayerApi;
723 let response = relayer_api
724 .handle_request(request.clone(), &web::ThinData(state))
725 .await;
726
727 assert!(response.error.is_some());
728 let error = response.error.unwrap();
729 assert!(error.contains("Relayer with ID test not found"));
730 }
731
732 #[tokio::test]
733 async fn test_handle_sign_transaction_evm_not_supported() {
734 setup_test_env();
735 let state = create_mock_app_state(
736 None,
737 Some(vec![create_mock_relayer("test".to_string(), false)]),
738 Some(vec![create_mock_signer()]),
739 Some(vec![create_mock_network()]),
740 None,
741 None,
742 )
743 .await;
744
745 let request = Request {
746 request_id: "test".to_string(),
747 relayer_id: "test".to_string(),
748 method: PluginMethod::SignTransaction,
749 payload: serde_json::json!({
750 "unsigned_xdr": "test_xdr"
751 }),
752 http_request_id: None,
753 };
754
755 let relayer_api = RelayerApi;
756 let response = relayer_api
757 .handle_request(request.clone(), &web::ThinData(state))
758 .await;
759
760 assert!(response.error.is_some());
761 let error = response.error.unwrap();
762 assert!(error.contains("sign_transaction not supported for EVM"));
763 }
764
765 #[tokio::test]
766 async fn test_handle_sign_transaction_invalid_payload() {
767 setup_test_env();
768 let state = create_mock_app_state(
769 None,
770 Some(vec![create_mock_relayer("test".to_string(), false)]),
771 Some(vec![create_mock_signer()]),
772 Some(vec![create_mock_network()]),
773 None,
774 None,
775 )
776 .await;
777
778 let request = Request {
779 request_id: "test".to_string(),
780 relayer_id: "test".to_string(),
781 method: PluginMethod::SignTransaction,
782 payload: serde_json::json!({"invalid": "payload"}),
783 http_request_id: None,
784 };
785
786 let relayer_api = RelayerApi;
787 let response = relayer_api
788 .handle_request(request.clone(), &web::ThinData(state))
789 .await;
790
791 assert!(response.error.is_some());
792 let error = response.error.unwrap();
793 assert!(error.contains("Invalid payload"));
794 }
795
796 #[tokio::test]
797 async fn test_handle_sign_transaction_relayer_not_found() {
798 setup_test_env();
799 let state = create_mock_app_state(
800 None,
801 None,
802 Some(vec![create_mock_signer()]),
803 Some(vec![create_mock_network()]),
804 None,
805 None,
806 )
807 .await;
808
809 let request = Request {
810 request_id: "test".to_string(),
811 relayer_id: "test".to_string(),
812 method: PluginMethod::SignTransaction,
813 payload: serde_json::json!({
814 "unsigned_xdr": "test_xdr"
815 }),
816 http_request_id: None,
817 };
818
819 let relayer_api = RelayerApi;
820 let response = relayer_api
821 .handle_request(request.clone(), &web::ThinData(state))
822 .await;
823
824 assert!(response.error.is_some());
825 let error = response.error.unwrap();
826 assert!(error.contains("Relayer with ID test not found"));
827 }
828
829 #[tokio::test]
830 async fn test_handle_get_relayer_info_success() {
831 setup_test_env();
832 let state = create_mock_app_state(
833 None,
834 Some(vec![create_mock_relayer("test".to_string(), false)]),
835 Some(vec![create_mock_signer()]),
836 Some(vec![create_mock_network()]),
837 None,
838 None,
839 )
840 .await;
841
842 let request = Request {
843 request_id: "test".to_string(),
844 relayer_id: "test".to_string(),
845 method: PluginMethod::GetRelayer,
846 payload: serde_json::json!({}),
847 http_request_id: None,
848 };
849
850 let relayer_api = RelayerApi;
851 let response = relayer_api
852 .handle_request(request.clone(), &web::ThinData(state))
853 .await;
854
855 assert!(response.error.is_none());
856 assert!(response.result.is_some());
857
858 let result = response.result.unwrap();
859 assert!(result.get("id").is_some());
860 assert!(result.get("name").is_some());
861 assert!(result.get("network").is_some());
862 assert!(result.get("address").is_some());
863 }
864
865 #[tokio::test]
866 async fn test_handle_get_relayer_info_relayer_not_found() {
867 setup_test_env();
868 let state = create_mock_app_state(
869 None,
870 None,
871 Some(vec![create_mock_signer()]),
872 Some(vec![create_mock_network()]),
873 None,
874 None,
875 )
876 .await;
877
878 let request = Request {
879 request_id: "test".to_string(),
880 relayer_id: "test".to_string(),
881 method: PluginMethod::GetRelayer,
882 payload: serde_json::json!({}),
883 http_request_id: None,
884 };
885
886 let relayer_api = RelayerApi;
887 let response = relayer_api
888 .handle_request(request.clone(), &web::ThinData(state))
889 .await;
890
891 assert!(response.error.is_some());
892 let error = response.error.unwrap();
893 assert!(error.contains("Relayer with ID test not found"));
894 }
895}