v0.2.5-pre.006
This commit is contained in:
130
crates/ksp-wallet-lib/unit_tests/persistence.rs
Normal file
130
crates/ksp-wallet-lib/unit_tests/persistence.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
// file: crates/ksp-wallet-lib/unit_tests/persistence.rs
|
||||
// version: 1
|
||||
|
||||
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::persistence::persist_new_wallet_for_test(destination.as_path(), first).expect("first no-clobber publication must succeed");
|
||||
let second_result = crate::persistence::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::persistence::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::persistence::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("pre006-owner-password").expect("test OWNER password must be valid");
|
||||
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("pre006-other-owner-password").expect("second test OWNER password must be valid");
|
||||
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::persistence::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("pre005-view-password").expect("test VIEW password must be valid");
|
||||
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("pre005-owner-password").expect("test OWNER password must be valid");
|
||||
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"));
|
||||
}
|
||||
Reference in New Issue
Block a user