v0.5.2-pre.002
This commit is contained in:
184
ks-wallet/tests/legacy_characterization.rs
Normal file
184
ks-wallet/tests/legacy_characterization.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
// file: ks-wallet/tests/legacy_characterization.rs
|
||||
// version: 1
|
||||
|
||||
//! External characterization tests for the legacy Solana JSON wallet format.
|
||||
|
||||
use solana_signer::Signer; // rust-rules: trait-import
|
||||
use zeroize::Zeroize; // rust-rules: trait-import
|
||||
|
||||
fn write_legacy_fixture(path: &std::path::Path, keypair: &solana_keypair::Keypair) {
|
||||
let mut keypair_bytes = keypair.to_bytes();
|
||||
let encoded = serde_json::to_vec(keypair_bytes.as_slice())
|
||||
.unwrap_or_else(|error| panic!("legacy fixture must serialize: {error}"));
|
||||
keypair_bytes.zeroize();
|
||||
std::fs::write(path, encoded)
|
||||
.unwrap_or_else(|error| panic!("legacy fixture must be writable: {error}"));
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt; // rust-rules: trait-import
|
||||
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
|
||||
.unwrap_or_else(|error| panic!("legacy fixture permissions must be set: {error}"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_path_is_alias_json() {
|
||||
let directory = tempfile::tempdir()
|
||||
.unwrap_or_else(|error| panic!("temporary directory must exist: {error}"));
|
||||
let store = ks_wallet::TemporaryWalletStore::new(directory.path())
|
||||
.unwrap_or_else(|error| panic!("unexpected store error: {error}"));
|
||||
let alias = ks_wallet::WalletAlias::parse("legacy-name")
|
||||
.unwrap_or_else(|error| panic!("unexpected alias error: {error}"));
|
||||
assert_eq!(
|
||||
store.wallet_path(&alias).file_name(),
|
||||
std::option::Option::Some(std::ffi::OsStr::new("legacy-name.json"))
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn legacy_standard_json_fixture_preserves_public_key() {
|
||||
let directory = tempfile::tempdir()
|
||||
.unwrap_or_else(|error| panic!("temporary directory must exist: {error}"));
|
||||
let store = ks_wallet::TemporaryWalletStore::new(directory.path())
|
||||
.unwrap_or_else(|error| panic!("unexpected store error: {error}"));
|
||||
let alias = ks_wallet::WalletAlias::parse("legacy-fixture")
|
||||
.unwrap_or_else(|error| panic!("unexpected alias error: {error}"));
|
||||
let expected = solana_keypair::Keypair::new();
|
||||
write_legacy_fixture(&store.wallet_path(&alias), &expected);
|
||||
let loaded = store
|
||||
.load(alias)
|
||||
.await
|
||||
.unwrap_or_else(|error| panic!("legacy fixture must load: {error}"));
|
||||
assert_eq!(loaded.public_key(), expected.pubkey().to_string());
|
||||
assert_eq!(loaded.persistence(), ks_wallet::WalletPersistence::Persistent);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn legacy_missing_file_preserves_current_error_contract() {
|
||||
let directory = tempfile::tempdir()
|
||||
.unwrap_or_else(|error| panic!("temporary directory must exist: {error}"));
|
||||
let store = ks_wallet::TemporaryWalletStore::new(directory.path())
|
||||
.unwrap_or_else(|error| panic!("unexpected store error: {error}"));
|
||||
let alias = ks_wallet::WalletAlias::parse("missing")
|
||||
.unwrap_or_else(|error| panic!("unexpected alias error: {error}"));
|
||||
let error = store
|
||||
.load(alias)
|
||||
.await
|
||||
.err()
|
||||
.unwrap_or_else(|| panic!("missing legacy wallet must fail"));
|
||||
assert_eq!(error.code(), "wallet_file_metadata_failed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn legacy_corrupted_json_and_short_keypair_are_distinct() {
|
||||
let directory = tempfile::tempdir()
|
||||
.unwrap_or_else(|error| panic!("temporary directory must exist: {error}"));
|
||||
let store = ks_wallet::TemporaryWalletStore::new(directory.path())
|
||||
.unwrap_or_else(|error| panic!("unexpected store error: {error}"));
|
||||
let corrupted_alias = ks_wallet::WalletAlias::parse("corrupted-json")
|
||||
.unwrap_or_else(|error| panic!("unexpected alias error: {error}"));
|
||||
let corrupted_path = store.wallet_path(&corrupted_alias);
|
||||
std::fs::write(&corrupted_path, b"not-json")
|
||||
.unwrap_or_else(|error| panic!("corrupted fixture must be writable: {error}"));
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt; // rust-rules: trait-import
|
||||
std::fs::set_permissions(&corrupted_path, std::fs::Permissions::from_mode(0o600))
|
||||
.unwrap_or_else(|error| panic!("fixture permissions must be set: {error}"));
|
||||
}
|
||||
let corrupted_error = store
|
||||
.load(corrupted_alias)
|
||||
.await
|
||||
.err()
|
||||
.unwrap_or_else(|| panic!("corrupted JSON must fail"));
|
||||
assert_eq!(corrupted_error.code(), "wallet_keypair_json_invalid");
|
||||
let short_alias = ks_wallet::WalletAlias::parse("short-keypair")
|
||||
.unwrap_or_else(|error| panic!("unexpected alias error: {error}"));
|
||||
let short_path = store.wallet_path(&short_alias);
|
||||
std::fs::write(&short_path, b"[1,2,3]")
|
||||
.unwrap_or_else(|error| panic!("short fixture must be writable: {error}"));
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt; // rust-rules: trait-import
|
||||
std::fs::set_permissions(&short_path, std::fs::Permissions::from_mode(0o600))
|
||||
.unwrap_or_else(|error| panic!("fixture permissions must be set: {error}"));
|
||||
}
|
||||
let short_error = store
|
||||
.load(short_alias)
|
||||
.await
|
||||
.err()
|
||||
.unwrap_or_else(|| panic!("short keypair must fail"));
|
||||
assert_eq!(short_error.code(), "wallet_keypair_length_invalid");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn legacy_create_refuses_existing_wallet_and_preserves_first_pubkey() {
|
||||
let directory = tempfile::tempdir()
|
||||
.unwrap_or_else(|error| panic!("temporary directory must exist: {error}"));
|
||||
let store = ks_wallet::TemporaryWalletStore::new(directory.path())
|
||||
.unwrap_or_else(|error| panic!("unexpected store error: {error}"));
|
||||
let alias = ks_wallet::WalletAlias::parse("collision")
|
||||
.unwrap_or_else(|error| panic!("unexpected alias error: {error}"));
|
||||
let first = store
|
||||
.create(alias.clone())
|
||||
.await
|
||||
.unwrap_or_else(|error| panic!("first legacy wallet must be created: {error}"));
|
||||
let second_error = store
|
||||
.create(alias.clone())
|
||||
.await
|
||||
.err()
|
||||
.unwrap_or_else(|| panic!("second legacy create must fail"));
|
||||
assert_eq!(second_error.code(), "wallet_file_already_exists");
|
||||
let loaded = store
|
||||
.load(alias)
|
||||
.await
|
||||
.unwrap_or_else(|error| panic!("first legacy wallet must remain readable: {error}"));
|
||||
assert_eq!(loaded.public_key(), first.public_key());
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tokio::test]
|
||||
async fn legacy_file_with_open_permissions_is_rejected() {
|
||||
use std::os::unix::fs::PermissionsExt; // rust-rules: trait-import
|
||||
let directory = tempfile::tempdir()
|
||||
.unwrap_or_else(|error| panic!("temporary directory must exist: {error}"));
|
||||
let store = ks_wallet::TemporaryWalletStore::new(directory.path())
|
||||
.unwrap_or_else(|error| panic!("unexpected store error: {error}"));
|
||||
let alias = ks_wallet::WalletAlias::parse("open-permissions")
|
||||
.unwrap_or_else(|error| panic!("unexpected alias error: {error}"));
|
||||
let keypair = solana_keypair::Keypair::new();
|
||||
let path = store.wallet_path(&alias);
|
||||
write_legacy_fixture(&path, &keypair);
|
||||
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644))
|
||||
.unwrap_or_else(|error| panic!("fixture permissions must be changed: {error}"));
|
||||
let error = store
|
||||
.load(alias)
|
||||
.await
|
||||
.err()
|
||||
.unwrap_or_else(|| panic!("open permissions must fail"));
|
||||
assert_eq!(error.code(), "wallet_file_permissions_too_open");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tokio::test]
|
||||
async fn legacy_symlink_is_rejected() {
|
||||
let directory = tempfile::tempdir()
|
||||
.unwrap_or_else(|error| panic!("temporary directory must exist: {error}"));
|
||||
let store = ks_wallet::TemporaryWalletStore::new(directory.path())
|
||||
.unwrap_or_else(|error| panic!("unexpected store error: {error}"));
|
||||
let target_alias = ks_wallet::WalletAlias::parse("target")
|
||||
.unwrap_or_else(|error| panic!("unexpected alias error: {error}"));
|
||||
let link_alias = ks_wallet::WalletAlias::parse("linked")
|
||||
.unwrap_or_else(|error| panic!("unexpected alias error: {error}"));
|
||||
let keypair = solana_keypair::Keypair::new();
|
||||
let target_path = store.wallet_path(&target_alias);
|
||||
write_legacy_fixture(&target_path, &keypair);
|
||||
std::os::unix::fs::symlink(&target_path, store.wallet_path(&link_alias))
|
||||
.unwrap_or_else(|error| panic!("legacy symlink fixture must be created: {error}"));
|
||||
let error = store
|
||||
.load(link_alias)
|
||||
.await
|
||||
.err()
|
||||
.unwrap_or_else(|| panic!("legacy symlink must fail"));
|
||||
assert_eq!(error.code(), "wallet_file_type_invalid");
|
||||
}
|
||||
48
ks-wallet/tests/public_api.rs
Normal file
48
ks-wallet/tests/public_api.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
// file: ks-wallet/tests/public_api.rs
|
||||
// version: 1
|
||||
|
||||
//! External crate-root contract tests for the `ks-wallet` public API.
|
||||
|
||||
fn write_native_identification_fixture(path: &std::path::Path) {
|
||||
let mut bytes = std::vec::Vec::new();
|
||||
bytes.extend_from_slice(b"KSWALLET");
|
||||
bytes.extend_from_slice(&1_u16.to_le_bytes());
|
||||
std::fs::write(path, bytes)
|
||||
.unwrap_or_else(|error| panic!("native fixture must be writable: {error}"));
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt; // rust-rules: trait-import
|
||||
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
|
||||
.unwrap_or_else(|error| panic!("native fixture permissions must be set: {error}"));
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn crate_root_exposes_multi_wallet_discovery_without_path_disclosure() {
|
||||
assert_eq!(ks_wallet::KSWALLET_FILE_EXTENSION, "kswallet");
|
||||
let directory = tempfile::tempdir()
|
||||
.unwrap_or_else(|error| panic!("temporary directory must exist: {error}"));
|
||||
let manager = ks_wallet::WalletManager::new(directory.path())
|
||||
.unwrap_or_else(|error| panic!("unexpected manager error: {error}"));
|
||||
let path = directory.path().join("public-api.kswallet");
|
||||
write_native_identification_fixture(&path);
|
||||
let handles = manager
|
||||
.scan()
|
||||
.await
|
||||
.unwrap_or_else(|error| panic!("scan must succeed: {error}"));
|
||||
assert_eq!(handles.len(), 1);
|
||||
assert_eq!(handles[0].alias().as_str(), "public-api");
|
||||
assert!(!format!("{:?}", handles[0]).contains(directory.path().to_string_lossy().as_ref()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crate_root_exposes_non_secret_temporary_identity() {
|
||||
let alias = ks_wallet::WalletAlias::parse("public-identity")
|
||||
.unwrap_or_else(|error| panic!("unexpected alias error: {error}"));
|
||||
let wallet = ks_wallet::TemporaryWallet::generate(alias.clone());
|
||||
let identity: ks_wallet::WalletIdentity = wallet.identity();
|
||||
let summary: ks_wallet::WalletSummary = wallet.summary();
|
||||
assert_eq!(identity.alias, alias);
|
||||
assert_eq!(identity.public_key, summary.public_key);
|
||||
assert_eq!(identity.persistence, ks_wallet::WalletPersistence::Temporary);
|
||||
}
|
||||
Reference in New Issue
Block a user