use std::path::{Path, PathBuf};
use std::collections::HashMap;
use crate::error::{WalletError, Result};
use crate::database::{Database, IWItem, IWField, IWLabel, IWProperties};
use crate::database::queries::{self, parse_timestamp};
use crate::database::migrations;
use crate::crypto;
use crate::utils::generate_database_id;
use crate::{DATABASE_FILENAME, ROOT_ID, ROOT_PARENT_ID, DB_VERSION, ENCRYPTION_COUNT_DEFAULT};
pub struct Wallet {
pub(crate) folder: PathBuf,
pub(crate) db: Option<Database>,
pub(crate) password: Option<String>,
pub(crate) encryption_count: u32,
pub(crate) items_cache: Option<Vec<IWItem>>,
pub(crate) fields_cache: Option<Vec<IWField>>,
pub(crate) labels_cache: Option<HashMap<String, IWLabel>>,
}
impl Wallet {
pub fn open(folder: &Path) -> Result<Self> {
let db_path = folder.join(DATABASE_FILENAME);
if !db_path.exists() {
return Err(WalletError::DatabaseNotFound(
db_path.to_string_lossy().to_string()
));
}
let db = Database::open(&db_path)?;
{
let conn = db.connection()?;
let current = migrations::get_database_version(conn)?;
migrations::upgrade_database(conn, ¤t)?;
}
Ok(Self {
folder: folder.to_path_buf(),
db: Some(db),
password: None,
encryption_count: ENCRYPTION_COUNT_DEFAULT,
items_cache: None,
fields_cache: None,
labels_cache: None,
})
}
pub fn create(folder: &Path, password: &str, lang: &str) -> Result<Self> {
std::fs::create_dir_all(folder)?;
let db_path = folder.join(DATABASE_FILENAME);
let db = Database::create(&db_path)?;
let mut wallet = Self {
folder: folder.to_path_buf(),
db: Some(db),
password: Some(password.to_string()),
encryption_count: 0, items_cache: None,
fields_cache: None,
labels_cache: None,
};
wallet.init_new_database(password, lang)?;
Ok(wallet)
}
fn init_new_database(&mut self, password: &str, lang: &str) -> Result<()> {
let conn = self.db.as_ref()
.ok_or_else(|| WalletError::DatabaseError("Database not open".to_string()))?
.connection()?;
let db_id = generate_database_id();
queries::set_properties(conn, &db_id, lang, DB_VERSION, 0)?;
let root_data = crate::utils::generate_id(32);
let encrypted = crypto::encrypt(&root_data, password, 0, None)
.map_err(|e| WalletError::EncryptionError(e))?;
queries::create_item(conn, ROOT_ID, ROOT_PARENT_ID, &encrypted, "", true)?;
self.add_system_labels()?;
Ok(())
}
pub fn unlock(&mut self, password: &str) -> Result<bool> {
let db = self.db.as_ref().ok_or(WalletError::DatabaseError(
"Database not open".to_string()
))?;
let conn = db.connection()?;
let root_name = queries::get_root_item_raw(conn)?;
let Some(encrypted_name) = root_name else {
return Err(WalletError::DatabaseError("Root item not found".to_string()));
};
if let Some(props) = queries::get_properties(conn)? {
self.encryption_count = props.email.parse().unwrap_or(ENCRYPTION_COUNT_DEFAULT);
}
match crypto::decrypt(&encrypted_name, password, self.encryption_count, None) {
Ok(_) => {
self.password = Some(password.to_string());
self.clear_caches();
self.add_system_labels()?;
Ok(true)
}
Err(_) => Ok(false)
}
}
pub fn lock(&mut self) {
self.password = None;
self.clear_caches();
}
pub fn is_unlocked(&self) -> bool {
self.password.is_some()
}
pub fn close(&mut self) {
self.lock();
if let Some(mut db) = self.db.take() {
db.close();
}
}
pub(crate) fn clear_caches(&mut self) {
self.items_cache = None;
self.fields_cache = None;
self.labels_cache = None;
}
pub fn folder(&self) -> &Path {
&self.folder
}
pub fn check_password(&self, password: &str) -> Result<bool> {
let db = self.db.as_ref().ok_or(WalletError::DatabaseError(
"Database not open".to_string()
))?;
let conn = db.connection()?;
let root_name = queries::get_root_item_raw(conn)?;
let Some(encrypted_name) = root_name else {
return Err(WalletError::DatabaseError("Root item not found".to_string()));
};
let encryption_count = if let Some(props) = queries::get_properties(conn)? {
props.email.parse().unwrap_or(ENCRYPTION_COUNT_DEFAULT)
} else {
self.encryption_count
};
match crypto::decrypt(&encrypted_name, password, encryption_count, None) {
Ok(_) => Ok(true),
Err(_) => Ok(false)
}
}
pub fn get_properties(&self) -> Result<IWProperties> {
let db = self.db.as_ref().ok_or(WalletError::DatabaseError(
"Database not open".to_string()
))?;
let conn = db.connection()?;
let raw_props = queries::get_properties(conn)?
.ok_or_else(|| WalletError::DatabaseError("Properties not found".to_string()))?;
Ok(IWProperties {
database_id: raw_props.database_id,
lang: raw_props.lang,
version: raw_props.version,
encryption_count: raw_props.email.parse().unwrap_or(ENCRYPTION_COUNT_DEFAULT),
sync_timestamp: raw_props.sync_timestamp.as_ref().and_then(|s| parse_timestamp(s)),
update_timestamp: raw_props.update_timestamp.as_ref().and_then(|s| parse_timestamp(s)),
})
}
pub fn change_password(&mut self, new_password: &str) -> Result<bool> {
self.ensure_unlocked()?;
let old_password = self.password.as_ref().unwrap().clone();
self.load_items_if_needed()?;
self.load_fields_if_needed()?;
let items = self.items_cache.take().unwrap();
let fields = self.fields_cache.take().unwrap();
let encryption_count = self.encryption_count;
let db = self.db.as_mut()
.ok_or_else(|| WalletError::DatabaseError("Database not open".to_string()))?;
db.begin_transaction()?;
let result = (|| -> Result<()> {
let conn = db.connection()?;
for item in &items {
let new_encrypted = crypto::encrypt(&item.name, new_password, encryption_count, None)
.map_err(|e| WalletError::EncryptionError(e))?;
queries::update_item_name_only(conn, &item.item_id, &new_encrypted)?;
}
for field in &fields {
let new_encrypted = crypto::encrypt(&field.value, new_password, encryption_count, None)
.map_err(|e| WalletError::EncryptionError(e))?;
queries::update_field_value_only(conn, &field.item_id, &field.field_id, &new_encrypted)?;
}
let deleted_items_raw = queries::get_deleted_items_raw(conn)?;
for raw in &deleted_items_raw {
if let Ok(name) = crypto::decrypt(&raw.name_encrypted, &old_password, encryption_count, None) {
let new_encrypted = crypto::encrypt(&name, new_password, encryption_count, None)
.map_err(|e| WalletError::EncryptionError(e))?;
queries::update_item_name_only(conn, &raw.item_id, &new_encrypted)?;
}
}
let deleted_fields_raw = queries::get_deleted_fields_raw(conn)?;
for raw in &deleted_fields_raw {
if let Ok(value) = crypto::decrypt(&raw.value_encrypted, &old_password, encryption_count, None) {
let new_encrypted = crypto::encrypt(&value, new_password, encryption_count, None)
.map_err(|e| WalletError::EncryptionError(e))?;
queries::update_field_value_only(conn, &raw.item_id, &raw.field_id, &new_encrypted)?;
}
}
Ok(())
})();
match result {
Ok(()) => {
db.commit_transaction()?;
self.password = Some(new_password.to_string());
self.clear_caches();
Ok(true)
}
Err(e) => {
db.rollback_transaction()?;
self.password = Some(old_password);
self.items_cache = Some(items);
self.fields_cache = Some(fields);
Err(e)
}
}
}
pub(crate) fn ensure_unlocked(&self) -> Result<()> {
if self.password.is_none() {
return Err(WalletError::Locked);
}
Ok(())
}
pub fn database_path(&self) -> PathBuf {
self.folder.join(DATABASE_FILENAME)
}
pub fn compact(&mut self) -> Result<(u32, u32)> {
self.ensure_unlocked()?;
let conn = self.db.as_ref()
.ok_or_else(|| WalletError::DatabaseError("Database not open".to_string()))?
.connection()?;
let result = queries::purge_deleted(conn)?;
self.clear_caches();
Ok(result)
}
pub fn get_database_stats(&self) -> Result<queries::DatabaseStats> {
let conn = self.db.as_ref()
.ok_or_else(|| WalletError::DatabaseError("Database not open".to_string()))?
.connection()?;
let mut stats = queries::get_database_stats(conn)?;
let db_path = self.database_path();
if let Ok(metadata) = std::fs::metadata(&db_path) {
stats.file_size_bytes = metadata.len();
}
Ok(stats)
}
pub fn database(&self) -> Result<&Database> {
self.db.as_ref().ok_or_else(|| WalletError::DatabaseError("Database not open".to_string()))
}
}
impl Drop for Wallet {
fn drop(&mut self) {
self.close();
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use tempfile::TempDir;
use crate::DB_VERSION;
pub fn create_test_wallet() -> (Wallet, TempDir) {
let temp_dir = TempDir::new().unwrap();
let wallet = Wallet::create(temp_dir.path(), "TestPassword123", "en").unwrap();
(wallet, temp_dir)
}
#[test]
fn test_create_and_unlock() {
let (mut wallet, _temp) = create_test_wallet();
wallet.lock();
assert!(!wallet.is_unlocked());
assert!(wallet.unlock("TestPassword123").unwrap());
assert!(wallet.is_unlocked());
}
#[test]
fn test_open_migrates_old_database() {
use rusqlite::Connection;
let temp_dir = TempDir::new().unwrap();
let wallet = Wallet::create(temp_dir.path(), "TestPassword123", "en").unwrap();
let folder = temp_dir.path().to_path_buf();
drop(wallet);
let db_path = folder.join(crate::DATABASE_FILENAME);
let conn = Connection::open(&db_path).unwrap();
conn.execute("UPDATE nswallet_properties SET version = ?", ["4"]).unwrap();
drop(conn);
let _ = Wallet::open(&folder).unwrap();
let conn = Connection::open(&db_path).unwrap();
let v: String = conn
.query_row(
"SELECT version FROM nswallet_properties LIMIT 1",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(v, DB_VERSION);
let seed_count: i32 = conn
.query_row(
"SELECT COUNT(*) FROM nswallet_labels WHERE field_type = 'SEED'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(seed_count, 1);
}
#[test]
fn test_open_no_op_on_current_database() {
let temp_dir = TempDir::new().unwrap();
let wallet = Wallet::create(temp_dir.path(), "TestPassword123", "en").unwrap();
let folder = temp_dir.path().to_path_buf();
drop(wallet);
let _ = Wallet::open(&folder).unwrap();
use rusqlite::Connection;
let db_path = folder.join(crate::DATABASE_FILENAME);
let conn = Connection::open(&db_path).unwrap();
let v: String = conn
.query_row(
"SELECT version FROM nswallet_properties LIMIT 1",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(v, DB_VERSION);
}
#[test]
fn test_wrong_password() {
let (mut wallet, _temp) = create_test_wallet();
wallet.lock();
assert!(!wallet.unlock("WrongPassword").unwrap());
assert!(!wallet.is_unlocked());
}
#[test]
fn test_properties() {
let (wallet, _temp) = create_test_wallet();
let props = wallet.get_properties().unwrap();
assert_eq!(props.lang, "en");
assert_eq!(props.version, DB_VERSION);
assert_eq!(props.encryption_count, 0);
assert_eq!(props.database_id.len(), 32);
}
#[test]
fn test_change_password() {
let (mut wallet, _temp) = create_test_wallet();
let item_id = wallet.add_item("Test Item", "document", false, None).unwrap();
wallet.add_field(&item_id, "PASS", "secret123", None).unwrap();
assert!(wallet.change_password("NewPassword456").unwrap());
wallet.lock();
assert!(!wallet.unlock("TestPassword123").unwrap());
assert!(wallet.unlock("NewPassword456").unwrap());
let fields = wallet.get_fields_by_item(&item_id).unwrap();
assert_eq!(fields[0].value, "secret123");
}
#[test]
fn test_wallet_folder() {
let (wallet, temp) = create_test_wallet();
assert_eq!(wallet.folder(), temp.path());
}
#[test]
fn test_database_path() {
let (wallet, temp) = create_test_wallet();
assert_eq!(wallet.database_path(), temp.path().join("nswallet.dat"));
}
#[test]
fn test_open_nonexistent() {
let result = Wallet::open(std::path::Path::new("/nonexistent/path"));
assert!(result.is_err());
}
#[test]
fn test_check_password() {
let (mut wallet, _temp) = create_test_wallet();
wallet.lock();
assert!(wallet.check_password("TestPassword123").unwrap());
assert!(!wallet.check_password("WrongPassword").unwrap());
}
fn create_wallet_with_encryption_count(encryption_count: u32) -> (TempDir, std::path::PathBuf) {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().to_path_buf();
let password = "TestPassword123";
let wallet = Wallet::create(&path, password, "en").unwrap();
let db = wallet.db.as_ref().unwrap();
let conn = db.connection().unwrap();
let root_raw = queries::get_root_item_raw(conn).unwrap().unwrap();
let plaintext = crypto::decrypt(&root_raw, password, 0, None).unwrap();
let new_encrypted = crypto::encrypt(&plaintext, password, encryption_count, None).unwrap();
conn.execute(
"UPDATE nswallet_items SET name = ? WHERE item_id = '__ROOT__'",
rusqlite::params![new_encrypted],
).unwrap();
conn.execute(
"UPDATE nswallet_properties SET email = ?",
rusqlite::params![encryption_count.to_string()],
).unwrap();
assert_eq!(wallet.get_properties().unwrap().encryption_count, encryption_count);
drop(wallet);
(temp_dir, path)
}
#[test]
fn test_check_password_after_reopen_enc0() {
let (_temp, path) = create_wallet_with_encryption_count(0);
let wallet = Wallet::open(&path).unwrap();
assert!(wallet.check_password("TestPassword123").unwrap());
assert!(!wallet.check_password("WrongPassword").unwrap());
}
#[test]
fn test_check_password_after_reopen_enc33() {
let (_temp, path) = create_wallet_with_encryption_count(33);
let wallet = Wallet::open(&path).unwrap();
assert!(wallet.check_password("TestPassword123").unwrap());
assert!(!wallet.check_password("WrongPassword").unwrap());
}
#[test]
fn test_check_password_after_reopen_enc200() {
let (_temp, path) = create_wallet_with_encryption_count(200);
let wallet = Wallet::open(&path).unwrap();
assert!(wallet.check_password("TestPassword123").unwrap());
assert!(!wallet.check_password("WrongPassword").unwrap());
}
#[test]
fn test_check_password_after_reopen_enc500() {
let (_temp, path) = create_wallet_with_encryption_count(500);
let wallet = Wallet::open(&path).unwrap();
assert!(wallet.check_password("TestPassword123").unwrap());
assert!(!wallet.check_password("WrongPassword").unwrap());
}
#[test]
fn test_compact_items() {
let (mut wallet, _temp) = create_test_wallet();
let item_id = wallet.add_item("To Purge", "document", false, None).unwrap();
wallet.delete_item(&item_id).unwrap();
wallet.compact().unwrap();
let deleted = wallet.get_deleted_items().unwrap();
assert!(deleted.is_empty());
}
#[test]
fn test_compact_fields() {
let (mut wallet, _temp) = create_test_wallet();
let item_id = wallet.add_item("Item", "document", false, None).unwrap();
let field_id = wallet.add_field(&item_id, "MAIL", "purge@test.com", None).unwrap();
wallet.delete_field(&item_id, &field_id).unwrap();
wallet.compact().unwrap();
let deleted = wallet.get_deleted_fields().unwrap();
assert!(deleted.is_empty());
}
#[test]
fn test_compact_cascaded_fields() {
let (mut wallet, _temp) = create_test_wallet();
let item_id = wallet.add_item("Item", "document", false, None).unwrap();
wallet.add_field(&item_id, "MAIL", "orphan@test.com", None).unwrap();
wallet.add_field(&item_id, "PASS", "secret", None).unwrap();
wallet.delete_item(&item_id).unwrap();
wallet.compact().unwrap();
let deleted_fields = wallet.get_deleted_fields().unwrap();
assert!(deleted_fields.is_empty());
let deleted_items = wallet.get_deleted_items().unwrap();
assert!(deleted_items.is_empty());
}
#[test]
fn test_compact_returns_counts() {
let (mut wallet, _temp) = create_test_wallet();
let item1_id = wallet.add_item("Item 1", "document", false, None).unwrap();
let item2_id = wallet.add_item("Item 2", "document", false, None).unwrap();
let _field_id = wallet.add_field(&item1_id, "MAIL", "test@test.com", None).unwrap();
wallet.delete_item(&item1_id).unwrap();
wallet.delete_item(&item2_id).unwrap();
let (items_count, fields_count) = wallet.compact().unwrap();
assert_eq!(items_count, 2);
assert_eq!(fields_count, 1); }
#[test]
fn test_compact_empty() {
let (mut wallet, _temp) = create_test_wallet();
let (items, fields) = wallet.compact().unwrap();
assert_eq!(items, 0);
assert_eq!(fields, 0);
}
#[test]
fn test_compact_preserves_active_records() {
let (mut wallet, _temp) = create_test_wallet();
let item1 = wallet.add_item("Keep This", "document", false, None).unwrap();
wallet.add_field(&item1, "MAIL", "keep@test.com", None).unwrap();
let item2 = wallet.add_item("Delete This", "document", false, None).unwrap();
wallet.add_field(&item2, "PASS", "gone", None).unwrap();
wallet.delete_item(&item2).unwrap();
wallet.compact().unwrap();
let item = wallet.get_item(&item1).unwrap().unwrap();
assert_eq!(item.name, "Keep This");
let fields = wallet.get_fields_by_item(&item1).unwrap();
assert_eq!(fields.len(), 1);
assert_eq!(fields[0].value, "keep@test.com");
}
#[test]
fn test_compact_double_call_idempotent() {
let (mut wallet, _temp) = create_test_wallet();
let item_id = wallet.add_item("Delete Me", "document", false, None).unwrap();
wallet.delete_item(&item_id).unwrap();
let (i1, _f1) = wallet.compact().unwrap();
assert_eq!(i1, 1);
let (i2, f2) = wallet.compact().unwrap();
assert_eq!(i2, 0);
assert_eq!(f2, 0);
}
#[test]
fn test_compact_after_cascade_delete() {
let (mut wallet, _temp) = create_test_wallet();
let folder = wallet.add_item("Folder", "folder", true, None).unwrap();
let child1 = wallet.add_item("Child 1", "document", false, Some(&folder)).unwrap();
let child2 = wallet.add_item("Child 2", "document", false, Some(&folder)).unwrap();
wallet.add_field(&child1, "MAIL", "c1@test.com", None).unwrap();
wallet.add_field(&child2, "PASS", "secret", None).unwrap();
wallet.delete_item(&folder).unwrap();
let (items_count, fields_count) = wallet.compact().unwrap();
assert_eq!(items_count, 3); assert_eq!(fields_count, 2);
assert!(wallet.get_deleted_items().unwrap().is_empty());
assert!(wallet.get_deleted_fields().unwrap().is_empty());
}
#[test]
fn test_compact_mixed_deleted_and_cascaded_fields() {
let (mut wallet, _temp) = create_test_wallet();
let item_id = wallet.add_item("Item", "document", false, None).unwrap();
let f1 = wallet.add_field(&item_id, "MAIL", "test@test.com", None).unwrap();
wallet.add_field(&item_id, "PASS", "secret", None).unwrap();
wallet.delete_field(&item_id, &f1).unwrap();
wallet.delete_item(&item_id).unwrap();
let (items_count, fields_count) = wallet.compact().unwrap();
assert_eq!(items_count, 1);
assert_eq!(fields_count, 2);
}
#[test]
fn test_compact_with_active_and_deleted_fields_same_item() {
let (mut wallet, _temp) = create_test_wallet();
let item_id = wallet.add_item("Item", "document", false, None).unwrap();
let f_del = wallet.add_field(&item_id, "MAIL", "delete@me.com", None).unwrap();
wallet.add_field(&item_id, "PASS", "keep_me", None).unwrap();
wallet.delete_field(&item_id, &f_del).unwrap();
wallet.compact().unwrap();
assert!(wallet.get_deleted_fields().unwrap().is_empty());
let active = wallet.get_fields_by_item(&item_id).unwrap();
assert_eq!(active.len(), 1);
assert_eq!(active[0].value, "keep_me");
}
#[test]
fn test_database_stats() {
let (mut wallet, _temp) = create_test_wallet();
let folder = wallet.add_item("Folder", "folder", true, None).unwrap();
let item1 = wallet.add_item("Item 1", "document", false, None).unwrap();
let item2 = wallet.add_item("Item 2", "document", false, Some(&folder)).unwrap();
wallet.add_field(&item1, "MAIL", "a@a.com", None).unwrap();
wallet.add_field(&item1, "PASS", "secret", None).unwrap();
wallet.add_field(&item2, "NOTE", "note", None).unwrap();
wallet.delete_item(&item2).unwrap();
let stats = wallet.get_database_stats().unwrap();
assert_eq!(stats.total_items, 1); assert_eq!(stats.total_folders, 1); assert_eq!(stats.total_fields, 2); assert_eq!(stats.deleted_items, 1); assert_eq!(stats.deleted_fields, 1); assert!(stats.total_labels >= 19); assert!(stats.file_size_bytes > 0);
}
#[test]
fn test_change_password_reencrypts_deleted_items() {
let (mut wallet, _temp) = create_test_wallet();
let item_id = wallet.add_item("Deleted Item", "document", false, None).unwrap();
wallet.delete_item(&item_id).unwrap();
assert!(wallet.change_password("NewPassword456").unwrap());
let deleted = wallet.get_deleted_items().unwrap();
assert_eq!(deleted.len(), 1);
assert_eq!(deleted[0].name, "Deleted Item");
wallet.lock();
assert!(wallet.unlock("NewPassword456").unwrap());
let deleted2 = wallet.get_deleted_items().unwrap();
assert_eq!(deleted2.len(), 1);
assert_eq!(deleted2[0].name, "Deleted Item");
}
#[test]
fn test_change_password_reencrypts_deleted_fields() {
let (mut wallet, _temp) = create_test_wallet();
let item_id = wallet.add_item("Item", "document", false, None).unwrap();
let field_id = wallet.add_field(&item_id, "PASS", "my_secret", None).unwrap();
wallet.delete_field(&item_id, &field_id).unwrap();
assert!(wallet.change_password("NewPassword456").unwrap());
let deleted = wallet.get_deleted_fields().unwrap();
assert_eq!(deleted.len(), 1);
assert_eq!(deleted[0].value, "my_secret");
wallet.lock();
assert!(wallet.unlock("NewPassword456").unwrap());
let deleted2 = wallet.get_deleted_fields().unwrap();
assert_eq!(deleted2.len(), 1);
assert_eq!(deleted2[0].value, "my_secret");
}
#[test]
fn test_change_password_reencrypts_cascade_deleted_fields() {
let (mut wallet, _temp) = create_test_wallet();
let item_id = wallet.add_item("Item", "document", false, None).unwrap();
wallet.add_field(&item_id, "MAIL", "cascade@test.com", None).unwrap();
wallet.add_field(&item_id, "PASS", "cascade_secret", None).unwrap();
wallet.delete_item(&item_id).unwrap();
let deleted_before = wallet.get_deleted_fields().unwrap();
let our_fields: Vec<_> = deleted_before.iter().filter(|f| f.item_id == item_id).collect();
assert_eq!(our_fields.len(), 2);
assert!(wallet.change_password("NewPassword456").unwrap());
let deleted_after = wallet.get_deleted_fields().unwrap();
let our_fields_after: Vec<_> = deleted_after.iter().filter(|f| f.item_id == item_id).collect();
assert_eq!(our_fields_after.len(), 2);
let values: Vec<&str> = our_fields_after.iter().map(|f| f.value.as_str()).collect();
assert!(values.contains(&"cascade@test.com"));
assert!(values.contains(&"cascade_secret"));
wallet.lock();
assert!(wallet.unlock("NewPassword456").unwrap());
let deleted_reopen = wallet.get_deleted_fields().unwrap();
let our_fields_reopen: Vec<_> = deleted_reopen.iter().filter(|f| f.item_id == item_id).collect();
assert_eq!(our_fields_reopen.len(), 2);
}
#[test]
fn test_change_password_mixed_active_and_deleted() {
let (mut wallet, _temp) = create_test_wallet();
let active_item = wallet.add_item("Active Item", "document", false, None).unwrap();
wallet.add_field(&active_item, "MAIL", "active@test.com", None).unwrap();
let del_item = wallet.add_item("Deleted Item", "document", false, None).unwrap();
let del_field = wallet.add_field(&del_item, "PASS", "deleted_secret", None).unwrap();
wallet.delete_item(&del_item).unwrap();
wallet.delete_field(&del_item, &del_field).unwrap();
assert!(wallet.change_password("NewPassword456").unwrap());
let item = wallet.get_item(&active_item).unwrap().unwrap();
assert_eq!(item.name, "Active Item");
let fields = wallet.get_fields_by_item(&active_item).unwrap();
assert_eq!(fields[0].value, "active@test.com");
let deleted_items = wallet.get_deleted_items().unwrap();
assert_eq!(deleted_items.len(), 1);
assert_eq!(deleted_items[0].name, "Deleted Item");
let deleted_fields = wallet.get_deleted_fields().unwrap();
assert_eq!(deleted_fields.len(), 1);
assert_eq!(deleted_fields[0].value, "deleted_secret");
}
}