// file: crates/ksp-wallet-lib/unit_tests/wallet_v2.rs // version: 1 const V1_FULL_VECTOR: &[u8] = include_bytes!("../tests/fixtures/kspwallet_v1_full_vector.json"); fn runtime() -> tokio::runtime::Runtime { return tokio::runtime::Builder::new_current_thread().build().expect("Wallet V2 test runtime must build"); } #[test] fn v2_create_open_and_generic_dispatch_preserve_authorized_identity() { let runtime = runtime(); let owner_password_text = std::string::String::from("pre016-v2-owner-password"); let view_password_text = std::string::String::from("pre016-v2-view-password"); let owner = runtime .block_on(crate::create_wallet_v2( crate::OwnerPassword::new(owner_password_text.clone()), std::option::Option::Some(crate::ViewPassword::new(view_password_text.clone())), crate::WalletCreateMetadataV1::new(std::option::Option::Some(std::string::String::from("pre016-v2")), std::vec::Vec::new()), )) .expect("V2 wallet creation must succeed"); assert_eq!(owner.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2); let pubkey = *owner.pubkey(); let bytes = owner.to_native_bytes().expect("V2 owner must serialize native bytes"); assert_eq!(crate::detect_wallet_format(bytes.as_slice()).expect("V2 framing must detect"), crate::WalletFormat::V2); assert!(owner.to_json_bytes().is_err()); let view = runtime .block_on(crate::open_wallet_view(bytes.as_slice(), crate::ViewPassword::new(view_password_text))) .expect("generic VIEW open must dispatch V2"); assert_eq!(view.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2); assert_eq!(*view.pubkey(), pubkey); assert_eq!(view.alias(), std::option::Option::Some("pre016-v2")); let reopened = runtime .block_on(crate::open_wallet_owner(bytes.as_slice(), crate::OwnerPassword::new(owner_password_text))) .expect("generic OWNER open must dispatch V2"); assert_eq!(reopened.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2); assert_eq!(*reopened.pubkey(), pubkey); } #[test] fn generic_dispatch_keeps_v1_read_compatibility() { let runtime = runtime(); assert_eq!(crate::detect_wallet_format(V1_FULL_VECTOR).expect("V1 framing must detect"), crate::WalletFormat::V1); let locked = crate::inspect_locked_wallet(V1_FULL_VECTOR).expect("generic inspect must keep V1 compatibility"); assert_eq!(locked.format_version(), crate::KSPWALLET_FORMAT_VERSION_V1); let owner = runtime .block_on(crate::open_wallet_owner(V1_FULL_VECTOR, crate::OwnerPassword::new(std::string::String::from("pre005-owner-password")))) .expect("generic OWNER open must dispatch V1"); assert_eq!(owner.format_version(), crate::KSPWALLET_FORMAT_VERSION_V1); } #[test] fn default_file_creation_is_v2_and_v2_administration_stays_v2() { let directory = tempfile::Builder::new().prefix("ksp-pre016-v2-").tempdir().expect("V2 test directory must be creatable"); let path = directory.path().join("default.kspwallet"); let runtime = runtime(); let mut owner = runtime .block_on(crate::create_wallet_file( path.as_path(), crate::OwnerPassword::new(std::string::String::from("pre016-default-owner")), std::option::Option::Some(crate::ViewPassword::new(std::string::String::from("pre016-default-view"))), crate::WalletCreateMetadataV1::default(), )) .expect("default file creation must succeed"); assert_eq!(owner.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2); let locked = runtime.block_on(crate::inspect_locked_wallet_file(path.as_path())).expect("generic inspect must read default file"); assert_eq!(locked.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2); runtime .block_on(owner.update_alias(path.as_path(), std::option::Option::Some(std::string::String::from("pre016-updated")))) .expect("V2 metadata update must persist in V2"); runtime .block_on(owner.rotate_owner_password(path.as_path(), crate::OwnerPassword::new(std::string::String::from("pre016-owner-rotated")))) .expect("V2 OWNER rotation must persist in V2"); runtime .block_on(owner.rotate_view_password(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre016-view-rotated")))) .expect("V2 VIEW rotation by OWNER must persist in V2"); let reopened_owner = runtime .block_on(crate::open_wallet_owner_file(path.as_path(), crate::OwnerPassword::new(std::string::String::from("pre016-owner-rotated")))) .expect("rotated V2 OWNER must reopen through generic dispatch"); assert_eq!(reopened_owner.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2); assert_eq!(reopened_owner.alias(), std::option::Option::Some("pre016-updated")); let reopened_view = runtime .block_on(crate::open_wallet_view_file(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre016-view-rotated")))) .expect("rotated V2 VIEW must reopen through generic dispatch"); assert_eq!(reopened_view.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2); assert_eq!(reopened_view.alias(), std::option::Option::Some("pre016-updated")); runtime.block_on(owner.disable_view(path.as_path())).expect("V2 strong VIEW disable must persist"); let disabled = runtime.block_on(crate::inspect_locked_wallet_file(path.as_path())).expect("disabled V2 must remain inspectable"); assert!(!disabled.view_enabled()); let old_view = runtime.block_on(crate::open_wallet_view_file(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre016-view-rotated")))); assert_eq!(old_view.expect_err("disabled V2 VIEW must not reopen").code(), crate::ERROR_CODE_CAPABILITY_INSUFFICIENT); runtime .block_on(owner.recreate_view(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre016-view-recreated")))) .expect("V2 strong VIEW recreation must persist"); let mut recreated_view = runtime .block_on(crate::open_wallet_view_file(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre016-view-recreated")))) .expect("recreated V2 VIEW must open"); runtime .block_on(recreated_view.rotate_view_password(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre016-view-self-rotated")))) .expect("V2 VIEW self-rotation must persist"); let final_view = runtime .block_on(crate::open_wallet_view_file(path.as_path(), crate::ViewPassword::new(std::string::String::from("pre016-view-self-rotated")))) .expect("self-rotated V2 VIEW must reopen"); assert_eq!(final_view.format_version(), crate::KSPWALLET_FORMAT_VERSION_V2); let bytes = std::fs::read(path).expect("V2 file must remain readable for framing canary"); assert_eq!(crate::detect_wallet_format(bytes.as_slice()).expect("administered file must still be V2"), crate::WalletFormat::V2); } #[test] fn explicit_versioned_readers_reject_the_other_native_wire_format() { let runtime = runtime(); let v1_as_v2 = runtime.block_on(crate::open_wallet_owner_v2(V1_FULL_VECTOR, crate::OwnerPassword::new(std::string::String::from("irrelevant-before-v2-parse")))); assert_eq!(v1_as_v2.expect_err("explicit V2 reader must reject V1 framing").code(), crate::ERROR_CODE_FORMAT_INVALID); let owner = runtime .block_on(crate::create_wallet_v2( crate::OwnerPassword::new(std::string::String::from("pre016-strict-v2-owner")), std::option::Option::None, crate::WalletCreateMetadata::default(), )) .expect("strict-reader V2 fixture creation must succeed"); let bytes = owner.to_native_bytes().expect("strict-reader V2 fixture must serialize"); let v2_as_v1 = runtime.block_on(crate::open_wallet_owner_v1(bytes.as_slice(), crate::OwnerPassword::new(std::string::String::from("irrelevant-before-v1-parse")))); assert_eq!(v2_as_v1.expect_err("explicit V1 reader must reject V2 framing").code(), crate::ERROR_CODE_FORMAT_INVALID); }