#[cfg(test)]
mod test {
use zingo_test_vectors::seeds;
use crate::{
config::{ClientConfig, WalletConfig},
lightclient::{LightClient, error::LightClientError},
testutils::default_test_wallet_settings,
};
async fn create_offline_client() -> LightClient {
let config = ClientConfig::builder()
.set_wallet_config(WalletConfig::MnemonicPhrase {
mnemonic_phrase: seeds::HOSPITAL_MUSEUM_SEED.to_string(),
no_of_accounts: 1.try_into().unwrap(),
birthday: 419200,
wallet_settings: default_test_wallet_settings(),
})
.build()
.unwrap();
LightClient::new(config, true).await.unwrap()
}
#[tokio::test]
async fn offline_construction_succeeds_without_network() {
let _lc = create_offline_client().await;
}
#[tokio::test]
async fn offline_indexer_uri_is_none() {
let lc = create_offline_client().await;
assert!(lc.indexer_uri().is_none());
}
#[tokio::test]
async fn offline_balance_query_works() {
let lc = create_offline_client().await;
let result = lc.account_balance(zip32::AccountId::ZERO).await;
assert!(
result.is_ok(),
"expected Ok from offline balance query, got: {result:?}"
);
}
#[tokio::test]
async fn offline_address_query_works() {
let lc = create_offline_client().await;
let addrs = lc.unified_addresses_json().await;
assert!(!addrs.is_empty(), "expected at least one address");
}
#[tokio::test]
async fn offline_sync_returns_offline_error() {
let mut lc = create_offline_client().await;
assert!(matches!(lc.sync().await, Err(LightClientError::Offline)));
}
#[tokio::test]
async fn offline_rescan_returns_offline_error() {
let mut lc = create_offline_client().await;
assert!(matches!(lc.rescan().await, Err(LightClientError::Offline)));
}
#[tokio::test]
async fn offline_send_stored_proposal_returns_offline() {
let mut lc = create_offline_client().await;
assert!(matches!(
lc.send_stored_proposal(false).await,
Err(LightClientError::Offline)
));
}
#[tokio::test]
async fn offline_send_stored_proposal_preserves_proposal() {
use crate::mocks::proposal::ProposalBuilder;
let mut lc = create_offline_client().await;
let proposal = ProposalBuilder::default().build();
lc.wallet()
.write()
.await
.store_proposal(crate::data::proposal::ZingoProposal::Send {
proposal,
sending_account: zip32::AccountId::ZERO,
});
assert!(matches!(
lc.send_stored_proposal(false).await,
Err(LightClientError::Offline)
));
assert!(
lc.wallet().write().await.take_proposal().is_some(),
"proposal must survive an offline send attempt"
);
}
#[tokio::test]
async fn offline_calculate_signs_and_transmit_refuses() {
use crate::testutils::lightclient::from_inputs;
use crate::testutils::synthetic_wallet::SyntheticWalletBuilder;
use zingo_status::confirmation_status::ConfirmationStatus;
let wallet = SyntheticWalletBuilder::new(zingo_test_vectors::seeds::HOSPITAL_MUSEUM_SEED)
.orchard_note(100_000)
.build();
let mut client = LightClient::new_for_test(wallet).await;
from_inputs::propose(
&mut client,
vec![(zingo_test_vectors::EXT_TADDR, 10_000, None)],
)
.await
.unwrap();
let calculated_txids = client.calculate_stored_proposal().await.unwrap();
{
let wallet = client.wallet().read().await;
for txid in calculated_txids.iter() {
let transaction = wallet.wallet_transactions.get(txid).unwrap();
assert!(matches!(
transaction.status(),
ConfirmationStatus::Calculated(_)
));
assert_eq!(
u32::from(transaction.transaction().expiry_height()),
crate::lightclient::send::ZIP_203_EXPIRY_HEIGHT_THRESHOLD - 1,
"an Indexerless calculation must retarget expiry to the \
top of the wallet's consensus-branch epoch"
);
}
}
assert!(matches!(
client.transmit_calculated(calculated_txids).await,
Err(LightClientError::Offline)
));
}
#[tokio::test]
async fn offline_create_account_works() {
let mut lc = create_offline_client().await;
lc.create_account().await.unwrap();
}
#[tokio::test]
async fn offline_recovery_info_returns_seed() {
let lc = create_offline_client().await;
let info = lc
.recovery_info()
.await
.expect("spending wallet has recovery info");
assert_eq!(info.seed_phrase, seeds::HOSPITAL_MUSEUM_SEED);
}
#[tokio::test]
async fn offline_clear_proposal_does_not_panic() {
let mut lc = create_offline_client().await;
lc.clear_proposal().await; }
#[tokio::test]
async fn file_pointer_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let initial_addrs = {
let config = ClientConfig::builder()
.set_wallet_dir(dir.path().to_path_buf())
.set_wallet_config(WalletConfig::MnemonicPhrase {
mnemonic_phrase: seeds::HOSPITAL_MUSEUM_SEED.to_string(),
no_of_accounts: 1.try_into().unwrap(),
birthday: 419200,
wallet_settings: default_test_wallet_settings(),
})
.build()
.unwrap();
let mut lc = LightClient::new(config, true).await.unwrap();
let addrs = lc.unified_addresses_json().await;
lc.flush().await.unwrap();
addrs
};
let loaded_addrs = {
let config = ClientConfig::builder()
.set_wallet_dir(dir.path().to_path_buf())
.set_wallet_config(WalletConfig::Read)
.build()
.unwrap();
let lc = LightClient::new(config, false).await.unwrap();
assert!(lc.indexer_uri().is_none(), "reload should be offline");
assert_eq!(
lc.chain_type(),
crate::config::ChainType::Mainnet,
"chain type must survive round-trip"
);
assert_eq!(lc.birthday(), 419200, "birthday must survive round-trip");
assert_eq!(
lc.mnemonic_phrase().as_deref(),
Some(seeds::HOSPITAL_MUSEUM_SEED),
"mnemonic must survive round-trip"
);
lc.unified_addresses_json().await
};
assert_eq!(
initial_addrs, loaded_addrs,
"addresses must survive save/load round-trip"
);
}
#[tokio::test]
async fn file_pointer_missing_file_returns_error() {
let dir = tempfile::tempdir().unwrap();
let config = ClientConfig::builder()
.set_wallet_dir(dir.path().to_path_buf())
.set_wallet_config(WalletConfig::Read)
.build()
.unwrap();
let result = LightClient::new(config, false).await;
assert!(
matches!(result, Err(LightClientError::FileError(_))),
"expected FileError for missing wallet, got: {result:?}"
);
}
}