openzeppelin_relayer/services/plugins/
relayer_api.rs

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