1use async_trait::async_trait;
8use eyre::{eyre, Result};
9use soroban_rs::stellar_rpc_client::Client;
10use soroban_rs::stellar_rpc_client::{
11 EventStart, EventType, GetEventsResponse, GetLatestLedgerResponse, GetLedgerEntriesResponse,
12 GetNetworkResponse, GetTransactionResponse, GetTransactionsRequest, GetTransactionsResponse,
13 SimulateTransactionResponse,
14};
15use soroban_rs::xdr::{AccountEntry, Hash, LedgerKey, TransactionEnvelope};
16#[cfg(test)]
17use soroban_rs::xdr::{AccountId, LedgerKeyAccount, PublicKey, Uint256};
18use soroban_rs::SorobanTransactionResponse;
19
20#[cfg(test)]
21use mockall::automock;
22
23use crate::models::RpcConfig;
24use crate::services::provider::ProviderError;
25
26#[derive(Debug, Clone)]
27pub struct GetEventsRequest {
28 pub start: EventStart,
29 pub event_type: Option<EventType>,
30 pub contract_ids: Vec<String>,
31 pub topics: Vec<String>,
32 pub limit: Option<usize>,
33}
34
35#[derive(Clone, Debug)]
36pub struct StellarProvider {
37 client: Client,
38 rpc_url: String,
39}
40
41#[async_trait]
42#[cfg_attr(test, automock)]
43#[allow(dead_code)]
44pub trait StellarProviderTrait: Send + Sync {
45 async fn get_account(&self, account_id: &str) -> Result<AccountEntry>;
46 async fn simulate_transaction_envelope(
47 &self,
48 tx_envelope: &TransactionEnvelope,
49 ) -> Result<SimulateTransactionResponse>;
50 async fn send_transaction_polling(
51 &self,
52 tx_envelope: &TransactionEnvelope,
53 ) -> Result<SorobanTransactionResponse>;
54 async fn get_network(&self) -> Result<GetNetworkResponse>;
55 async fn get_latest_ledger(&self) -> Result<GetLatestLedgerResponse>;
56 async fn send_transaction(&self, tx_envelope: &TransactionEnvelope) -> Result<Hash>;
57 async fn get_transaction(&self, tx_id: &Hash) -> Result<GetTransactionResponse>;
58 async fn get_transactions(
59 &self,
60 request: GetTransactionsRequest,
61 ) -> Result<GetTransactionsResponse>;
62 async fn get_ledger_entries(&self, keys: &[LedgerKey]) -> Result<GetLedgerEntriesResponse>;
63 async fn get_events(&self, request: GetEventsRequest) -> Result<GetEventsResponse>;
64 fn rpc_url(&self) -> &str;
65}
66
67impl StellarProvider {
68 pub fn new(mut rpc_configs: Vec<RpcConfig>, _timeout: u64) -> Result<Self, ProviderError> {
69 if rpc_configs.is_empty() {
70 return Err(ProviderError::NetworkConfiguration(
71 "No RPC configurations provided for StellarProvider".to_string(),
72 ));
73 }
74
75 RpcConfig::validate_list(&rpc_configs)
76 .map_err(|e| ProviderError::NetworkConfiguration(e.to_string()))?;
77
78 rpc_configs.retain(|config| config.get_weight() > 0);
79
80 if rpc_configs.is_empty() {
81 return Err(ProviderError::NetworkConfiguration(
82 "No active RPC configurations provided (all weights are 0 or list was empty after filtering)".to_string(),
83 ));
84 }
85
86 rpc_configs.sort_by_key(|config| std::cmp::Reverse(config.get_weight()));
87
88 let selected_config = &rpc_configs[0];
89 let url = &selected_config.url;
90
91 let client = Client::new(url).map_err(|e| {
92 ProviderError::NetworkConfiguration(format!(
93 "Failed to create Stellar RPC client: {} - URL: '{}'",
94 e, url
95 ))
96 })?;
97 Ok(Self {
98 client,
99 rpc_url: url.clone(),
100 })
101 }
102
103 pub fn rpc_url(&self) -> &str {
104 &self.rpc_url
105 }
106}
107
108impl AsRef<StellarProvider> for StellarProvider {
109 fn as_ref(&self) -> &StellarProvider {
110 self
111 }
112}
113
114#[async_trait]
115impl StellarProviderTrait for StellarProvider {
116 async fn get_account(&self, account_id: &str) -> Result<AccountEntry> {
117 self.client
118 .get_account(account_id)
119 .await
120 .map_err(|e| eyre!("Failed to get account: {}", e))
121 }
122
123 async fn simulate_transaction_envelope(
124 &self,
125 tx_envelope: &TransactionEnvelope,
126 ) -> Result<SimulateTransactionResponse> {
127 self.client
128 .simulate_transaction_envelope(tx_envelope, None)
129 .await
130 .map_err(|e| eyre!("Failed to simulate transaction: {}", e))
131 }
132
133 async fn send_transaction_polling(
134 &self,
135 tx_envelope: &TransactionEnvelope,
136 ) -> Result<SorobanTransactionResponse> {
137 self.client
138 .send_transaction_polling(tx_envelope)
139 .await
140 .map(SorobanTransactionResponse::from)
141 .map_err(|e| eyre!("Failed to send transaction (polling): {}", e))
142 }
143
144 async fn get_network(&self) -> Result<GetNetworkResponse> {
145 self.client
146 .get_network()
147 .await
148 .map_err(|e| eyre!("Failed to get network: {}", e))
149 }
150
151 async fn get_latest_ledger(&self) -> Result<GetLatestLedgerResponse> {
152 self.client
153 .get_latest_ledger()
154 .await
155 .map_err(|e| eyre!("Failed to get latest ledger: {}", e))
156 }
157
158 async fn send_transaction(&self, tx_envelope: &TransactionEnvelope) -> Result<Hash> {
159 self.client
160 .send_transaction(tx_envelope)
161 .await
162 .map_err(|e| eyre!("Failed to send transaction: {}", e))
163 }
164
165 async fn get_transaction(&self, tx_id: &Hash) -> Result<GetTransactionResponse> {
166 self.client
167 .get_transaction(tx_id)
168 .await
169 .map_err(|e| eyre!("Failed to get transaction: {}", e))
170 }
171
172 async fn get_transactions(
173 &self,
174 request: GetTransactionsRequest,
175 ) -> Result<GetTransactionsResponse> {
176 self.client
177 .get_transactions(request)
178 .await
179 .map_err(|e| eyre!("Failed to get transactions: {}", e))
180 }
181
182 async fn get_ledger_entries(&self, keys: &[LedgerKey]) -> Result<GetLedgerEntriesResponse> {
183 self.client
184 .get_ledger_entries(keys)
185 .await
186 .map_err(|e| eyre!("Failed to get ledger entries: {}", e))
187 }
188
189 async fn get_events(&self, request: GetEventsRequest) -> Result<GetEventsResponse> {
190 self.client
191 .get_events(
192 request.start,
193 request.event_type,
194 &request.contract_ids,
195 &request.topics,
196 request.limit,
197 )
198 .await
199 .map_err(|e| eyre!("Failed to get events: {}", e))
200 }
201
202 fn rpc_url(&self) -> &str {
203 &self.rpc_url
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use crate::services::provider::stellar::{
211 GetEventsRequest, StellarProvider, StellarProviderTrait,
212 };
213 use eyre::eyre;
214 use futures::FutureExt;
215 use mockall::predicate as p;
216 use soroban_rs::stellar_rpc_client::{
217 EventStart, GetEventsResponse, GetLatestLedgerResponse, GetLedgerEntriesResponse,
218 GetNetworkResponse, GetTransactionEvents, GetTransactionResponse, GetTransactionsRequest,
219 GetTransactionsResponse, SimulateTransactionResponse,
220 };
221 use soroban_rs::xdr::{
222 AccountEntryExt, Hash, LedgerKey, OperationResult, String32, Thresholds,
223 TransactionEnvelope, TransactionResult, TransactionResultExt, TransactionResultResult,
224 VecM,
225 };
226 use soroban_rs::{create_mock_set_options_tx_envelope, SorobanTransactionResponse};
227 use std::str::FromStr;
228
229 fn dummy_hash() -> Hash {
230 Hash([0u8; 32])
231 }
232
233 fn dummy_get_network_response() -> GetNetworkResponse {
234 GetNetworkResponse {
235 friendbot_url: Some("https://friendbot.testnet.stellar.org/".into()),
236 passphrase: "Test SDF Network ; September 2015".into(),
237 protocol_version: 20,
238 }
239 }
240
241 fn dummy_get_latest_ledger_response() -> GetLatestLedgerResponse {
242 GetLatestLedgerResponse {
243 id: "c73c5eac58a441d4eb733c35253ae85f783e018f7be5ef974258fed067aabb36".into(),
244 protocol_version: 20,
245 sequence: 2_539_605,
246 }
247 }
248
249 fn dummy_simulate() -> SimulateTransactionResponse {
250 SimulateTransactionResponse {
251 min_resource_fee: 100,
252 transaction_data: "test".to_string(),
253 ..Default::default()
254 }
255 }
256
257 fn create_success_tx_result() -> TransactionResult {
258 let empty_vec: Vec<OperationResult> = Vec::new();
260 let op_results = empty_vec.try_into().unwrap_or_default();
261
262 TransactionResult {
263 fee_charged: 100,
264 result: TransactionResultResult::TxSuccess(op_results),
265 ext: TransactionResultExt::V0,
266 }
267 }
268
269 fn dummy_get_transaction_response() -> GetTransactionResponse {
270 GetTransactionResponse {
271 status: "SUCCESS".to_string(),
272 envelope: None,
273 result: Some(create_success_tx_result()),
274 result_meta: None,
275 events: GetTransactionEvents {
276 contract_events: vec![],
277 diagnostic_events: vec![],
278 transaction_events: vec![],
279 },
280 ledger: None,
281 }
282 }
283
284 fn dummy_soroban_tx() -> SorobanTransactionResponse {
285 SorobanTransactionResponse {
286 response: dummy_get_transaction_response(),
287 }
288 }
289
290 fn dummy_get_transactions_response() -> GetTransactionsResponse {
291 GetTransactionsResponse {
292 transactions: vec![],
293 latest_ledger: 0,
294 latest_ledger_close_time: 0,
295 oldest_ledger: 0,
296 oldest_ledger_close_time: 0,
297 cursor: 0,
298 }
299 }
300
301 fn dummy_get_ledger_entries_response() -> GetLedgerEntriesResponse {
302 GetLedgerEntriesResponse {
303 entries: None,
304 latest_ledger: 0,
305 }
306 }
307
308 fn dummy_get_events_response() -> GetEventsResponse {
309 GetEventsResponse {
310 events: vec![],
311 latest_ledger: 0,
312 latest_ledger_close_time: "0".to_string(),
313 oldest_ledger: 0,
314 oldest_ledger_close_time: "0".to_string(),
315 cursor: "0".to_string(),
316 }
317 }
318
319 fn dummy_transaction_envelope() -> TransactionEnvelope {
320 create_mock_set_options_tx_envelope()
321 }
322
323 fn dummy_ledger_key() -> LedgerKey {
324 LedgerKey::Account(LedgerKeyAccount {
325 account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256([0; 32]))),
326 })
327 }
328
329 pub fn mock_account_entry(account_id: &str) -> AccountEntry {
330 AccountEntry {
331 account_id: AccountId(PublicKey::from_str(account_id).unwrap()),
332 balance: 0,
333 ext: AccountEntryExt::V0,
334 flags: 0,
335 home_domain: String32::default(),
336 inflation_dest: None,
337 seq_num: 0.into(),
338 num_sub_entries: 0,
339 signers: VecM::default(),
340 thresholds: Thresholds([0, 0, 0, 0]),
341 }
342 }
343
344 fn dummy_account_entry() -> AccountEntry {
345 mock_account_entry("GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF")
346 }
347
348 #[test]
353 fn test_new_provider() {
354 let provider =
355 StellarProvider::new(vec![RpcConfig::new("http://localhost:8000".to_string())], 0);
356 assert!(provider.is_ok());
357
358 let provider_err = StellarProvider::new(vec![], 0);
359 assert!(provider_err.is_err());
360 match provider_err.unwrap_err() {
361 ProviderError::NetworkConfiguration(msg) => {
362 assert!(msg.contains("No RPC configurations provided"));
363 }
364 _ => panic!("Unexpected error type"),
365 }
366 }
367
368 #[test]
369 fn test_new_provider_selects_highest_weight() {
370 let configs = vec![
371 RpcConfig::with_weight("http://rpc1.example.com".to_string(), 10).unwrap(),
372 RpcConfig::with_weight("http://rpc2.example.com".to_string(), 100).unwrap(), RpcConfig::with_weight("http://rpc3.example.com".to_string(), 50).unwrap(),
374 ];
375 let provider = StellarProvider::new(configs, 0);
376 assert!(provider.is_ok());
377 }
381
382 #[test]
383 fn test_new_provider_ignores_weight_zero() {
384 let configs = vec![
385 RpcConfig::with_weight("http://rpc1.example.com".to_string(), 0).unwrap(), RpcConfig::with_weight("http://rpc2.example.com".to_string(), 100).unwrap(), ];
388 let provider = StellarProvider::new(configs, 0);
389 assert!(provider.is_ok());
390
391 let configs_only_zero =
392 vec![RpcConfig::with_weight("http://rpc1.example.com".to_string(), 0).unwrap()];
393 let provider_err = StellarProvider::new(configs_only_zero, 0);
394 assert!(provider_err.is_err());
395 match provider_err.unwrap_err() {
396 ProviderError::NetworkConfiguration(msg) => {
397 assert!(msg.contains("No active RPC configurations provided"));
398 }
399 _ => panic!("Unexpected error type"),
400 }
401 }
402
403 #[test]
404 fn test_new_provider_invalid_url_scheme() {
405 let configs = vec![RpcConfig::new("ftp://invalid.example.com".to_string())];
406 let provider_err = StellarProvider::new(configs, 0);
407 assert!(provider_err.is_err());
408 match provider_err.unwrap_err() {
409 ProviderError::NetworkConfiguration(msg) => {
410 assert!(msg.contains("Invalid URL scheme"));
411 }
412 _ => panic!("Unexpected error type"),
413 }
414 }
415
416 #[test]
417 fn test_new_provider_all_zero_weight_configs() {
418 let configs = vec![
419 RpcConfig::with_weight("http://rpc1.example.com".to_string(), 0).unwrap(),
420 RpcConfig::with_weight("http://rpc2.example.com".to_string(), 0).unwrap(),
421 ];
422 let provider_err = StellarProvider::new(configs, 0);
423 assert!(provider_err.is_err());
424 match provider_err.unwrap_err() {
425 ProviderError::NetworkConfiguration(msg) => {
426 assert!(msg.contains("No active RPC configurations provided"));
427 }
428 _ => panic!("Unexpected error type"),
429 }
430 }
431
432 #[tokio::test]
433 async fn test_mock_basic_methods() {
434 let mut mock = MockStellarProviderTrait::new();
435
436 mock.expect_get_network()
437 .times(1)
438 .returning(|| async { Ok(dummy_get_network_response()) }.boxed());
439
440 mock.expect_get_latest_ledger()
441 .times(1)
442 .returning(|| async { Ok(dummy_get_latest_ledger_response()) }.boxed());
443
444 assert!(mock.get_network().await.is_ok());
445 assert!(mock.get_latest_ledger().await.is_ok());
446 }
447
448 #[tokio::test]
449 async fn test_mock_transaction_flow() {
450 let mut mock = MockStellarProviderTrait::new();
451
452 let envelope: TransactionEnvelope = dummy_transaction_envelope();
453 let hash = dummy_hash();
454
455 mock.expect_simulate_transaction_envelope()
456 .withf(|_| true)
457 .times(1)
458 .returning(|_| async { Ok(dummy_simulate()) }.boxed());
459
460 mock.expect_send_transaction()
461 .withf(|_| true)
462 .times(1)
463 .returning(|_| async { Ok(dummy_hash()) }.boxed());
464
465 mock.expect_send_transaction_polling()
466 .withf(|_| true)
467 .times(1)
468 .returning(|_| async { Ok(dummy_soroban_tx()) }.boxed());
469
470 mock.expect_get_transaction()
471 .withf(|_| true)
472 .times(1)
473 .returning(|_| async { Ok(dummy_get_transaction_response()) }.boxed());
474
475 mock.simulate_transaction_envelope(&envelope).await.unwrap();
476 mock.send_transaction(&envelope).await.unwrap();
477 mock.send_transaction_polling(&envelope).await.unwrap();
478 mock.get_transaction(&hash).await.unwrap();
479 }
480
481 #[tokio::test]
482 async fn test_mock_events_and_entries() {
483 let mut mock = MockStellarProviderTrait::new();
484
485 mock.expect_get_events()
486 .times(1)
487 .returning(|_| async { Ok(dummy_get_events_response()) }.boxed());
488
489 mock.expect_get_ledger_entries()
490 .times(1)
491 .returning(|_| async { Ok(dummy_get_ledger_entries_response()) }.boxed());
492
493 let events_request = GetEventsRequest {
494 start: EventStart::Ledger(1),
495 event_type: None,
496 contract_ids: vec![],
497 topics: vec![],
498 limit: Some(10),
499 };
500
501 let dummy_key: LedgerKey = dummy_ledger_key();
502 mock.get_events(events_request).await.unwrap();
503 mock.get_ledger_entries(&[dummy_key]).await.unwrap();
504 }
505
506 #[tokio::test]
507 async fn test_mock_all_methods_ok() {
508 let mut mock = MockStellarProviderTrait::new();
509
510 mock.expect_get_account()
511 .with(p::eq("GTESTACCOUNTID"))
512 .times(1)
513 .returning(|_| async { Ok(dummy_account_entry()) }.boxed());
514
515 mock.expect_simulate_transaction_envelope()
516 .times(1)
517 .returning(|_| async { Ok(dummy_simulate()) }.boxed());
518
519 mock.expect_send_transaction_polling()
520 .times(1)
521 .returning(|_| async { Ok(dummy_soroban_tx()) }.boxed());
522
523 mock.expect_get_network()
524 .times(1)
525 .returning(|| async { Ok(dummy_get_network_response()) }.boxed());
526
527 mock.expect_get_latest_ledger()
528 .times(1)
529 .returning(|| async { Ok(dummy_get_latest_ledger_response()) }.boxed());
530
531 mock.expect_send_transaction()
532 .times(1)
533 .returning(|_| async { Ok(dummy_hash()) }.boxed());
534
535 mock.expect_get_transaction()
536 .times(1)
537 .returning(|_| async { Ok(dummy_get_transaction_response()) }.boxed());
538
539 mock.expect_get_transactions()
540 .times(1)
541 .returning(|_| async { Ok(dummy_get_transactions_response()) }.boxed());
542
543 mock.expect_get_ledger_entries()
544 .times(1)
545 .returning(|_| async { Ok(dummy_get_ledger_entries_response()) }.boxed());
546
547 mock.expect_get_events()
548 .times(1)
549 .returning(|_| async { Ok(dummy_get_events_response()) }.boxed());
550
551 let _ = mock.get_account("GTESTACCOUNTID").await.unwrap();
552 let env: TransactionEnvelope = dummy_transaction_envelope();
553 mock.simulate_transaction_envelope(&env).await.unwrap();
554 mock.send_transaction_polling(&env).await.unwrap();
555 mock.get_network().await.unwrap();
556 mock.get_latest_ledger().await.unwrap();
557 mock.send_transaction(&env).await.unwrap();
558
559 let h = dummy_hash();
560 mock.get_transaction(&h).await.unwrap();
561
562 let req: GetTransactionsRequest = GetTransactionsRequest {
563 start_ledger: None,
564 pagination: None,
565 };
566 mock.get_transactions(req).await.unwrap();
567
568 let key: LedgerKey = dummy_ledger_key();
569 mock.get_ledger_entries(&[key]).await.unwrap();
570
571 let ev_req = GetEventsRequest {
572 start: EventStart::Ledger(0),
573 event_type: None,
574 contract_ids: vec![],
575 topics: vec![],
576 limit: None,
577 };
578 mock.get_events(ev_req).await.unwrap();
579 }
580
581 #[tokio::test]
582 async fn test_error_propagation() {
583 let mut mock = MockStellarProviderTrait::new();
584
585 mock.expect_get_account()
586 .returning(|_| async { Err(eyre!("boom")) }.boxed());
587
588 let res = mock.get_account("BAD").await;
589 assert!(res.is_err());
590 assert!(res.unwrap_err().to_string().contains("boom"));
591 }
592
593 #[tokio::test]
594 async fn test_get_events_edge_cases() {
595 let mut mock = MockStellarProviderTrait::new();
596
597 mock.expect_get_events()
598 .withf(|req| {
599 req.contract_ids.is_empty() && req.topics.is_empty() && req.limit.is_none()
600 })
601 .times(1)
602 .returning(|_| async { Ok(dummy_get_events_response()) }.boxed());
603
604 let ev_req = GetEventsRequest {
605 start: EventStart::Ledger(0),
606 event_type: None,
607 contract_ids: vec![],
608 topics: vec![],
609 limit: None,
610 };
611
612 mock.get_events(ev_req).await.unwrap();
613 }
614
615 #[test]
616 fn test_provider_send_sync_bounds() {
617 fn assert_send_sync<T: Send + Sync>() {}
618 assert_send_sync::<StellarProvider>();
619 }
620
621 #[cfg(test)]
622 mod concrete_tests {
623 use super::*;
624
625 const NON_EXISTENT_URL: &str = "http://127.0.0.1:9999";
626
627 fn setup_provider() -> StellarProvider {
628 StellarProvider::new(vec![RpcConfig::new(NON_EXISTENT_URL.to_string())], 0)
629 .expect("Provider creation should succeed even with bad URL")
630 }
631
632 #[tokio::test]
633 async fn test_concrete_get_account_error() {
634 let provider = setup_provider();
635 let result = provider.get_account("SOME_ACCOUNT_ID").await;
636 assert!(result.is_err());
637 assert!(result
638 .unwrap_err()
639 .to_string()
640 .contains("Failed to get account"));
641 }
642
643 #[tokio::test]
644 async fn test_concrete_simulate_transaction_envelope_error() {
645 let provider = setup_provider();
646 let envelope: TransactionEnvelope = dummy_transaction_envelope();
647 let result = provider.simulate_transaction_envelope(&envelope).await;
648 assert!(result.is_err());
649 assert!(result
650 .unwrap_err()
651 .to_string()
652 .contains("Failed to simulate transaction"));
653 }
654
655 #[tokio::test]
656 async fn test_concrete_send_transaction_polling_error() {
657 let provider = setup_provider();
658 let envelope: TransactionEnvelope = dummy_transaction_envelope();
659 let result = provider.send_transaction_polling(&envelope).await;
660 assert!(result.is_err());
661 assert!(result
662 .unwrap_err()
663 .to_string()
664 .contains("Failed to send transaction (polling)"));
665 }
666
667 #[tokio::test]
668 async fn test_concrete_get_network_error() {
669 let provider = setup_provider();
670 let result = provider.get_network().await;
671 assert!(result.is_err());
672 assert!(result
673 .unwrap_err()
674 .to_string()
675 .contains("Failed to get network"));
676 }
677
678 #[tokio::test]
679 async fn test_concrete_get_latest_ledger_error() {
680 let provider = setup_provider();
681 let result = provider.get_latest_ledger().await;
682 assert!(result.is_err());
683 assert!(result
684 .unwrap_err()
685 .to_string()
686 .contains("Failed to get latest ledger"));
687 }
688
689 #[tokio::test]
690 async fn test_concrete_send_transaction_error() {
691 let provider = setup_provider();
692 let envelope: TransactionEnvelope = dummy_transaction_envelope();
693 let result = provider.send_transaction(&envelope).await;
694 assert!(result.is_err());
695 assert!(result
696 .unwrap_err()
697 .to_string()
698 .contains("Failed to send transaction"));
699 }
700
701 #[tokio::test]
702 async fn test_concrete_get_transaction_error() {
703 let provider = setup_provider();
704 let hash: Hash = dummy_hash();
705 let result = provider.get_transaction(&hash).await;
706 assert!(result.is_err());
707 assert!(result
708 .unwrap_err()
709 .to_string()
710 .contains("Failed to get transaction"));
711 }
712
713 #[tokio::test]
714 async fn test_concrete_get_transactions_error() {
715 let provider = setup_provider();
716 let req = GetTransactionsRequest {
717 start_ledger: None,
718 pagination: None,
719 };
720 let result = provider.get_transactions(req).await;
721 assert!(result.is_err());
722 assert!(result
723 .unwrap_err()
724 .to_string()
725 .contains("Failed to get transactions"));
726 }
727
728 #[tokio::test]
729 async fn test_concrete_get_ledger_entries_error() {
730 let provider = setup_provider();
731 let key: LedgerKey = dummy_ledger_key();
732 let result = provider.get_ledger_entries(&[key]).await;
733 assert!(result.is_err());
734 assert!(result
735 .unwrap_err()
736 .to_string()
737 .contains("Failed to get ledger entries"));
738 }
739
740 #[tokio::test]
741 async fn test_concrete_get_events_error() {
742 let provider = setup_provider();
743 let req = GetEventsRequest {
744 start: EventStart::Ledger(1),
745 event_type: None,
746 contract_ids: vec![],
747 topics: vec![],
748 limit: None,
749 };
750 let result = provider.get_events(req).await;
751 assert!(result.is_err());
752 assert!(result
753 .unwrap_err()
754 .to_string()
755 .contains("Failed to get events"));
756 }
757 }
758}