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 convert_to_internal_rpc_request, AppState, JsonRpcRequest, NetworkRepoModel, NetworkRpcRequest,
14 NetworkTransactionRequest, NotificationRepoModel, RelayerRepoModel, SignerRepoModel,
15 ThinDataAppState, TransactionRepoModel, TransactionResponse,
16};
17use crate::observability::request_id::set_request_id;
18use crate::repositories::{
19 ApiKeyRepositoryTrait, NetworkRepository, PluginRepositoryTrait, RelayerRepository, Repository,
20 TransactionCounterTrait, TransactionRepository,
21};
22use crate::services::plugins::PluginError;
23use actix_web::web;
24use async_trait::async_trait;
25use serde::{Deserialize, Serialize};
26use strum::Display;
27use tracing::instrument;
28
29#[cfg(test)]
30use mockall::automock;
31
32#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Display)]
33pub enum PluginMethod {
34 #[serde(rename = "sendTransaction")]
35 SendTransaction,
36 #[serde(rename = "getTransaction")]
37 GetTransaction,
38 #[serde(rename = "getRelayerStatus")]
39 GetRelayerStatus,
40 #[serde(rename = "signTransaction")]
41 SignTransaction,
42 #[serde(rename = "getRelayer")]
43 GetRelayer,
44 #[serde(rename = "rpc")]
45 Rpc,
46}
47
48#[derive(Deserialize, Serialize, Clone, Debug)]
49#[serde(rename_all = "camelCase")]
50pub struct Request {
51 pub request_id: String,
52 pub relayer_id: String,
53 pub method: PluginMethod,
54 pub payload: serde_json::Value,
55 pub http_request_id: Option<String>,
56}
57
58#[derive(Deserialize, Serialize, Clone, Debug)]
59#[serde(rename_all = "camelCase")]
60pub struct GetTransactionRequest {
61 pub transaction_id: String,
62}
63
64#[derive(Serialize, Deserialize, Clone, Debug, Default)]
65#[serde(rename_all = "camelCase")]
66pub struct Response {
67 pub request_id: String,
68 pub result: Option<serde_json::Value>,
69 pub error: Option<String>,
70}
71
72#[async_trait]
73#[cfg_attr(test, automock)]
74pub trait RelayerApiTrait<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>: Send + Sync
75where
76 J: JobProducerTrait + 'static,
77 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
78 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
79 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
80 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
81 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
82 TCR: TransactionCounterTrait + Send + Sync + 'static,
83 PR: PluginRepositoryTrait + Send + Sync + 'static,
84 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
85{
86 async fn handle_request(
87 &self,
88 request: Request,
89 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
90 ) -> Response;
91
92 async fn process_request(
93 &self,
94 request: Request,
95 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
96 ) -> Result<Response, PluginError>;
97
98 async fn handle_send_transaction(
99 &self,
100 request: Request,
101 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
102 ) -> Result<Response, PluginError>;
103
104 async fn handle_get_transaction(
105 &self,
106 request: Request,
107 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
108 ) -> Result<Response, PluginError>;
109
110 async fn handle_get_relayer_status(
111 &self,
112 request: Request,
113 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
114 ) -> Result<Response, PluginError>;
115
116 async fn handle_sign_transaction(
117 &self,
118 request: Request,
119 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
120 ) -> Result<Response, PluginError>;
121 async fn handle_get_relayer_info(
122 &self,
123 request: Request,
124 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
125 ) -> Result<Response, PluginError>;
126 async fn handle_rpc_request(
127 &self,
128 request: Request,
129 state: &web::ThinData<AppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>>,
130 ) -> Result<Response, PluginError>;
131}
132
133#[derive(Default)]
134pub struct RelayerApi;
135
136impl RelayerApi {
137 #[instrument(name = "Plugin::handle_request", skip_all, fields(method = %request.method, relayer_id = %request.relayer_id, plugin_req_id = %request.request_id))]
138 pub async fn handle_request<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
139 &self,
140 request: Request,
141 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
142 ) -> Response
143 where
144 J: JobProducerTrait + 'static,
145 TR: TransactionRepository
146 + Repository<TransactionRepoModel, String>
147 + Send
148 + Sync
149 + 'static,
150 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
151 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
152 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
153 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
154 TCR: TransactionCounterTrait + Send + Sync + 'static,
155 PR: PluginRepositoryTrait + Send + Sync + 'static,
156 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
157 {
158 if let Some(http_rid) = request.http_request_id.clone() {
160 set_request_id(http_rid);
161 }
162
163 match self.process_request(request.clone(), state).await {
164 Ok(response) => response,
165 Err(e) => Response {
166 request_id: request.request_id,
167 result: None,
168 error: Some(e.to_string()),
169 },
170 }
171 }
172
173 async fn process_request<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
174 &self,
175 request: Request,
176 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
177 ) -> Result<Response, PluginError>
178 where
179 J: JobProducerTrait + 'static,
180 TR: TransactionRepository
181 + Repository<TransactionRepoModel, String>
182 + Send
183 + Sync
184 + 'static,
185 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
186 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
187 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
188 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
189 TCR: TransactionCounterTrait + Send + Sync + 'static,
190 PR: PluginRepositoryTrait + Send + Sync + 'static,
191 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
192 {
193 match request.method {
194 PluginMethod::SendTransaction => self.handle_send_transaction(request, state).await,
195 PluginMethod::GetTransaction => self.handle_get_transaction(request, state).await,
196 PluginMethod::GetRelayerStatus => self.handle_get_relayer_status(request, state).await,
197 PluginMethod::SignTransaction => self.handle_sign_transaction(request, state).await,
198 PluginMethod::GetRelayer => self.handle_get_relayer_info(request, state).await,
199 PluginMethod::Rpc => self.handle_rpc_request(request, state).await,
200 }
201 }
202
203 async fn handle_send_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
204 &self,
205 request: Request,
206 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
207 ) -> Result<Response, PluginError>
208 where
209 J: JobProducerTrait + 'static,
210 TR: TransactionRepository
211 + Repository<TransactionRepoModel, String>
212 + Send
213 + Sync
214 + 'static,
215 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
216 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
217 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
218 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
219 TCR: TransactionCounterTrait + Send + Sync + 'static,
220 PR: PluginRepositoryTrait + Send + Sync + 'static,
221 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
222 {
223 let relayer_repo_model = get_relayer_by_id(request.relayer_id.clone(), state)
224 .await
225 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
226
227 relayer_repo_model
228 .validate_active_state()
229 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
230
231 let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
232 .await
233 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
234
235 let tx_request = NetworkTransactionRequest::from_json(
236 &relayer_repo_model.network_type,
237 request.payload.clone(),
238 )
239 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
240
241 tx_request
242 .validate(&relayer_repo_model)
243 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
244
245 let transaction = network_relayer
246 .process_transaction_request(tx_request)
247 .await
248 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
249
250 let transaction_response: TransactionResponse = transaction.into();
251 let result = serde_json::to_value(transaction_response)
252 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
253
254 Ok(Response {
255 request_id: request.request_id,
256 result: Some(result),
257 error: None,
258 })
259 }
260
261 async fn handle_get_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
262 &self,
263 request: Request,
264 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
265 ) -> Result<Response, PluginError>
266 where
267 J: JobProducerTrait + 'static,
268 TR: TransactionRepository
269 + Repository<TransactionRepoModel, String>
270 + Send
271 + Sync
272 + 'static,
273 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
274 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
275 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
276 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
277 TCR: TransactionCounterTrait + Send + Sync + 'static,
278 PR: PluginRepositoryTrait + Send + Sync + 'static,
279 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
280 {
281 get_relayer_by_id(request.relayer_id.clone(), state)
283 .await
284 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
285
286 let get_transaction_request: GetTransactionRequest =
287 serde_json::from_value(request.payload)
288 .map_err(|e| PluginError::InvalidPayload(e.to_string()))?;
289
290 let transaction = get_transaction_by_id(get_transaction_request.transaction_id, state)
291 .await
292 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
293
294 let transaction_response: TransactionResponse = transaction.into();
295
296 let result = serde_json::to_value(transaction_response)
297 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
298
299 Ok(Response {
300 request_id: request.request_id,
301 result: Some(result),
302 error: None,
303 })
304 }
305
306 async fn handle_get_relayer_status<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
307 &self,
308 request: Request,
309 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
310 ) -> Result<Response, PluginError>
311 where
312 J: JobProducerTrait + 'static,
313 TR: TransactionRepository
314 + Repository<TransactionRepoModel, String>
315 + Send
316 + Sync
317 + 'static,
318 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
319 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
320 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
321 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
322 TCR: TransactionCounterTrait + Send + Sync + 'static,
323 PR: PluginRepositoryTrait + Send + Sync + 'static,
324 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
325 {
326 let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
327 .await
328 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
329
330 let status = network_relayer
331 .get_status()
332 .await
333 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
334
335 let result =
336 serde_json::to_value(status).map_err(|e| PluginError::RelayerError(e.to_string()))?;
337
338 Ok(Response {
339 request_id: request.request_id,
340 result: Some(result),
341 error: None,
342 })
343 }
344
345 async fn handle_sign_transaction<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
346 &self,
347 request: Request,
348 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
349 ) -> Result<Response, PluginError>
350 where
351 J: JobProducerTrait + 'static,
352 TR: TransactionRepository
353 + Repository<TransactionRepoModel, String>
354 + Send
355 + Sync
356 + 'static,
357 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
358 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
359 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
360 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
361 TCR: TransactionCounterTrait + Send + Sync + 'static,
362 PR: PluginRepositoryTrait + Send + Sync + 'static,
363 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
364 {
365 let sign_request: SignTransactionRequest = serde_json::from_value(request.payload)
366 .map_err(|e| PluginError::InvalidPayload(e.to_string()))?;
367
368 let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
369 .await
370 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
371
372 let response = network_relayer
373 .sign_transaction(&sign_request)
374 .await
375 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
376
377 let result =
378 serde_json::to_value(response).map_err(|e| PluginError::RelayerError(e.to_string()))?;
379
380 Ok(Response {
381 request_id: request.request_id,
382 result: Some(result),
383 error: None,
384 })
385 }
386
387 async fn handle_get_relayer_info<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
388 &self,
389 request: Request,
390 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
391 ) -> Result<Response, PluginError>
392 where
393 J: JobProducerTrait + 'static,
394 TR: TransactionRepository
395 + Repository<TransactionRepoModel, String>
396 + Send
397 + Sync
398 + 'static,
399 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
400 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
401 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
402 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
403 TCR: TransactionCounterTrait + Send + Sync + 'static,
404 PR: PluginRepositoryTrait + Send + Sync + 'static,
405 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
406 {
407 let relayer = get_relayer_by_id(request.relayer_id.clone(), state)
408 .await
409 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
410 let relayer_response: crate::models::RelayerResponse = relayer.into();
411 let result = serde_json::to_value(relayer_response)
412 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
413 Ok(Response {
414 request_id: request.request_id,
415 result: Some(result),
416 error: None,
417 })
418 }
419
420 async fn handle_rpc_request<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>(
421 &self,
422 request: Request,
423 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
424 ) -> Result<Response, PluginError>
425 where
426 J: JobProducerTrait + 'static,
427 TR: TransactionRepository
428 + Repository<TransactionRepoModel, String>
429 + Send
430 + Sync
431 + 'static,
432 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
433 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
434 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
435 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
436 TCR: TransactionCounterTrait + Send + Sync + 'static,
437 PR: PluginRepositoryTrait + Send + Sync + 'static,
438 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
439 {
440 let relayer_repo_model = get_relayer_by_id(request.relayer_id.clone(), state)
441 .await
442 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
443
444 relayer_repo_model
445 .validate_active_state()
446 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
447
448 let network_relayer = get_network_relayer(request.relayer_id.clone(), state)
449 .await
450 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
451
452 let network_rpc_request: JsonRpcRequest<NetworkRpcRequest> =
454 convert_to_internal_rpc_request(request.payload, &relayer_repo_model.network_type)
455 .map_err(|e| PluginError::InvalidPayload(e.to_string()))?;
456
457 let result = network_relayer.rpc(network_rpc_request).await;
458
459 match result {
460 Ok(json_rpc_response) => {
461 let result_value = serde_json::to_value(json_rpc_response)
462 .map_err(|e| PluginError::RelayerError(e.to_string()))?;
463 Ok(Response {
464 request_id: request.request_id,
465 result: Some(result_value),
466 error: None,
467 })
468 }
469 Err(e) => Ok(Response {
470 request_id: request.request_id,
471 result: None,
472 error: Some(e.to_string()),
473 }),
474 }
475 }
476}
477
478#[async_trait]
479impl<J, RR, TR, NR, NFR, SR, TCR, PR, AKR> RelayerApiTrait<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>
480 for RelayerApi
481where
482 J: JobProducerTrait + 'static,
483 TR: TransactionRepository + Repository<TransactionRepoModel, String> + Send + Sync + 'static,
484 RR: RelayerRepository + Repository<RelayerRepoModel, String> + Send + Sync + 'static,
485 NR: NetworkRepository + Repository<NetworkRepoModel, String> + Send + Sync + 'static,
486 NFR: Repository<NotificationRepoModel, String> + Send + Sync + 'static,
487 SR: Repository<SignerRepoModel, String> + Send + Sync + 'static,
488 TCR: TransactionCounterTrait + Send + Sync + 'static,
489 PR: PluginRepositoryTrait + Send + Sync + 'static,
490 AKR: ApiKeyRepositoryTrait + Send + Sync + 'static,
491{
492 async fn handle_request(
493 &self,
494 request: Request,
495 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
496 ) -> Response {
497 self.handle_request(request, state).await
498 }
499
500 async fn process_request(
501 &self,
502 request: Request,
503 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
504 ) -> Result<Response, PluginError> {
505 self.process_request(request, state).await
506 }
507
508 async fn handle_send_transaction(
509 &self,
510 request: Request,
511 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
512 ) -> Result<Response, PluginError> {
513 self.handle_send_transaction(request, state).await
514 }
515
516 async fn handle_get_transaction(
517 &self,
518 request: Request,
519 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
520 ) -> Result<Response, PluginError> {
521 self.handle_get_transaction(request, state).await
522 }
523
524 async fn handle_get_relayer_status(
525 &self,
526 request: Request,
527 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
528 ) -> Result<Response, PluginError> {
529 self.handle_get_relayer_status(request, state).await
530 }
531
532 async fn handle_sign_transaction(
533 &self,
534 request: Request,
535 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
536 ) -> Result<Response, PluginError> {
537 self.handle_sign_transaction(request, state).await
538 }
539
540 async fn handle_get_relayer_info(
541 &self,
542 request: Request,
543 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
544 ) -> Result<Response, PluginError> {
545 self.handle_get_relayer_info(request, state).await
546 }
547
548 async fn handle_rpc_request(
549 &self,
550 request: Request,
551 state: &ThinDataAppState<J, RR, TR, NR, NFR, SR, TCR, PR, AKR>,
552 ) -> Result<Response, PluginError> {
553 self.handle_rpc_request(request, state).await
554 }
555}
556
557#[cfg(test)]
558mod tests {
559 use std::env;
560
561 use crate::utils::mocks::mockutils::{
562 create_mock_app_state, create_mock_evm_transaction_request, create_mock_network,
563 create_mock_relayer, create_mock_signer, create_mock_transaction,
564 };
565
566 use super::*;
567
568 fn setup_test_env() {
569 env::set_var("API_KEY", "7EF1CB7C-5003-4696-B384-C72AF8C3E15D"); env::set_var("REDIS_URL", "redis://localhost:6379");
571 env::set_var("RPC_TIMEOUT_MS", "5000");
572 }
573
574 #[tokio::test]
575 async fn test_handle_request() {
576 setup_test_env();
577 let state = create_mock_app_state(
578 None,
579 Some(vec![create_mock_relayer("test".to_string(), false)]),
580 Some(vec![create_mock_signer()]),
581 Some(vec![create_mock_network()]),
582 None,
583 None,
584 )
585 .await;
586
587 let request = Request {
588 request_id: "test".to_string(),
589 relayer_id: "test".to_string(),
590 method: PluginMethod::SendTransaction,
591 payload: serde_json::json!(create_mock_evm_transaction_request()),
592 http_request_id: None,
593 };
594
595 let relayer_api = RelayerApi;
596 let response = relayer_api
597 .handle_request(request.clone(), &web::ThinData(state))
598 .await;
599
600 assert!(response.error.is_none());
601 assert!(response.result.is_some());
602 }
603
604 #[tokio::test]
605 async fn test_handle_request_error_paused_relayer() {
606 setup_test_env();
607 let paused = true;
608 let state = create_mock_app_state(
609 None,
610 Some(vec![create_mock_relayer("test".to_string(), paused)]),
611 Some(vec![create_mock_signer()]),
612 Some(vec![create_mock_network()]),
613 None,
614 None,
615 )
616 .await;
617
618 let request = Request {
619 request_id: "test".to_string(),
620 relayer_id: "test".to_string(),
621 method: PluginMethod::SendTransaction,
622 payload: serde_json::json!(create_mock_evm_transaction_request()),
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_some());
632 assert!(response.result.is_none());
633 assert_eq!(response.error.unwrap(), "Relayer error: Relayer is paused");
634 }
635
636 #[tokio::test]
637 async fn test_handle_request_using_trait() {
638 setup_test_env();
639 let state = create_mock_app_state(
640 None,
641 Some(vec![create_mock_relayer("test".to_string(), false)]),
642 Some(vec![create_mock_signer()]),
643 Some(vec![create_mock_network()]),
644 None,
645 None,
646 )
647 .await;
648
649 let request = Request {
650 request_id: "test".to_string(),
651 relayer_id: "test".to_string(),
652 method: PluginMethod::SendTransaction,
653 payload: serde_json::json!(create_mock_evm_transaction_request()),
654 http_request_id: None,
655 };
656
657 let relayer_api = RelayerApi;
658
659 let state = web::ThinData(state);
660
661 let response = RelayerApiTrait::handle_request(&relayer_api, request.clone(), &state).await;
662
663 assert!(response.error.is_none());
664 assert!(response.result.is_some());
665
666 let response =
667 RelayerApiTrait::process_request(&relayer_api, request.clone(), &state).await;
668
669 assert!(response.is_ok());
670
671 let response =
672 RelayerApiTrait::handle_send_transaction(&relayer_api, request.clone(), &state).await;
673
674 assert!(response.is_ok());
675 }
676
677 #[tokio::test]
678 async fn test_handle_get_transaction() {
679 setup_test_env();
680 let state = create_mock_app_state(
681 None,
682 Some(vec![create_mock_relayer("test".to_string(), false)]),
683 Some(vec![create_mock_signer()]),
684 Some(vec![create_mock_network()]),
685 None,
686 Some(vec![create_mock_transaction()]),
687 )
688 .await;
689
690 let request = Request {
691 request_id: "test".to_string(),
692 relayer_id: "test".to_string(),
693 method: PluginMethod::GetTransaction,
694 payload: serde_json::json!(GetTransactionRequest {
695 transaction_id: "test".to_string(),
696 }),
697 http_request_id: None,
698 };
699
700 let relayer_api = RelayerApi;
701 let response = relayer_api
702 .handle_request(request.clone(), &web::ThinData(state))
703 .await;
704
705 assert!(response.error.is_none());
706 assert!(response.result.is_some());
707 }
708
709 #[tokio::test]
710 async fn test_handle_get_transaction_error_relayer_not_found() {
711 setup_test_env();
712 let state = create_mock_app_state(
713 None,
714 None,
715 Some(vec![create_mock_signer()]),
716 Some(vec![create_mock_network()]),
717 None,
718 Some(vec![create_mock_transaction()]),
719 )
720 .await;
721
722 let request = Request {
723 request_id: "test".to_string(),
724 relayer_id: "test".to_string(),
725 method: PluginMethod::GetTransaction,
726 payload: serde_json::json!(GetTransactionRequest {
727 transaction_id: "test".to_string(),
728 }),
729 http_request_id: None,
730 };
731
732 let relayer_api = RelayerApi;
733 let response = relayer_api
734 .handle_request(request.clone(), &web::ThinData(state))
735 .await;
736
737 assert!(response.error.is_some());
738 let error = response.error.unwrap();
739 assert!(error.contains("Relayer with ID test not found"));
740 }
741
742 #[tokio::test]
743 async fn test_handle_get_transaction_error_transaction_not_found() {
744 setup_test_env();
745 let state = create_mock_app_state(
746 None,
747 Some(vec![create_mock_relayer("test".to_string(), false)]),
748 Some(vec![create_mock_signer()]),
749 Some(vec![create_mock_network()]),
750 None,
751 None,
752 )
753 .await;
754
755 let request = Request {
756 request_id: "test".to_string(),
757 relayer_id: "test".to_string(),
758 method: PluginMethod::GetTransaction,
759 payload: serde_json::json!(GetTransactionRequest {
760 transaction_id: "test".to_string(),
761 }),
762 http_request_id: None,
763 };
764
765 let relayer_api = RelayerApi;
766 let response = relayer_api
767 .handle_request(request.clone(), &web::ThinData(state))
768 .await;
769
770 assert!(response.error.is_some());
771 let error = response.error.unwrap();
772 assert!(error.contains("Transaction with ID test not found"));
773 }
774
775 #[tokio::test]
776 async fn test_handle_get_relayer_status_relayer_not_found() {
777 setup_test_env();
778 let state = create_mock_app_state(
779 None,
780 None,
781 Some(vec![create_mock_signer()]),
782 Some(vec![create_mock_network()]),
783 None,
784 None,
785 )
786 .await;
787
788 let request = Request {
789 request_id: "test".to_string(),
790 relayer_id: "test".to_string(),
791 method: PluginMethod::GetRelayerStatus,
792 payload: serde_json::json!({}),
793 http_request_id: None,
794 };
795
796 let relayer_api = RelayerApi;
797 let response = relayer_api
798 .handle_request(request.clone(), &web::ThinData(state))
799 .await;
800
801 assert!(response.error.is_some());
802 let error = response.error.unwrap();
803 assert!(error.contains("Relayer with ID test not found"));
804 }
805
806 #[tokio::test]
807 async fn test_handle_sign_transaction_evm_not_supported() {
808 setup_test_env();
809 let state = create_mock_app_state(
810 None,
811 Some(vec![create_mock_relayer("test".to_string(), false)]),
812 Some(vec![create_mock_signer()]),
813 Some(vec![create_mock_network()]),
814 None,
815 None,
816 )
817 .await;
818
819 let request = Request {
820 request_id: "test".to_string(),
821 relayer_id: "test".to_string(),
822 method: PluginMethod::SignTransaction,
823 payload: serde_json::json!({
824 "unsigned_xdr": "test_xdr"
825 }),
826 http_request_id: None,
827 };
828
829 let relayer_api = RelayerApi;
830 let response = relayer_api
831 .handle_request(request.clone(), &web::ThinData(state))
832 .await;
833
834 assert!(response.error.is_some());
835 let error = response.error.unwrap();
836 assert!(error.contains("sign_transaction not supported for EVM"));
837 }
838
839 #[tokio::test]
840 async fn test_handle_sign_transaction_invalid_payload() {
841 setup_test_env();
842 let state = create_mock_app_state(
843 None,
844 Some(vec![create_mock_relayer("test".to_string(), false)]),
845 Some(vec![create_mock_signer()]),
846 Some(vec![create_mock_network()]),
847 None,
848 None,
849 )
850 .await;
851
852 let request = Request {
853 request_id: "test".to_string(),
854 relayer_id: "test".to_string(),
855 method: PluginMethod::SignTransaction,
856 payload: serde_json::json!({"invalid": "payload"}),
857 http_request_id: None,
858 };
859
860 let relayer_api = RelayerApi;
861 let response = relayer_api
862 .handle_request(request.clone(), &web::ThinData(state))
863 .await;
864
865 assert!(response.error.is_some());
866 let error = response.error.unwrap();
867 assert!(error.contains("Invalid payload"));
868 }
869
870 #[tokio::test]
871 async fn test_handle_sign_transaction_relayer_not_found() {
872 setup_test_env();
873 let state = create_mock_app_state(
874 None,
875 None,
876 Some(vec![create_mock_signer()]),
877 Some(vec![create_mock_network()]),
878 None,
879 None,
880 )
881 .await;
882
883 let request = Request {
884 request_id: "test".to_string(),
885 relayer_id: "test".to_string(),
886 method: PluginMethod::SignTransaction,
887 payload: serde_json::json!({
888 "unsigned_xdr": "test_xdr"
889 }),
890 http_request_id: None,
891 };
892
893 let relayer_api = RelayerApi;
894 let response = relayer_api
895 .handle_request(request.clone(), &web::ThinData(state))
896 .await;
897
898 assert!(response.error.is_some());
899 let error = response.error.unwrap();
900 assert!(error.contains("Relayer with ID test not found"));
901 }
902
903 #[tokio::test]
904 async fn test_handle_get_relayer_info_success() {
905 setup_test_env();
906 let state = create_mock_app_state(
907 None,
908 Some(vec![create_mock_relayer("test".to_string(), false)]),
909 Some(vec![create_mock_signer()]),
910 Some(vec![create_mock_network()]),
911 None,
912 None,
913 )
914 .await;
915
916 let request = Request {
917 request_id: "test".to_string(),
918 relayer_id: "test".to_string(),
919 method: PluginMethod::GetRelayer,
920 payload: serde_json::json!({}),
921 http_request_id: None,
922 };
923
924 let relayer_api = RelayerApi;
925 let response = relayer_api
926 .handle_request(request.clone(), &web::ThinData(state))
927 .await;
928
929 assert!(response.error.is_none());
930 assert!(response.result.is_some());
931
932 let result = response.result.unwrap();
933 assert!(result.get("id").is_some());
934 assert!(result.get("name").is_some());
935 assert!(result.get("network").is_some());
936 assert!(result.get("address").is_some());
937 }
938
939 #[tokio::test]
940 async fn test_handle_get_relayer_info_relayer_not_found() {
941 setup_test_env();
942 let state = create_mock_app_state(
943 None,
944 None,
945 Some(vec![create_mock_signer()]),
946 Some(vec![create_mock_network()]),
947 None,
948 None,
949 )
950 .await;
951
952 let request = Request {
953 request_id: "test".to_string(),
954 relayer_id: "test".to_string(),
955 method: PluginMethod::GetRelayer,
956 payload: serde_json::json!({}),
957 http_request_id: None,
958 };
959
960 let relayer_api = RelayerApi;
961 let response = relayer_api
962 .handle_request(request.clone(), &web::ThinData(state))
963 .await;
964
965 assert!(response.error.is_some());
966 let error = response.error.unwrap();
967 assert!(error.contains("Relayer with ID test not found"));
968 }
969
970 #[tokio::test]
971 async fn test_handle_rpc_request_evm_success() {
972 setup_test_env();
973 let state = create_mock_app_state(
974 None,
975 Some(vec![create_mock_relayer("test".to_string(), false)]),
976 Some(vec![create_mock_signer()]),
977 Some(vec![create_mock_network()]),
978 None,
979 None,
980 )
981 .await;
982
983 let request = Request {
984 request_id: "test-rpc-1".to_string(),
985 relayer_id: "test".to_string(),
986 method: PluginMethod::Rpc,
987 payload: serde_json::json!({
988 "jsonrpc": "2.0",
989 "method": "eth_blockNumber",
990 "params": [],
991 "id": 1
992 }),
993 http_request_id: None,
994 };
995
996 let relayer_api = RelayerApi;
997 let response = relayer_api
998 .handle_request(request.clone(), &web::ThinData(state))
999 .await;
1000
1001 assert!(response.error.is_none());
1002 assert!(response.result.is_some());
1003 let result = response.result.unwrap();
1004 assert!(result.get("jsonrpc").is_some());
1005 }
1006
1007 #[tokio::test]
1008 async fn test_handle_rpc_request_invalid_payload() {
1009 setup_test_env();
1010 let state = create_mock_app_state(
1011 None,
1012 Some(vec![create_mock_relayer("test".to_string(), false)]),
1013 Some(vec![create_mock_signer()]),
1014 Some(vec![create_mock_network()]),
1015 None,
1016 None,
1017 )
1018 .await;
1019
1020 let request = Request {
1021 request_id: "test-rpc-2".to_string(),
1022 relayer_id: "test".to_string(),
1023 method: PluginMethod::Rpc,
1024 payload: serde_json::json!({
1025 "invalid": "payload"
1026 }),
1027 http_request_id: None,
1028 };
1029
1030 let relayer_api = RelayerApi;
1031 let response = relayer_api
1032 .handle_request(request.clone(), &web::ThinData(state))
1033 .await;
1034
1035 assert!(response.error.is_some());
1036 let error = response.error.unwrap();
1037 assert!(error.contains("Invalid payload") || error.contains("Missing 'method' field"));
1038 }
1039
1040 #[tokio::test]
1041 async fn test_handle_rpc_request_relayer_not_found() {
1042 setup_test_env();
1043 let state = create_mock_app_state(
1044 None,
1045 None,
1046 Some(vec![create_mock_signer()]),
1047 Some(vec![create_mock_network()]),
1048 None,
1049 None,
1050 )
1051 .await;
1052
1053 let request = Request {
1054 request_id: "test-rpc-3".to_string(),
1055 relayer_id: "nonexistent".to_string(),
1056 method: PluginMethod::Rpc,
1057 payload: serde_json::json!({
1058 "jsonrpc": "2.0",
1059 "method": "eth_blockNumber",
1060 "params": [],
1061 "id": 1
1062 }),
1063 http_request_id: None,
1064 };
1065
1066 let relayer_api = RelayerApi;
1067 let response = relayer_api
1068 .handle_request(request.clone(), &web::ThinData(state))
1069 .await;
1070
1071 assert!(response.error.is_some());
1072 let error = response.error.unwrap();
1073 assert!(error.contains("Relayer with ID nonexistent not found"));
1074 }
1075
1076 #[tokio::test]
1077 async fn test_handle_rpc_request_paused_relayer() {
1078 setup_test_env();
1079 let paused = true;
1080 let state = create_mock_app_state(
1081 None,
1082 Some(vec![create_mock_relayer("test".to_string(), paused)]),
1083 Some(vec![create_mock_signer()]),
1084 Some(vec![create_mock_network()]),
1085 None,
1086 None,
1087 )
1088 .await;
1089
1090 let request = Request {
1091 request_id: "test-rpc-4".to_string(),
1092 relayer_id: "test".to_string(),
1093 method: PluginMethod::Rpc,
1094 payload: serde_json::json!({
1095 "jsonrpc": "2.0",
1096 "method": "eth_blockNumber",
1097 "params": [],
1098 "id": 1
1099 }),
1100 http_request_id: None,
1101 };
1102
1103 let relayer_api = RelayerApi;
1104 let response = relayer_api
1105 .handle_request(request.clone(), &web::ThinData(state))
1106 .await;
1107
1108 assert!(response.error.is_some());
1109 let error = response.error.unwrap();
1110 assert!(error.contains("Relayer is paused"));
1111 }
1112
1113 #[tokio::test]
1114 async fn test_handle_rpc_request_with_string_id() {
1115 setup_test_env();
1116 let state = create_mock_app_state(
1117 None,
1118 Some(vec![create_mock_relayer("test".to_string(), false)]),
1119 Some(vec![create_mock_signer()]),
1120 Some(vec![create_mock_network()]),
1121 None,
1122 None,
1123 )
1124 .await;
1125
1126 let request = Request {
1127 request_id: "test-rpc-5".to_string(),
1128 relayer_id: "test".to_string(),
1129 method: PluginMethod::Rpc,
1130 payload: serde_json::json!({
1131 "jsonrpc": "2.0",
1132 "method": "eth_chainId",
1133 "params": [],
1134 "id": "custom-string-id"
1135 }),
1136 http_request_id: None,
1137 };
1138
1139 let relayer_api = RelayerApi;
1140 let response = relayer_api
1141 .handle_request(request.clone(), &web::ThinData(state))
1142 .await;
1143
1144 assert!(response.error.is_none());
1145 assert!(response.result.is_some());
1146 let result = response.result.unwrap();
1147 assert_eq!(result.get("id").unwrap(), "custom-string-id");
1148 }
1149
1150 #[tokio::test]
1151 async fn test_handle_rpc_request_with_null_id() {
1152 setup_test_env();
1153 let state = create_mock_app_state(
1154 None,
1155 Some(vec![create_mock_relayer("test".to_string(), false)]),
1156 Some(vec![create_mock_signer()]),
1157 Some(vec![create_mock_network()]),
1158 None,
1159 None,
1160 )
1161 .await;
1162
1163 let request = Request {
1164 request_id: "test-rpc-6".to_string(),
1165 relayer_id: "test".to_string(),
1166 method: PluginMethod::Rpc,
1167 payload: serde_json::json!({
1168 "jsonrpc": "2.0",
1169 "method": "eth_chainId",
1170 "params": [],
1171 "id": null
1172 }),
1173 http_request_id: None,
1174 };
1175
1176 let relayer_api = RelayerApi;
1177 let response = relayer_api
1178 .handle_request(request.clone(), &web::ThinData(state))
1179 .await;
1180
1181 assert!(response.error.is_none());
1182 assert!(response.result.is_some());
1183 }
1184
1185 #[tokio::test]
1186 async fn test_handle_rpc_request_with_array_params() {
1187 setup_test_env();
1188 let state = create_mock_app_state(
1189 None,
1190 Some(vec![create_mock_relayer("test".to_string(), false)]),
1191 Some(vec![create_mock_signer()]),
1192 Some(vec![create_mock_network()]),
1193 None,
1194 None,
1195 )
1196 .await;
1197
1198 let request = Request {
1199 request_id: "test-rpc-7".to_string(),
1200 relayer_id: "test".to_string(),
1201 method: PluginMethod::Rpc,
1202 payload: serde_json::json!({
1203 "jsonrpc": "2.0",
1204 "method": "eth_getBalance",
1205 "params": ["0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "latest"],
1206 "id": 1
1207 }),
1208 http_request_id: None,
1209 };
1210
1211 let relayer_api = RelayerApi;
1212 let response = relayer_api
1213 .handle_request(request.clone(), &web::ThinData(state))
1214 .await;
1215
1216 assert!(response.error.is_none());
1217 assert!(response.result.is_some());
1218 }
1219
1220 #[tokio::test]
1221 async fn test_handle_rpc_request_with_object_params() {
1222 setup_test_env();
1223 let state = create_mock_app_state(
1224 None,
1225 Some(vec![create_mock_relayer("test".to_string(), false)]),
1226 Some(vec![create_mock_signer()]),
1227 Some(vec![create_mock_network()]),
1228 None,
1229 None,
1230 )
1231 .await;
1232
1233 let request = Request {
1234 request_id: "test-rpc-8".to_string(),
1235 relayer_id: "test".to_string(),
1236 method: PluginMethod::Rpc,
1237 payload: serde_json::json!({
1238 "jsonrpc": "2.0",
1239 "method": "eth_call",
1240 "params": {
1241 "to": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
1242 "data": "0x"
1243 },
1244 "id": 1
1245 }),
1246 http_request_id: None,
1247 };
1248
1249 let relayer_api = RelayerApi;
1250 let response = relayer_api
1251 .handle_request(request.clone(), &web::ThinData(state))
1252 .await;
1253
1254 assert!(response.error.is_none());
1255 assert!(response.result.is_some());
1256 }
1257
1258 #[tokio::test]
1259 async fn test_handle_rpc_request_missing_method() {
1260 setup_test_env();
1261 let state = create_mock_app_state(
1262 None,
1263 Some(vec![create_mock_relayer("test".to_string(), false)]),
1264 Some(vec![create_mock_signer()]),
1265 Some(vec![create_mock_network()]),
1266 None,
1267 None,
1268 )
1269 .await;
1270
1271 let request = Request {
1272 request_id: "test-rpc-9".to_string(),
1273 relayer_id: "test".to_string(),
1274 method: PluginMethod::Rpc,
1275 payload: serde_json::json!({
1276 "jsonrpc": "2.0",
1277 "params": [],
1278 "id": 1
1279 }),
1280 http_request_id: None,
1281 };
1282
1283 let relayer_api = RelayerApi;
1284 let response = relayer_api
1285 .handle_request(request.clone(), &web::ThinData(state))
1286 .await;
1287
1288 assert!(response.error.is_some());
1289 let error = response.error.unwrap();
1290 assert!(error.contains("Missing 'method' field") || error.contains("Invalid payload"));
1291 }
1292
1293 #[tokio::test]
1294 async fn test_handle_rpc_request_empty_method() {
1295 setup_test_env();
1296 let state = create_mock_app_state(
1297 None,
1298 Some(vec![create_mock_relayer("test".to_string(), false)]),
1299 Some(vec![create_mock_signer()]),
1300 Some(vec![create_mock_network()]),
1301 None,
1302 None,
1303 )
1304 .await;
1305
1306 let request = Request {
1307 request_id: "test-rpc-10".to_string(),
1308 relayer_id: "test".to_string(),
1309 method: PluginMethod::Rpc,
1310 payload: serde_json::json!({
1311 "jsonrpc": "2.0",
1312 "method": "",
1313 "params": [],
1314 "id": 1
1315 }),
1316 http_request_id: None,
1317 };
1318
1319 let relayer_api = RelayerApi;
1320 let response = relayer_api
1321 .handle_request(request.clone(), &web::ThinData(state))
1322 .await;
1323
1324 assert!(
1327 response.error.is_some()
1328 || (response.result.is_some()
1329 && response.result.as_ref().unwrap().get("error").is_some())
1330 );
1331 }
1332
1333 #[tokio::test]
1334 async fn test_handle_rpc_request_with_http_request_id() {
1335 setup_test_env();
1336 let state = create_mock_app_state(
1337 None,
1338 Some(vec![create_mock_relayer("test".to_string(), false)]),
1339 Some(vec![create_mock_signer()]),
1340 Some(vec![create_mock_network()]),
1341 None,
1342 None,
1343 )
1344 .await;
1345
1346 let request = Request {
1347 request_id: "test-rpc-11".to_string(),
1348 relayer_id: "test".to_string(),
1349 method: PluginMethod::Rpc,
1350 payload: serde_json::json!({
1351 "jsonrpc": "2.0",
1352 "method": "eth_blockNumber",
1353 "params": [],
1354 "id": 1
1355 }),
1356 http_request_id: Some("http-req-123".to_string()),
1357 };
1358
1359 let relayer_api = RelayerApi;
1360 let response = relayer_api
1361 .handle_request(request.clone(), &web::ThinData(state))
1362 .await;
1363
1364 assert!(response.error.is_none());
1365 assert!(response.result.is_some());
1366 assert_eq!(response.request_id, "test-rpc-11");
1367 }
1368
1369 #[tokio::test]
1370 async fn test_handle_rpc_request_default_jsonrpc_version() {
1371 setup_test_env();
1372 let state = create_mock_app_state(
1373 None,
1374 Some(vec![create_mock_relayer("test".to_string(), false)]),
1375 Some(vec![create_mock_signer()]),
1376 Some(vec![create_mock_network()]),
1377 None,
1378 None,
1379 )
1380 .await;
1381
1382 let request = Request {
1383 request_id: "test-rpc-12".to_string(),
1384 relayer_id: "test".to_string(),
1385 method: PluginMethod::Rpc,
1386 payload: serde_json::json!({
1387 "method": "eth_blockNumber",
1388 "params": [],
1389 "id": 1
1390 }),
1391 http_request_id: None,
1392 };
1393
1394 let relayer_api = RelayerApi;
1395 let response = relayer_api
1396 .handle_request(request.clone(), &web::ThinData(state))
1397 .await;
1398
1399 if response.error.is_none() {
1401 assert!(response.result.is_some());
1402 let result = response.result.unwrap();
1403 assert_eq!(result.get("jsonrpc").unwrap(), "2.0");
1404 } else {
1405 assert!(response.error.is_some());
1407 }
1408 }
1409
1410 #[tokio::test]
1411 async fn test_handle_rpc_request_custom_jsonrpc_version() {
1412 setup_test_env();
1413 let state = create_mock_app_state(
1414 None,
1415 Some(vec![create_mock_relayer("test".to_string(), false)]),
1416 Some(vec![create_mock_signer()]),
1417 Some(vec![create_mock_network()]),
1418 None,
1419 None,
1420 )
1421 .await;
1422
1423 let request = Request {
1424 request_id: "test-rpc-13".to_string(),
1425 relayer_id: "test".to_string(),
1426 method: PluginMethod::Rpc,
1427 payload: serde_json::json!({
1428 "jsonrpc": "1.0",
1429 "method": "eth_blockNumber",
1430 "params": [],
1431 "id": 1
1432 }),
1433 http_request_id: None,
1434 };
1435
1436 let relayer_api = RelayerApi;
1437 let response = relayer_api
1438 .handle_request(request.clone(), &web::ThinData(state))
1439 .await;
1440
1441 assert!(response.error.is_none());
1442 assert!(response.result.is_some());
1443 }
1444
1445 #[tokio::test]
1446 async fn test_handle_rpc_request_result_structure() {
1447 setup_test_env();
1448 let state = create_mock_app_state(
1449 None,
1450 Some(vec![create_mock_relayer("test".to_string(), false)]),
1451 Some(vec![create_mock_signer()]),
1452 Some(vec![create_mock_network()]),
1453 None,
1454 None,
1455 )
1456 .await;
1457
1458 let request = Request {
1459 request_id: "test-rpc-14".to_string(),
1460 relayer_id: "test".to_string(),
1461 method: PluginMethod::Rpc,
1462 payload: serde_json::json!({
1463 "jsonrpc": "2.0",
1464 "method": "eth_blockNumber",
1465 "params": [],
1466 "id": 42
1467 }),
1468 http_request_id: None,
1469 };
1470
1471 let relayer_api = RelayerApi;
1472 let response = relayer_api
1473 .handle_request(request.clone(), &web::ThinData(state))
1474 .await;
1475
1476 assert!(response.error.is_none());
1477 assert!(response.result.is_some());
1478 assert_eq!(response.request_id, "test-rpc-14");
1479
1480 let result = response.result.unwrap();
1481 assert!(result.get("jsonrpc").is_some());
1482 assert!(result.get("id").is_some());
1483 assert!(result.get("result").is_some() || result.get("error").is_some());
1485 }
1486}