mod common;
use std::io::Write;
use anyhow::Result;
use solana_sdk::signature::Keypair;
use solana_sdk::signer::Signer;
use common::{assert_success, decode_onchain_metadata, mint_test_nft, trim_null, TestContext};
#[test]
#[ignore = "requires solana-test-validator (run with --ignored)"]
fn test_update_symbol() -> Result<()> {
let mut ctx = TestContext::new()?;
let temp_dir = ctx.create_temp_dir("update-symbol");
let mint = mint_test_nft(&ctx, &temp_dir)?;
let metadata = decode_onchain_metadata(&ctx, &mint)?;
assert_eq!(trim_null(&metadata.symbol), "TNFT");
let output = ctx.run_metaboss(&[
"update",
"symbol",
"-k",
&ctx.keypair_path,
"-a",
&mint,
"--new-symbol",
"NEWT",
]);
assert_success(&output);
let metadata = decode_onchain_metadata(&ctx, &mint)?;
assert_eq!(
trim_null(&metadata.symbol),
"NEWT",
"symbol should be updated to NEWT"
);
assert_eq!(trim_null(&metadata.name), "Test NFT");
assert_eq!(metadata.seller_fee_basis_points, 100);
Ok(())
}
#[test]
#[ignore = "requires solana-test-validator (run with --ignored)"]
fn test_update_name() -> Result<()> {
let mut ctx = TestContext::new()?;
let temp_dir = ctx.create_temp_dir("update-name");
let mint = mint_test_nft(&ctx, &temp_dir)?;
let metadata = decode_onchain_metadata(&ctx, &mint)?;
assert_eq!(trim_null(&metadata.name), "Test NFT");
let output = ctx.run_metaboss(&[
"update",
"name",
"-k",
&ctx.keypair_path,
"-a",
&mint,
"--new-name",
"Updated Name",
]);
assert_success(&output);
let metadata = decode_onchain_metadata(&ctx, &mint)?;
assert_eq!(
trim_null(&metadata.name),
"Updated Name",
"name should be updated to 'Updated Name'"
);
assert_eq!(trim_null(&metadata.symbol), "TNFT");
assert_eq!(metadata.seller_fee_basis_points, 100);
Ok(())
}
#[test]
#[ignore = "requires solana-test-validator (run with --ignored)"]
fn test_update_seller_fee_basis_points() -> Result<()> {
let mut ctx = TestContext::new()?;
let temp_dir = ctx.create_temp_dir("update-sfbp");
let mint = mint_test_nft(&ctx, &temp_dir)?;
let metadata = decode_onchain_metadata(&ctx, &mint)?;
assert_eq!(metadata.seller_fee_basis_points, 100);
let output = ctx.run_metaboss(&[
"update",
"sfbp",
"-k",
&ctx.keypair_path,
"-a",
&mint,
"--new-sfbp",
"500",
]);
assert_success(&output);
let metadata = decode_onchain_metadata(&ctx, &mint)?;
assert_eq!(
metadata.seller_fee_basis_points, 500,
"seller_fee_basis_points should be updated to 500"
);
assert_eq!(trim_null(&metadata.name), "Test NFT");
assert_eq!(trim_null(&metadata.symbol), "TNFT");
Ok(())
}
#[test]
#[ignore = "requires solana-test-validator (run with --ignored)"]
fn test_update_creators() -> Result<()> {
let mut ctx = TestContext::new()?;
let temp_dir = ctx.create_temp_dir("update-creators");
let mint = mint_test_nft(&ctx, &temp_dir)?;
let new_creator = Keypair::new();
let new_creator_pubkey = new_creator.pubkey().to_string();
let new_creators_arg = format!("{}:100:false", new_creator_pubkey);
let output = ctx.run_metaboss(&[
"update",
"creators",
"-k",
&ctx.keypair_path,
"-a",
&mint,
"--new-creators",
&new_creators_arg,
]);
assert_success(&output);
let metadata = decode_onchain_metadata(&ctx, &mint)?;
let creators = metadata
.creators
.expect("creators should be present after update");
assert_eq!(creators.len(), 1, "should have exactly one creator");
assert_eq!(
creators[0].address.to_string(),
new_creator_pubkey,
"creator address should match the new creator"
);
assert_eq!(creators[0].share, 100, "creator share should be 100");
assert!(!creators[0].verified, "creator should be unverified");
assert_eq!(trim_null(&metadata.name), "Test NFT");
assert_eq!(trim_null(&metadata.symbol), "TNFT");
assert_eq!(metadata.seller_fee_basis_points, 100);
Ok(())
}
#[test]
#[ignore = "requires solana-test-validator (run with --ignored)"]
fn test_update_data() -> Result<()> {
let mut ctx = TestContext::new()?;
let temp_dir = ctx.create_temp_dir("update-data");
let mint = mint_test_nft(&ctx, &temp_dir)?;
let new_creator = Keypair::new();
let new_creator_pubkey = new_creator.pubkey().to_string();
let new_data = serde_json::json!({
"name": "Updated NFT",
"symbol": "UPDT",
"uri": "https://example.com/updated-metadata.json",
"seller_fee_basis_points": 250,
"creators": [
{
"address": new_creator_pubkey,
"verified": false,
"share": 100
}
]
});
let data_file = temp_dir.join("new_data.json");
let mut file = std::fs::File::create(&data_file)?;
file.write_all(serde_json::to_string_pretty(&new_data)?.as_bytes())?;
let data_file_str = data_file.to_string_lossy().to_string();
let output = ctx.run_metaboss(&[
"update",
"data",
"-k",
&ctx.keypair_path,
"-a",
&mint,
"--new-data-file",
&data_file_str,
]);
assert_success(&output);
let metadata = decode_onchain_metadata(&ctx, &mint)?;
assert_eq!(
trim_null(&metadata.name),
"Updated NFT",
"name should be updated"
);
assert_eq!(
trim_null(&metadata.symbol),
"UPDT",
"symbol should be updated"
);
assert_eq!(
trim_null(&metadata.uri),
"https://example.com/updated-metadata.json",
"uri should be updated"
);
assert_eq!(
metadata.seller_fee_basis_points, 250,
"seller_fee_basis_points should be updated to 250"
);
let creators = metadata
.creators
.expect("creators should be present after update");
assert_eq!(creators.len(), 1, "should have exactly one creator");
assert_eq!(
creators[0].address.to_string(),
new_creator_pubkey,
"creator address should match the new creator"
);
assert_eq!(creators[0].share, 100);
assert!(!creators[0].verified);
Ok(())
}
#[test]
#[ignore = "requires solana-test-validator (run with --ignored)"]
fn test_update_creators_all_append() -> Result<()> {
let mut ctx = TestContext::new()?;
let temp_dir = ctx.create_temp_dir("update-creators-all-append");
let mint = mint_test_nft(&ctx, &temp_dir)?;
let mint_list_file = temp_dir.join("append_test_mints.json");
let mut file = std::fs::File::create(&mint_list_file)?;
file.write_all(serde_json::to_string(&vec![&mint])?.as_bytes())?;
let mint_list_file_str = mint_list_file.to_string_lossy().to_string();
let new_creator = Keypair::new();
let new_creator_pubkey = new_creator.pubkey().to_string();
let new_creators_arg = format!("{}:0:false", new_creator_pubkey);
let output = ctx.run_metaboss(&[
"update",
"creators-all",
"-k",
&ctx.keypair_path,
"-L",
&mint_list_file_str,
"--new-creators",
&new_creators_arg,
"--append",
]);
assert_success(&output);
let metadata = decode_onchain_metadata(&ctx, &mint)?;
let creators = metadata
.creators
.expect("creators should be present after update");
assert_eq!(creators.len(), 2, "--append works, both creators present");
Ok(())
}
#[test]
#[ignore = "requires solana-test-validator (run with --ignored)"]
fn test_uri_all_resume_missing_mint_fails_gracefully() -> Result<()> {
let mut ctx = TestContext::new()?;
let temp_dir = ctx.create_temp_dir("uri-all-resume-panic");
let mint = mint_test_nft(&ctx, &temp_dir)?;
let cache_file = temp_dir.join("resume_cache.json");
let cache_data = serde_json::json!({
mint: {
"error": "Simulated failure"
}
});
std::fs::write(&cache_file, serde_json::to_string(&cache_data)?)?;
let cache_file_str = cache_file.to_string_lossy().to_string();
let new_uris_file = temp_dir.join("new_uris.json");
let new_uris_data: Vec<serde_json::Value> = vec![];
std::fs::write(&new_uris_file, serde_json::to_string(&new_uris_data)?)?;
let new_uris_file_str = new_uris_file.to_string_lossy().to_string();
let output = ctx.run_metaboss(&[
"update",
"uri-all",
"-k",
&ctx.keypair_path,
"--new-uris-file",
&new_uris_file_str,
"--cache-file",
&cache_file_str,
]);
assert!(
!output.success,
"Command should fail when mint is missing from input list"
);
assert!(
!output.stderr.contains("panicked") && !output.stderr.contains("unwrap"),
"Process panicked in stderr: {}",
output.stderr
);
assert!(
output
.stderr
.contains("mint found in cache but missing from input list"),
"Missing expected error message in stderr: {}",
output.stderr
);
Ok(())
}