use std::str::FromStr;
use anyhow::{Context, Result};
use base64::Engine;
use solana_sdk::{account::Account, pubkey::Pubkey};
#[derive(Debug, Clone, serde::Deserialize)]
pub struct AccountFixture {
pub address: String,
pub data: String,
#[serde(default = "default_encoding")]
pub encoding: String,
pub lamports: u64,
pub owner: String,
#[serde(default)]
pub executable: bool,
#[serde(default)]
pub rent_epoch: u64,
}
fn default_encoding() -> String {
"base64".to_string()
}
pub fn load_account_fixture(json_bytes: &[u8]) -> Result<(Pubkey, Account)> {
let fixture: AccountFixture =
serde_json::from_slice(json_bytes).context("Failed to parse account fixture JSON")?;
let address = Pubkey::from_str(&fixture.address)
.map_err(|e| anyhow::anyhow!("Invalid account address '{}': {}", fixture.address, e))?;
let owner = Pubkey::from_str(&fixture.owner)
.map_err(|e| anyhow::anyhow!("Invalid owner address '{}': {}", fixture.owner, e))?;
let data = decode_data(&fixture.data, &fixture.encoding)?;
Ok((
address,
Account {
lamports: fixture.lamports,
data,
owner,
executable: fixture.executable,
rent_epoch: fixture.rent_epoch,
},
))
}
pub fn load_account_fixtures(json_bytes: &[u8]) -> Result<Vec<(Pubkey, Account)>> {
let fixtures: Vec<AccountFixture> = serde_json::from_slice(json_bytes)
.context("Failed to parse account fixtures JSON array")?;
fixtures
.into_iter()
.map(|fixture| {
let address = Pubkey::from_str(&fixture.address).map_err(|e| {
anyhow::anyhow!("Invalid account address '{}': {}", fixture.address, e)
})?;
let owner = Pubkey::from_str(&fixture.owner)
.map_err(|e| anyhow::anyhow!("Invalid owner address '{}': {}", fixture.owner, e))?;
let data = decode_data(&fixture.data, &fixture.encoding)?;
Ok((
address,
Account {
lamports: fixture.lamports,
data,
owner,
executable: fixture.executable,
rent_epoch: fixture.rent_epoch,
},
))
})
.collect()
}
fn decode_data(data_str: &str, encoding: &str) -> Result<Vec<u8>> {
match encoding {
"base64" => base64::engine::general_purpose::STANDARD
.decode(data_str)
.context("Failed to decode base64 account data"),
"hex" => hex::decode(data_str).context("Failed to decode hex account data"),
other => Err(anyhow::anyhow!("Unsupported encoding: '{}'. Use 'base64' or 'hex'.", other)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_load_account_fixture_base64() {
let json = serde_json::json!({
"address": "11111111111111111111111111111111",
"data": base64::engine::general_purpose::STANDARD.encode([1, 2, 3, 4]),
"encoding": "base64",
"lamports": 1000,
"owner": "11111111111111111111111111111111",
"executable": false,
"rent_epoch": 0
});
let bytes = serde_json::to_vec(&json).unwrap();
let (addr, account) = load_account_fixture(&bytes).unwrap();
assert_eq!(addr, Pubkey::from_str("11111111111111111111111111111111").unwrap());
assert_eq!(account.data, vec![1, 2, 3, 4]);
assert_eq!(account.lamports, 1000);
}
#[test]
fn test_load_account_fixture_hex() {
let json = serde_json::json!({
"address": "11111111111111111111111111111111",
"data": "01020304",
"encoding": "hex",
"lamports": 2000,
"owner": "11111111111111111111111111111111",
"executable": false,
"rent_epoch": 0
});
let bytes = serde_json::to_vec(&json).unwrap();
let (_, account) = load_account_fixture(&bytes).unwrap();
assert_eq!(account.data, vec![1, 2, 3, 4]);
assert_eq!(account.lamports, 2000);
}
#[test]
fn test_load_account_fixtures_array() {
let json = serde_json::json!([
{
"address": "11111111111111111111111111111111",
"data": base64::engine::general_purpose::STANDARD.encode([1, 2]),
"encoding": "base64",
"lamports": 100,
"owner": "11111111111111111111111111111111",
"executable": false,
"rent_epoch": 0
},
{
"address": "11111111111111111111111111111111",
"data": "0506",
"encoding": "hex",
"lamports": 200,
"owner": "11111111111111111111111111111111",
"executable": false,
"rent_epoch": 0
}
]);
let bytes = serde_json::to_vec(&json).unwrap();
let accounts = load_account_fixtures(&bytes).unwrap();
assert_eq!(accounts.len(), 2);
assert_eq!(accounts[0].1.data, vec![1, 2]);
assert_eq!(accounts[1].1.data, vec![5, 6]);
}
}