Skip to content

Commit b02c8bd

Browse files
authored
Merge pull request #238 from Trovic1/issue-36-horizon-mockito-tests
test: add mockito coverage for Horizon client
2 parents f73e57b + 8573652 commit b02c8bd

1 file changed

Lines changed: 212 additions & 0 deletions

File tree

src/utils/horizon.rs

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)