From 47d9e9d8e4d34e00d097426f8c5ffad3ac1abeed Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:16:18 -0400 Subject: [PATCH] Add migration to version 2 and update tests --- src/password_manager/encryption.py | 2 +- src/password_manager/entry_management.py | 31 +++++++++---------- src/password_manager/migrations.py | 15 ++++++++- src/tests/test_backup_restore.py | 22 +++++++++---- src/tests/test_entry_add.py | 4 +-- .../test_entry_management_checksum_path.py | 4 +-- src/tests/test_index_import_export.py | 16 ++++++++-- src/tests/test_migrations.py | 7 +++-- .../test_password_unlock_after_change.py | 2 +- src/tests/test_profile_management.py | 4 +-- 10 files changed, 70 insertions(+), 37 deletions(-) diff --git a/src/password_manager/encryption.py b/src/password_manager/encryption.py index f63fcbf..cd4cd73 100644 --- a/src/password_manager/encryption.py +++ b/src/password_manager/encryption.py @@ -291,7 +291,7 @@ class EncryptionManager: "yellow", ) ) - return {"passwords": {}} + return {"entries": {}} try: decrypted_data = self.decrypt_file(relative_path) diff --git a/src/password_manager/entry_management.py b/src/password_manager/entry_management.py index 26f4008..00c07c3 100644 --- a/src/password_manager/entry_management.py +++ b/src/password_manager/entry_management.py @@ -62,12 +62,12 @@ class EntryManager: return data except Exception as e: logger.error(f"Failed to load index: {e}") - return {"schema_version": LATEST_VERSION, "passwords": {}} + return {"schema_version": LATEST_VERSION, "entries": {}} else: logger.info( f"Index file '{self.index_file}' not found. Initializing new password database." ) - return {"schema_version": LATEST_VERSION, "passwords": {}} + return {"schema_version": LATEST_VERSION, "entries": {}} def _save_index(self, data: Dict[str, Any]) -> None: try: @@ -85,8 +85,8 @@ class EntryManager: """ try: data = self.vault.load_index() - if "passwords" in data and isinstance(data["passwords"], dict): - indices = [int(idx) for idx in data["passwords"].keys()] + if "entries" in data and isinstance(data["entries"], dict): + indices = [int(idx) for idx in data["entries"].keys()] next_index = max(indices) + 1 if indices else 0 else: next_index = 0 @@ -119,7 +119,8 @@ class EntryManager: index = self.get_next_index() data = self.vault.load_index() - data["passwords"][str(index)] = { + data.setdefault("entries", {}) + data["entries"][str(index)] = { "website": website_name, "length": length, "username": username if username else "", @@ -127,9 +128,7 @@ class EntryManager: "blacklisted": blacklisted, } - logger.debug( - f"Added entry at index {index}: {data['passwords'][str(index)]}" - ) + logger.debug(f"Added entry at index {index}: {data['entries'][str(index)]}") self._save_index(data) self.update_checksum() @@ -169,7 +168,7 @@ class EntryManager: """ try: data = self.vault.load_index() - entry = data.get("passwords", {}).get(str(index)) + entry = data.get("entries", {}).get(str(index)) if entry: logger.debug(f"Retrieved entry at index {index}: {entry}") @@ -205,7 +204,7 @@ class EntryManager: """ try: data = self.vault.load_index() - entry = data.get("passwords", {}).get(str(index)) + entry = data.get("entries", {}).get(str(index)) if not entry: logger.warning( @@ -233,7 +232,7 @@ class EntryManager: f"Updated blacklist status to '{blacklisted}' for index {index}." ) - data["passwords"][str(index)] = entry + data["entries"][str(index)] = entry logger.debug(f"Modified entry at index {index}: {entry}") self._save_index(data) @@ -259,15 +258,15 @@ class EntryManager: """ try: data = self.vault.load_index() - passwords = data.get("passwords", {}) + entries_data = data.get("entries", {}) - if not passwords: + if not entries_data: logger.info("No password entries found.") print(colored("No password entries found.", "yellow")) return [] entries = [] - for idx, entry in sorted(passwords.items(), key=lambda x: int(x[0])): + for idx, entry in sorted(entries_data.items(), key=lambda x: int(x[0])): entries.append( ( int(idx), @@ -302,8 +301,8 @@ class EntryManager: """ try: data = self.vault.load_index() - if "passwords" in data and str(index) in data["passwords"]: - del data["passwords"][str(index)] + if "entries" in data and str(index) in data["entries"]: + del data["entries"][str(index)] logger.debug(f"Deleted entry at index {index}.") self.vault.save_index(data) self.update_checksum() diff --git a/src/password_manager/migrations.py b/src/password_manager/migrations.py index 5984279..3e20295 100644 --- a/src/password_manager/migrations.py +++ b/src/password_manager/migrations.py @@ -26,7 +26,20 @@ def _v0_to_v1(data: dict) -> dict: return data -LATEST_VERSION = 1 +@migration(1) +def _v1_to_v2(data: dict) -> dict: + passwords = data.pop("passwords", {}) + entries = {} + for k, v in passwords.items(): + v.setdefault("type", "password") + v.setdefault("notes", "") + entries[k] = v + data["entries"] = entries + data["schema_version"] = 2 + return data + + +LATEST_VERSION = 2 def apply_migrations(data: dict) -> dict: diff --git a/src/tests/test_backup_restore.py b/src/tests/test_backup_restore.py index 08e2f3e..dbc1a24 100644 --- a/src/tests/test_backup_restore.py +++ b/src/tests/test_backup_restore.py @@ -19,7 +19,12 @@ def test_backup_restore_workflow(monkeypatch): index_file = fp_dir / "seedpass_passwords_db.json.enc" - data1 = {"passwords": {"0": {"website": "a", "length": 10}}} + data1 = { + "schema_version": 2, + "entries": { + "0": {"website": "a", "length": 10, "type": "password", "notes": ""} + }, + } vault.save_index(data1) os.utime(index_file, (1, 1)) @@ -29,7 +34,12 @@ def test_backup_restore_workflow(monkeypatch): assert backup1.exists() assert backup1.stat().st_mode & 0o777 == 0o600 - data2 = {"passwords": {"0": {"website": "b", "length": 12}}} + data2 = { + "schema_version": 2, + "entries": { + "0": {"website": "b", "length": 12, "type": "password", "notes": ""} + }, + } vault.save_index(data2) os.utime(index_file, (2, 2)) @@ -39,13 +49,13 @@ def test_backup_restore_workflow(monkeypatch): assert backup2.exists() assert backup2.stat().st_mode & 0o777 == 0o600 - vault.save_index({"passwords": {"temp": {}}}) + vault.save_index({"schema_version": 2, "entries": {"temp": {}}}) backup_mgr.restore_latest_backup() - assert vault.load_index()["passwords"] == data2["passwords"] + assert vault.load_index()["entries"] == data2["entries"] - vault.save_index({"passwords": {}}) + vault.save_index({"schema_version": 2, "entries": {}}) backup_mgr.restore_backup_by_timestamp(1111) - assert vault.load_index()["passwords"] == data1["passwords"] + assert vault.load_index()["entries"] == data1["entries"] backup1.unlink() current = vault.load_index() diff --git a/src/tests/test_entry_add.py b/src/tests/test_entry_add.py index 30ebab4..08d53e4 100644 --- a/src/tests/test_entry_add.py +++ b/src/tests/test_entry_add.py @@ -26,5 +26,5 @@ def test_add_and_retrieve_entry(): } data = enc_mgr.load_json_data(entry_mgr.index_file) - assert str(index) in data.get("passwords", {}) - assert data["passwords"][str(index)] == entry + assert str(index) in data.get("entries", {}) + assert data["entries"][str(index)] == entry diff --git a/src/tests/test_entry_management_checksum_path.py b/src/tests/test_entry_management_checksum_path.py index b1f6e13..fb48aee 100644 --- a/src/tests/test_entry_management_checksum_path.py +++ b/src/tests/test_entry_management_checksum_path.py @@ -16,7 +16,7 @@ def test_update_checksum_writes_to_expected_path(): entry_mgr = EntryManager(vault, tmp_path) # create an empty index file - vault.save_index({"passwords": {}}) + vault.save_index({"entries": {}}) entry_mgr.update_checksum() expected = tmp_path / "seedpass_passwords_db_checksum.txt" @@ -29,7 +29,7 @@ def test_backup_index_file_creates_backup_in_directory(): vault, enc_mgr = create_vault(tmp_path, TEST_SEED, TEST_PASSWORD) entry_mgr = EntryManager(vault, tmp_path) - vault.save_index({"passwords": {}}) + vault.save_index({"entries": {}}) entry_mgr.backup_index_file() backups = list(tmp_path.glob("passwords_db_backup_*.json.enc")) diff --git a/src/tests/test_index_import_export.py b/src/tests/test_index_import_export.py index 87ec285..fce78c2 100644 --- a/src/tests/test_index_import_export.py +++ b/src/tests/test_index_import_export.py @@ -30,17 +30,27 @@ def test_index_export_import_round_trip(): tmp = Path(td) vault = setup_vault(tmp) - original = {"passwords": {"0": {"website": "example"}}} + original = { + "schema_version": 2, + "entries": {"0": {"website": "example", "type": "password", "notes": ""}}, + } vault.save_index(original) encrypted = vault.get_encrypted_index() assert isinstance(encrypted, bytes) - vault.save_index({"passwords": {"0": {"website": "changed"}}}) + vault.save_index( + { + "schema_version": 2, + "entries": { + "0": {"website": "changed", "type": "password", "notes": ""} + }, + } + ) vault.decrypt_and_save_index_from_nostr(encrypted) loaded = vault.load_index() - assert loaded["passwords"] == original["passwords"] + assert loaded["entries"] == original["entries"] def test_get_encrypted_index_missing_file(tmp_path): diff --git a/src/tests/test_migrations.py b/src/tests/test_migrations.py index c212c2a..feb71aa 100644 --- a/src/tests/test_migrations.py +++ b/src/tests/test_migrations.py @@ -13,18 +13,19 @@ def setup(tmp_path: Path): return enc_mgr, vault -def test_migrate_v0_to_v1(tmp_path: Path): +def test_migrate_v0_to_v2(tmp_path: Path): enc_mgr, vault = setup(tmp_path) legacy = {"passwords": {"0": {"website": "a", "length": 8}}} enc_mgr.save_json_data(legacy) data = vault.load_index() assert data["schema_version"] == LATEST_VERSION - assert data["passwords"] == legacy["passwords"] + expected_entry = {"website": "a", "length": 8, "type": "password", "notes": ""} + assert data["entries"]["0"] == expected_entry def test_error_on_future_version(tmp_path: Path): enc_mgr, vault = setup(tmp_path) - future = {"schema_version": LATEST_VERSION + 1, "passwords": {}} + future = {"schema_version": LATEST_VERSION + 1, "entries": {}} enc_mgr.save_json_data(future) with pytest.raises(ValueError): vault.load_index() diff --git a/src/tests/test_password_unlock_after_change.py b/src/tests/test_password_unlock_after_change.py index c548135..5fabc93 100644 --- a/src/tests/test_password_unlock_after_change.py +++ b/src/tests/test_password_unlock_after_change.py @@ -32,7 +32,7 @@ def test_password_change_and_unlock(monkeypatch): entry_mgr = EntryManager(vault, fp) cfg_mgr = ConfigManager(vault, fp) - vault.save_index({"passwords": {}}) + vault.save_index({"entries": {}}) cfg_mgr.save_config( { "relays": [], diff --git a/src/tests/test_profile_management.py b/src/tests/test_profile_management.py index de0635b..6aab635 100644 --- a/src/tests/test_profile_management.py +++ b/src/tests/test_profile_management.py @@ -58,7 +58,7 @@ def test_add_and_delete_entry(monkeypatch): pm.entry_manager = entry_mgr index = entry_mgr.add_entry("example.com", 12) - assert str(index) in vault.load_index()["passwords"] + assert str(index) in vault.load_index()["entries"] published = [] pm.nostr_client = SimpleNamespace( @@ -73,5 +73,5 @@ def test_add_and_delete_entry(monkeypatch): pm.delete_entry() - assert str(index) not in vault.load_index()["passwords"] + assert str(index) not in vault.load_index()["entries"] assert published