@@ -518,3 +518,215 @@ fn sign_transaction_xdr(transaction_xdr: &str, secret_key: &str, network: &str)
518518 use base64:: { engine:: general_purpose, Engine as _} ;
519519 Ok ( general_purpose:: STANDARD . encode ( signed_mock) )
520520}
521+
522+ #[ cfg( test) ]
523+ mod tests {
524+ use super :: * ;
525+ use crate :: utils:: config:: { self , Config , NetworkConfig } ;
526+ use mockito:: { Matcher , Server } ;
527+ use std:: collections:: HashMap ;
528+ use tempfile:: TempDir ;
529+
530+ struct TestConfigGuard {
531+ _temp_dir : TempDir ,
532+ original_home : Option < String > ,
533+ }
534+
535+ impl TestConfigGuard {
536+ fn new ( horizon_url : & str , friendbot_url : Option < String > ) -> Self {
537+ let temp_dir = tempfile:: tempdir ( ) . expect ( "temp dir" ) ;
538+ let original_home = std:: env:: var ( "HOME" ) . ok ( ) ;
539+
540+ unsafe {
541+ std:: env:: set_var ( "HOME" , temp_dir. path ( ) ) ;
542+ }
543+
544+ let mut networks = HashMap :: new ( ) ;
545+ networks. insert (
546+ "mocknet" . to_string ( ) ,
547+ NetworkConfig {
548+ horizon_url : horizon_url. to_string ( ) ,
549+ soroban_rpc_url : None ,
550+ friendbot_url,
551+ passphrase : Some ( "Test SDF Network ; September 2015" . to_string ( ) ) ,
552+ } ,
553+ ) ;
554+
555+ config:: save ( & Config {
556+ network : "mocknet" . to_string ( ) ,
557+ version : "1" . to_string ( ) ,
558+ networks,
559+ wallets : HashMap :: new ( ) ,
560+ identities : HashMap :: new ( ) ,
561+ default_identity : None ,
562+ } )
563+ . expect ( "save config" ) ;
564+
565+ Self {
566+ _temp_dir : temp_dir,
567+ original_home,
568+ }
569+ }
570+ }
571+
572+ impl Drop for TestConfigGuard {
573+ fn drop ( & mut self ) {
574+ if let Some ( home) = & self . original_home {
575+ unsafe {
576+ std:: env:: set_var ( "HOME" , home) ;
577+ }
578+ } else {
579+ unsafe {
580+ std:: env:: remove_var ( "HOME" ) ;
581+ }
582+ }
583+ }
584+ }
585+
586+ #[ test]
587+ fn fetch_account_returns_mocked_account ( ) {
588+ let mut server = Server :: new ( ) ;
589+ let _guard = TestConfigGuard :: new ( & server. url ( ) , None ) ;
590+ let public_key = "GACCOUNT123" ;
591+
592+ let _mock = server
593+ . mock ( "GET" , format ! ( "/accounts/{public_key}" ) . as_str ( ) )
594+ . with_status ( 200 )
595+ . with_header ( "content-type" , "application/json" )
596+ . with_body (
597+ r#"{
598+ "id":"GACCOUNT123",
599+ "sequence":"123456789",
600+ "balances":[{"balance":"42.0000000","asset_type":"native","asset_code":null}],
601+ "subentry_count":1
602+ }"# ,
603+ )
604+ . create ( ) ;
605+
606+ let account = fetch_account ( public_key, "mocknet" ) . expect ( "account" ) ;
607+ assert_eq ! ( account. sequence, "123456789" ) ;
608+ assert_eq ! ( account. balances. len( ) , 1 ) ;
609+ assert_eq ! ( account. balances[ 0 ] . asset_type, "native" ) ;
610+ }
611+
612+ #[ test]
613+ fn fetch_account_reports_parse_error_for_invalid_json ( ) {
614+ let mut server = Server :: new ( ) ;
615+ let _guard = TestConfigGuard :: new ( & server. url ( ) , None ) ;
616+
617+ let _mock = server
618+ . mock ( "GET" , "/accounts/GACCOUNT123" )
619+ . with_status ( 200 )
620+ . with_header ( "content-type" , "application/json" )
621+ . with_body ( "{\" sequence\" :" )
622+ . create ( ) ;
623+
624+ let err = fetch_account ( "GACCOUNT123" , "mocknet" ) . unwrap_err ( ) ;
625+ assert ! ( err. to_string( ) . contains( "Failed to parse account response" ) ) ;
626+ }
627+
628+ #[ test]
629+ fn fund_account_reports_friendbot_error_path ( ) {
630+ let mut server = Server :: new ( ) ;
631+ let _guard = TestConfigGuard :: new ( & server. url ( ) , Some ( server. url ( ) ) ) ;
632+
633+ let _mock = server
634+ . mock ( "GET" , "/" )
635+ . match_query ( Matcher :: UrlEncoded ( "addr" . into ( ) , "GACCOUNT123" . into ( ) ) )
636+ . with_status ( 500 )
637+ . create ( ) ;
638+
639+ let err = fund_account ( "GACCOUNT123" , "mocknet" ) . unwrap_err ( ) ;
640+ assert ! ( err. to_string( ) . contains( "Friendbot returned status 500" ) ) ;
641+ }
642+
643+ #[ test]
644+ fn build_transaction_query_url_includes_pagination_params ( ) {
645+ let mut server = Server :: new ( ) ;
646+ let _guard = TestConfigGuard :: new ( & server. url ( ) , None ) ;
647+
648+ let filter = TxFilter {
649+ limit : 250 ,
650+ cursor : Some ( "cursor-123" . to_string ( ) ) ,
651+ order : Some ( "asc" . to_string ( ) ) ,
652+ type_filter : Some ( "payment" . to_string ( ) ) ,
653+ after : None ,
654+ before : None ,
655+ successful_only : None ,
656+ } ;
657+
658+ let url = build_transaction_query_url ( "GACCOUNT123" , "mocknet" , & filter) . expect ( "url" ) ;
659+ assert ! ( url. contains( "/accounts/GACCOUNT123/transactions?order=asc&limit=200" ) ) ;
660+ assert ! ( url. contains( "&cursor=cursor-123" ) ) ;
661+ assert ! ( url. contains( "&type=payment" ) ) ;
662+ }
663+
664+ #[ test]
665+ fn fetch_transactions_filtered_uses_cursor_and_filters_records ( ) {
666+ let mut server = Server :: new ( ) ;
667+ let _guard = TestConfigGuard :: new ( & server. url ( ) , None ) ;
668+
669+ let _mock = server
670+ . mock ( "GET" , "/accounts/GACCOUNT123/transactions" )
671+ . match_query ( Matcher :: AllOf ( vec ! [
672+ Matcher :: UrlEncoded ( "order" . into( ) , "asc" . into( ) ) ,
673+ Matcher :: UrlEncoded ( "limit" . into( ) , "2" . into( ) ) ,
674+ Matcher :: UrlEncoded ( "cursor" . into( ) , "cursor-2" . into( ) ) ,
675+ Matcher :: UrlEncoded ( "type" . into( ) , "payment" . into( ) ) ,
676+ ] ) )
677+ . with_status ( 200 )
678+ . with_header ( "content-type" , "application/json" )
679+ . with_body (
680+ r#"{
681+ "_embedded": {
682+ "records": [
683+ {
684+ "hash":"tx-1",
685+ "successful":true,
686+ "operation_count":1,
687+ "fee_charged":"100",
688+ "created_at":"2024-01-01T00:00:00Z",
689+ "memo_type":"text",
690+ "memo":"ok",
691+ "source_account":"GACCOUNT123",
692+ "type":"payment",
693+ "paging_token":"cursor-1"
694+ },
695+ {
696+ "hash":"tx-2",
697+ "successful":false,
698+ "operation_count":1,
699+ "fee_charged":"100",
700+ "created_at":"2024-01-02T00:00:00Z",
701+ "memo_type":null,
702+ "memo":null,
703+ "source_account":"GACCOUNT123",
704+ "type":"payment",
705+ "paging_token":"cursor-2"
706+ }
707+ ]
708+ }
709+ }"# ,
710+ )
711+ . create ( ) ;
712+
713+ let records = fetch_transactions_filtered (
714+ "GACCOUNT123" ,
715+ "mocknet" ,
716+ TxFilter {
717+ limit : 2 ,
718+ cursor : Some ( "cursor-2" . to_string ( ) ) ,
719+ order : Some ( "asc" . to_string ( ) ) ,
720+ type_filter : Some ( "payment" . to_string ( ) ) ,
721+ after : None ,
722+ before : None ,
723+ successful_only : Some ( true ) ,
724+ } ,
725+ )
726+ . expect ( "records" ) ;
727+
728+ assert_eq ! ( records. len( ) , 1 ) ;
729+ assert_eq ! ( records[ 0 ] . hash, "tx-1" ) ;
730+ assert_eq ! ( records[ 0 ] . paging_token. as_deref( ) , Some ( "cursor-1" ) ) ;
731+ }
732+ }
0 commit comments