// file: crates/ksp-app-wallet-desk/unit_tests/wallet_inventory.rs // version: 2 const VALID_WALLET: &[u8] = include_bytes!("../../ksp-wallet-lib/tests/fixtures/kspwallet_v1_full_vector.json"); #[test] fn inventory_lists_regular_wallets_reports_invalid_candidates_and_ignores_other_entries() { let root = fixture_path("inventory"); cleanup_fixture(root.as_path()); let created = std::fs::create_dir_all(root.as_path()); assert!(created.is_ok(), "inventory fixture root should be creatable: {created:?}"); if created.is_ok() { assert!(std::fs::write(root.join("beta.kspwallet"), VALID_WALLET).is_ok()); assert!(std::fs::write(root.join("alpha.kspwallet"), b"not-json").is_ok()); assert!(std::fs::write(root.join("ignored.txt"), VALID_WALLET).is_ok()); assert!(std::fs::write(root.join("upper.KSPWALLET"), VALID_WALLET).is_ok()); assert!(std::fs::write(root.join("backup.kspwallet.bak"), VALID_WALLET).is_ok()); assert!(std::fs::create_dir(root.join("directory.kspwallet")).is_ok()); assert!(std::fs::create_dir(root.join("nested")).is_ok()); assert!(std::fs::write(root.join("nested/hidden.kspwallet"), VALID_WALLET).is_ok()); let runtime = tokio::runtime::Builder::new_current_thread().build(); assert!(runtime.is_ok(), "test runtime should be constructible: {runtime:?}"); if let std::result::Result::Ok(runtime) = runtime { let inventory = runtime.block_on(crate::list_wallet_inventory(root.as_path())); assert!(inventory.is_ok(), "inventory should complete: {inventory:?}"); if let std::result::Result::Ok(entries) = inventory { assert_eq!(entries.len(), 2); assert_eq!(entries[0].filename, "alpha.kspwallet"); assert_eq!(entries[0].inspection_status, crate::WalletInspectionStatusDto::Invalid); assert_eq!(entries[0].state, crate::WalletInventoryStateDto::Error); assert!(entries[0].diagnostic.is_some()); assert_eq!(entries[1].filename, "beta.kspwallet"); assert_eq!(entries[1].inspection_status, crate::WalletInspectionStatusDto::Valid); assert_eq!(entries[1].state, crate::WalletInventoryStateDto::Locked); assert_eq!(entries[1].format_version, std::option::Option::Some(1)); assert!(entries[1].view_enabled.is_some()); assert!(entries[1].diagnostic.is_none()); } } } cleanup_fixture(root.as_path()); } #[test] fn selection_rejects_traversal_and_reinspects_one_valid_locked_wallet() { let root = fixture_path("selection"); cleanup_fixture(root.as_path()); let created = std::fs::create_dir_all(root.as_path()); assert!(created.is_ok(), "selection fixture root should be creatable: {created:?}"); if created.is_ok() { assert!(std::fs::write(root.join("selected.kspwallet"), VALID_WALLET).is_ok()); let runtime = tokio::runtime::Builder::new_current_thread().build(); assert!(runtime.is_ok(), "test runtime should be constructible: {runtime:?}"); if let std::result::Result::Ok(runtime) = runtime { let rejected = runtime .block_on(crate::select_locked_wallet(root.as_path(), crate::WalletSelectionRequestDto { wallet_id: "../selected.kspwallet".to_owned() })); assert!(rejected.is_err()); if let std::result::Result::Err(error) = rejected { assert_eq!(error.code(), crate::ERROR_CODE_WALLET_SELECTION_INVALID); } let rejected_backslash = runtime .block_on(crate::select_locked_wallet(root.as_path(), crate::WalletSelectionRequestDto { wallet_id: "..\\selected.kspwallet".to_owned() })); assert!(rejected_backslash.is_err()); if let std::result::Result::Err(error) = rejected_backslash { assert_eq!(error.code(), crate::ERROR_CODE_WALLET_SELECTION_INVALID); } for wallet_id in ["", "/selected.kspwallet", "nested/selected.kspwallet", "selected.kspwallet.bak"] { let rejected = runtime.block_on(crate::select_locked_wallet(root.as_path(), crate::WalletSelectionRequestDto { wallet_id: wallet_id.to_owned() })); assert!(rejected.is_err(), "invalid wallet selection should be rejected: {wallet_id}"); if let std::result::Result::Err(error) = rejected { assert_eq!(error.code(), crate::ERROR_CODE_WALLET_SELECTION_INVALID); } } let selected = runtime.block_on(crate::select_locked_wallet(root.as_path(), crate::WalletSelectionRequestDto { wallet_id: "selected.kspwallet".to_owned() })); assert!(selected.is_ok(), "valid locked Wallet should be selectable: {selected:?}"); if let std::result::Result::Ok(selected) = selected { assert_eq!(selected.wallet_id, "selected.kspwallet"); assert_eq!(selected.filename, "selected.kspwallet"); assert_eq!(selected.format_version, 1); } } } cleanup_fixture(root.as_path()); } #[cfg(unix)] #[test] fn inventory_and_selection_exclude_symbolic_link_wallet_entries() { let root = fixture_path("symlink-inventory"); let outside = fixture_path("symlink-wallet"); cleanup_fixture(root.as_path()); cleanup_fixture(outside.as_path()); let root_created = std::fs::create_dir_all(root.as_path()); let outside_written = std::fs::write(outside.as_path(), VALID_WALLET); assert!(root_created.is_ok()); assert!(outside_written.is_ok()); if root_created.is_ok() && outside_written.is_ok() { let linked = std::os::unix::fs::symlink(outside.as_path(), root.join("linked.kspwallet")); assert!(linked.is_ok()); if linked.is_ok() { let runtime = tokio::runtime::Builder::new_current_thread().build(); assert!(runtime.is_ok()); if let std::result::Result::Ok(runtime) = runtime { let inventory = runtime.block_on(crate::list_wallet_inventory(root.as_path())); assert_eq!(inventory.as_ref().ok().map(std::vec::Vec::len), std::option::Option::Some(0)); let selected = runtime .block_on(crate::select_locked_wallet(root.as_path(), crate::WalletSelectionRequestDto { wallet_id: "linked.kspwallet".to_owned() })); assert!(selected.is_err()); if let std::result::Result::Err(error) = selected { assert_eq!(error.code(), crate::ERROR_CODE_WALLET_SELECTION_INVALID); } } } } cleanup_fixture(root.as_path()); cleanup_fixture(outside.as_path()); } fn fixture_path(name: &str) -> std::path::PathBuf { return std::env::temp_dir().join(std::format!("ksp-wallet-desk-{name}-{}", std::process::id())); } fn cleanup_fixture(path: &std::path::Path) { let metadata = std::fs::symlink_metadata(path); match metadata { std::result::Result::Ok(metadata) if metadata.is_dir() && !metadata.file_type().is_symlink() => { let _ = std::fs::remove_dir_all(path); }, std::result::Result::Ok(_) => { let _ = std::fs::remove_file(path); }, std::result::Result::Err(_) => {}, } }