// file: crates/ksp-wallet-lib/unit_tests/persistence.rs // version: 4 fn temp_directory() -> std::io::Result { 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"); }