Files
khadhroony-solana-project/crates/ksp-wallet-lib/unit_tests/persistence.rs

146 lines
8.5 KiB
Rust

// file: crates/ksp-wallet-lib/unit_tests/persistence.rs
// version: 4
fn temp_directory() -> std::io::Result<tempfile::TempDir> {
return tempfile::tempdir();
}
#[test]
fn no_clobber_create_keeps_the_first_published_document() {
let directory = temp_directory().expect("Wallet persistence test directory must be creatable");
let destination = directory.path().join("wallet.kspwallet");
let first = b"first-wallet";
let second = b"second-wallet";
crate::persist_new_wallet_for_test(destination.as_path(), first).expect("first no-clobber publication must succeed");
let second_result = crate::persist_new_wallet_for_test(destination.as_path(), second);
let error = second_result.expect_err("second publication must not overwrite an existing wallet");
assert_eq!(error.code(), crate::ERROR_CODE_DESTINATION_EXISTS);
let persisted = std::fs::read(destination.as_path()).expect("published wallet must remain readable");
assert_eq!(persisted, first);
}
#[test]
fn injected_failure_before_publish_leaves_no_destination_or_partial_wallet() {
let directory = temp_directory().expect("Wallet persistence test directory must be creatable");
let destination = directory.path().join("wallet.kspwallet");
let result = crate::persist_new_wallet_fault_before_publish(destination.as_path(), b"candidate-wallet");
let error = result.expect_err("fault injection must abort before publication");
assert_eq!(error.code(), crate::ERROR_CODE_ATOMIC_PERSISTENCE_FAILED);
assert!(!destination.exists());
let entries = std::fs::read_dir(directory.path()).expect("Wallet persistence test directory must remain readable");
let count = entries.count();
assert_eq!(count, 0, "temporary artifacts should be cleaned on ordinary error unwinding");
}
#[test]
fn concurrent_no_clobber_publish_has_exactly_one_winner() {
let directory = temp_directory().expect("Wallet persistence test directory must be creatable");
let destination = directory.path().join("wallet.kspwallet");
let barrier = std::sync::Arc::new(std::sync::Barrier::new(8));
let mut handles = std::vec::Vec::new();
for index in 0_u8..8 {
let destination = destination.clone();
let barrier = std::sync::Arc::clone(&barrier);
handles.push(std::thread::spawn(move || {
barrier.wait();
let content = [index; 32];
return crate::persist_new_wallet_for_test(destination.as_path(), content.as_slice());
}));
}
let mut success_count = 0_usize;
let mut exists_count = 0_usize;
let mut unexpected_code = std::option::Option::None;
for handle in handles {
let result = handle.join().expect("Wallet persistence test worker must not panic");
match result {
std::result::Result::Ok(()) => success_count += 1,
std::result::Result::Err(error) if error.code() == crate::ERROR_CODE_DESTINATION_EXISTS => exists_count += 1,
std::result::Result::Err(error) => unexpected_code = std::option::Option::Some(error.code()),
}
}
assert_eq!(unexpected_code, std::option::Option::None);
assert_eq!(success_count, 1);
assert_eq!(exists_count, 7);
assert_eq!(std::fs::metadata(destination.as_path()).expect("winning wallet must exist").len(), 32);
}
#[test]
fn bounded_reader_rejects_oversized_wallet_before_parser_allocation() {
let directory = temp_directory().expect("Wallet persistence test directory must be creatable");
let destination = directory.path().join("oversized.kspwallet");
let oversized = std::vec![b'x'; crate::KSPWALLET_MAX_FILE_BYTES + 1];
std::fs::write(destination.as_path(), oversized).expect("oversized fixture must be writable");
let runtime = tokio::runtime::Builder::new_current_thread().build().expect("Wallet persistence test runtime must build");
let result = runtime.block_on(crate::inspect_locked_wallet_file_v1(destination.as_path()));
let error = result.expect_err("oversized Wallet file must be rejected before parsing");
assert_eq!(error.code(), crate::ERROR_CODE_FORMAT_INVALID);
}
#[test]
fn public_file_create_and_locked_inspect_round_trip_without_revealing_identity() {
let directory = temp_directory().expect("Wallet persistence test directory must be creatable");
let destination = directory.path().join("created.kspwallet");
let runtime = tokio::runtime::Builder::new_current_thread().build().expect("Wallet persistence test runtime must build");
let owner_password = crate::OwnerPassword::new(std::string::String::from("pre006-owner-password"));
let created = runtime
.block_on(crate::create_wallet_file_v1(destination.as_path(), owner_password, std::option::Option::None, crate::WalletCreateMetadataV1::default()))
.expect("native Wallet file creation must succeed");
let locked = runtime.block_on(crate::inspect_locked_wallet_file_v1(destination.as_path())).expect("persisted Wallet must inspect successfully");
assert!(!locked.view_enabled());
assert_eq!(created.capability(), crate::WalletCapability::Owner);
assert!(destination.exists());
let second_password = crate::OwnerPassword::new(std::string::String::from("pre006-other-owner-password"));
let second = runtime.block_on(crate::create_wallet_file_v1(
destination.as_path(),
second_password,
std::option::Option::None,
crate::WalletCreateMetadataV1::default(),
));
assert_eq!(second.expect_err("create must remain no-clobber").code(), crate::ERROR_CODE_DESTINATION_EXISTS);
}
#[test]
fn persisted_full_vector_opens_view_and_owner_through_file_apis() {
let directory = temp_directory().expect("Wallet persistence test directory must be creatable");
let destination = directory.path().join("vector.kspwallet");
let vector = include_bytes!("../tests/fixtures/kspwallet_v1_full_vector.json");
crate::persist_new_wallet_for_test(destination.as_path(), vector).expect("full vector must publish through no-clobber persistence");
let runtime = tokio::runtime::Builder::new_current_thread().build().expect("Wallet persistence test runtime must build");
let view_password = crate::ViewPassword::new(std::string::String::from("pre005-view-password"));
let view = runtime
.block_on(crate::open_wallet_view_file_v1(destination.as_path(), view_password))
.expect("persisted full vector must open through VIEW file API");
let owner_password = crate::OwnerPassword::new(std::string::String::from("pre005-owner-password"));
let owner = runtime
.block_on(crate::open_wallet_owner_file_v1(destination.as_path(), owner_password))
.expect("persisted full vector must open through OWNER file API");
assert_eq!(view.capability(), crate::WalletCapability::View);
assert_eq!(owner.capability(), crate::WalletCapability::Owner);
assert_eq!(view.pubkey(), owner.pubkey());
assert_eq!(view.alias(), std::option::Option::Some("pre005-vector-wallet"));
assert_eq!(owner.alias(), std::option::Option::Some("pre005-vector-wallet"));
}
#[test]
fn replacement_fault_before_publish_preserves_the_previous_wallet_bytes() {
let directory = temp_directory().expect("Wallet replacement test directory must be creatable");
let destination = directory.path().join("replace-fault.kspwallet");
let original = b"ORIGINAL-WALLET-CANARY";
let replacement = b"REPLACEMENT-WALLET-CANARY";
std::fs::write(destination.as_path(), original).expect("replacement test original must be writable");
let result = crate::replace_wallet_fault_before_publish(destination.as_path(), replacement);
assert_eq!(result.expect_err("injected replacement fault must fail").code(), crate::ERROR_CODE_ATOMIC_PERSISTENCE_FAILED);
assert_eq!(std::fs::read(destination.as_path()).expect("original Wallet bytes must remain readable"), original);
}
#[test]
fn replacement_requires_an_existing_regular_file_and_replaces_complete_content() {
let directory = temp_directory().expect("Wallet replacement test directory must be creatable");
let destination = directory.path().join("replace.kspwallet");
let missing = crate::replace_wallet_for_test(destination.as_path(), b"replacement");
assert_eq!(missing.expect_err("replacement must not create a missing Wallet destination").code(), crate::ERROR_CODE_IO_FAILED);
std::fs::write(destination.as_path(), b"old").expect("replacement test original must be writable");
crate::replace_wallet_for_test(destination.as_path(), b"new-complete-wallet").expect("existing Wallet replacement must succeed");
assert_eq!(std::fs::read(destination.as_path()).expect("replaced Wallet must be readable"), b"new-complete-wallet");
}