mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-09 15:58:48 +00:00
Merge pull request #170 from PR0M3TH3AN/codex/update-and-add-unit-tests-for-entries
Update tests for entries schema
This commit is contained in:
@@ -35,7 +35,7 @@ class BackupManager:
|
|||||||
timestamped filenames to facilitate easy identification and retrieval.
|
timestamped filenames to facilitate easy identification and retrieval.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BACKUP_FILENAME_TEMPLATE = "passwords_db_backup_{timestamp}.json.enc"
|
BACKUP_FILENAME_TEMPLATE = "entries_db_backup_{timestamp}.json.enc"
|
||||||
|
|
||||||
def __init__(self, fingerprint_dir: Path):
|
def __init__(self, fingerprint_dir: Path):
|
||||||
"""
|
"""
|
||||||
@@ -47,7 +47,7 @@ class BackupManager:
|
|||||||
self.fingerprint_dir = fingerprint_dir
|
self.fingerprint_dir = fingerprint_dir
|
||||||
self.backup_dir = self.fingerprint_dir / "backups"
|
self.backup_dir = self.fingerprint_dir / "backups"
|
||||||
self.backup_dir.mkdir(parents=True, exist_ok=True)
|
self.backup_dir.mkdir(parents=True, exist_ok=True)
|
||||||
self.index_file = self.fingerprint_dir / "seedpass_passwords_db.json.enc"
|
self.index_file = self.fingerprint_dir / "seedpass_entries_db.json.enc"
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"BackupManager initialized with backup directory at {self.backup_dir}"
|
f"BackupManager initialized with backup directory at {self.backup_dir}"
|
||||||
)
|
)
|
||||||
@@ -79,7 +79,7 @@ class BackupManager:
|
|||||||
def restore_latest_backup(self) -> None:
|
def restore_latest_backup(self) -> None:
|
||||||
try:
|
try:
|
||||||
backup_files = sorted(
|
backup_files = sorted(
|
||||||
self.backup_dir.glob("passwords_db_backup_*.json.enc"),
|
self.backup_dir.glob("entries_db_backup_*.json.enc"),
|
||||||
key=lambda x: x.stat().st_mtime,
|
key=lambda x: x.stat().st_mtime,
|
||||||
reverse=True,
|
reverse=True,
|
||||||
)
|
)
|
||||||
@@ -112,7 +112,7 @@ class BackupManager:
|
|||||||
def list_backups(self) -> None:
|
def list_backups(self) -> None:
|
||||||
try:
|
try:
|
||||||
backup_files = sorted(
|
backup_files = sorted(
|
||||||
self.backup_dir.glob("passwords_db_backup_*.json.enc"),
|
self.backup_dir.glob("entries_db_backup_*.json.enc"),
|
||||||
key=lambda x: x.stat().st_mtime,
|
key=lambda x: x.stat().st_mtime,
|
||||||
reverse=True,
|
reverse=True,
|
||||||
)
|
)
|
||||||
|
@@ -246,10 +246,10 @@ class EncryptionManager:
|
|||||||
|
|
||||||
:param data: The JSON data to save.
|
:param data: The JSON data to save.
|
||||||
:param relative_path: The relative path within the fingerprint directory where data will be saved.
|
:param relative_path: The relative path within the fingerprint directory where data will be saved.
|
||||||
Defaults to 'seedpass_passwords_db.json.enc'.
|
Defaults to 'seedpass_entries_db.json.enc'.
|
||||||
"""
|
"""
|
||||||
if relative_path is None:
|
if relative_path is None:
|
||||||
relative_path = Path("seedpass_passwords_db.json.enc")
|
relative_path = Path("seedpass_entries_db.json.enc")
|
||||||
try:
|
try:
|
||||||
json_data = json.dumps(data, indent=4).encode("utf-8")
|
json_data = json.dumps(data, indent=4).encode("utf-8")
|
||||||
self.encrypt_and_save_file(json_data, relative_path)
|
self.encrypt_and_save_file(json_data, relative_path)
|
||||||
@@ -273,11 +273,11 @@ class EncryptionManager:
|
|||||||
Decrypts and loads JSON data from the specified relative path within the fingerprint directory.
|
Decrypts and loads JSON data from the specified relative path within the fingerprint directory.
|
||||||
|
|
||||||
:param relative_path: The relative path within the fingerprint directory from which data will be loaded.
|
:param relative_path: The relative path within the fingerprint directory from which data will be loaded.
|
||||||
Defaults to 'seedpass_passwords_db.json.enc'.
|
Defaults to 'seedpass_entries_db.json.enc'.
|
||||||
:return: The decrypted JSON data as a dictionary.
|
:return: The decrypted JSON data as a dictionary.
|
||||||
"""
|
"""
|
||||||
if relative_path is None:
|
if relative_path is None:
|
||||||
relative_path = Path("seedpass_passwords_db.json.enc")
|
relative_path = Path("seedpass_entries_db.json.enc")
|
||||||
|
|
||||||
file_path = self.fingerprint_dir / relative_path
|
file_path = self.fingerprint_dir / relative_path
|
||||||
|
|
||||||
@@ -320,10 +320,10 @@ class EncryptionManager:
|
|||||||
Updates the checksum file for the specified file within the fingerprint directory.
|
Updates the checksum file for the specified file within the fingerprint directory.
|
||||||
|
|
||||||
:param relative_path: The relative path within the fingerprint directory for which the checksum will be updated.
|
:param relative_path: The relative path within the fingerprint directory for which the checksum will be updated.
|
||||||
Defaults to 'seedpass_passwords_db.json.enc'.
|
Defaults to 'seedpass_entries_db.json.enc'.
|
||||||
"""
|
"""
|
||||||
if relative_path is None:
|
if relative_path is None:
|
||||||
relative_path = Path("seedpass_passwords_db.json.enc")
|
relative_path = Path("seedpass_entries_db.json.enc")
|
||||||
try:
|
try:
|
||||||
file_path = self.fingerprint_dir / relative_path
|
file_path = self.fingerprint_dir / relative_path
|
||||||
logger.debug("Calculating checksum of the encrypted file bytes.")
|
logger.debug("Calculating checksum of the encrypted file bytes.")
|
||||||
@@ -368,7 +368,7 @@ class EncryptionManager:
|
|||||||
:return: Encrypted data as bytes or None if the index file does not exist.
|
:return: Encrypted data as bytes or None if the index file does not exist.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
relative_path = Path("seedpass_passwords_db.json.enc")
|
relative_path = Path("seedpass_entries_db.json.enc")
|
||||||
if not (self.fingerprint_dir / relative_path).exists():
|
if not (self.fingerprint_dir / relative_path).exists():
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Index file '{relative_path}' does not exist in '{self.fingerprint_dir}'."
|
f"Index file '{relative_path}' does not exist in '{self.fingerprint_dir}'."
|
||||||
@@ -407,10 +407,10 @@ class EncryptionManager:
|
|||||||
|
|
||||||
:param encrypted_data: The encrypted data retrieved from Nostr.
|
:param encrypted_data: The encrypted data retrieved from Nostr.
|
||||||
:param relative_path: The relative path within the fingerprint directory to update.
|
:param relative_path: The relative path within the fingerprint directory to update.
|
||||||
Defaults to 'seedpass_passwords_db.json.enc'.
|
Defaults to 'seedpass_entries_db.json.enc'.
|
||||||
"""
|
"""
|
||||||
if relative_path is None:
|
if relative_path is None:
|
||||||
relative_path = Path("seedpass_passwords_db.json.enc")
|
relative_path = Path("seedpass_entries_db.json.enc")
|
||||||
try:
|
try:
|
||||||
decrypted_data = self.decrypt_data(encrypted_data)
|
decrypted_data = self.decrypt_data(encrypted_data)
|
||||||
data = json.loads(decrypted_data.decode("utf-8"))
|
data = json.loads(decrypted_data.decode("utf-8"))
|
||||||
|
@@ -50,8 +50,8 @@ class EntryManager:
|
|||||||
self.fingerprint_dir = fingerprint_dir
|
self.fingerprint_dir = fingerprint_dir
|
||||||
|
|
||||||
# Use paths relative to the fingerprint directory
|
# Use paths relative to the fingerprint directory
|
||||||
self.index_file = self.fingerprint_dir / "seedpass_passwords_db.json.enc"
|
self.index_file = self.fingerprint_dir / "seedpass_entries_db.json.enc"
|
||||||
self.checksum_file = self.fingerprint_dir / "seedpass_passwords_db_checksum.txt"
|
self.checksum_file = self.fingerprint_dir / "seedpass_entries_db_checksum.txt"
|
||||||
|
|
||||||
logger.debug(f"EntryManager initialized with index file at {self.index_file}")
|
logger.debug(f"EntryManager initialized with index file at {self.index_file}")
|
||||||
|
|
||||||
@@ -410,7 +410,7 @@ class EntryManager:
|
|||||||
return
|
return
|
||||||
|
|
||||||
timestamp = int(time.time())
|
timestamp = int(time.time())
|
||||||
backup_filename = f"passwords_db_backup_{timestamp}.json.enc"
|
backup_filename = f"entries_db_backup_{timestamp}.json.enc"
|
||||||
backup_path = self.fingerprint_dir / backup_filename
|
backup_path = self.fingerprint_dir / backup_filename
|
||||||
|
|
||||||
with open(index_file_path, "rb") as original_file, open(
|
with open(index_file_path, "rb") as original_file, open(
|
||||||
|
@@ -769,7 +769,7 @@ class PasswordManager:
|
|||||||
|
|
||||||
def sync_index_from_nostr_if_missing(self) -> None:
|
def sync_index_from_nostr_if_missing(self) -> None:
|
||||||
"""Retrieve the password database from Nostr if it doesn't exist locally."""
|
"""Retrieve the password database from Nostr if it doesn't exist locally."""
|
||||||
index_file = self.fingerprint_dir / "seedpass_passwords_db.json.enc"
|
index_file = self.fingerprint_dir / "seedpass_entries_db.json.enc"
|
||||||
if index_file.exists():
|
if index_file.exists():
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
@@ -10,7 +10,7 @@ from .encryption import EncryptionManager
|
|||||||
class Vault:
|
class Vault:
|
||||||
"""Simple wrapper around :class:`EncryptionManager` for vault storage."""
|
"""Simple wrapper around :class:`EncryptionManager` for vault storage."""
|
||||||
|
|
||||||
INDEX_FILENAME = "seedpass_passwords_db.json.enc"
|
INDEX_FILENAME = "seedpass_entries_db.json.enc"
|
||||||
CONFIG_FILENAME = "seedpass_config.json.enc"
|
CONFIG_FILENAME = "seedpass_config.json.enc"
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@@ -17,7 +17,7 @@ def test_backup_restore_workflow(monkeypatch):
|
|||||||
vault, enc_mgr = create_vault(fp_dir, TEST_SEED, TEST_PASSWORD)
|
vault, enc_mgr = create_vault(fp_dir, TEST_SEED, TEST_PASSWORD)
|
||||||
backup_mgr = BackupManager(fp_dir)
|
backup_mgr = BackupManager(fp_dir)
|
||||||
|
|
||||||
index_file = fp_dir / "seedpass_passwords_db.json.enc"
|
index_file = fp_dir / "seedpass_entries_db.json.enc"
|
||||||
|
|
||||||
data1 = {
|
data1 = {
|
||||||
"schema_version": 2,
|
"schema_version": 2,
|
||||||
@@ -30,7 +30,7 @@ def test_backup_restore_workflow(monkeypatch):
|
|||||||
|
|
||||||
monkeypatch.setattr(time, "time", lambda: 1111)
|
monkeypatch.setattr(time, "time", lambda: 1111)
|
||||||
backup_mgr.create_backup()
|
backup_mgr.create_backup()
|
||||||
backup1 = fp_dir / "backups" / "passwords_db_backup_1111.json.enc"
|
backup1 = fp_dir / "backups" / "entries_db_backup_1111.json.enc"
|
||||||
assert backup1.exists()
|
assert backup1.exists()
|
||||||
assert backup1.stat().st_mode & 0o777 == 0o600
|
assert backup1.stat().st_mode & 0o777 == 0o600
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ def test_backup_restore_workflow(monkeypatch):
|
|||||||
|
|
||||||
monkeypatch.setattr(time, "time", lambda: 2222)
|
monkeypatch.setattr(time, "time", lambda: 2222)
|
||||||
backup_mgr.create_backup()
|
backup_mgr.create_backup()
|
||||||
backup2 = fp_dir / "backups" / "passwords_db_backup_2222.json.enc"
|
backup2 = fp_dir / "backups" / "entries_db_backup_2222.json.enc"
|
||||||
assert backup2.exists()
|
assert backup2.exists()
|
||||||
assert backup2.stat().st_mode & 0o777 == 0o600
|
assert backup2.stat().st_mode & 0o777 == 0o600
|
||||||
|
|
||||||
|
@@ -21,8 +21,8 @@ def test_encryption_checksum_workflow():
|
|||||||
manager.save_json_data(data)
|
manager.save_json_data(data)
|
||||||
manager.update_checksum()
|
manager.update_checksum()
|
||||||
|
|
||||||
enc_file = tmp_path / "seedpass_passwords_db.json.enc"
|
enc_file = tmp_path / "seedpass_entries_db.json.enc"
|
||||||
chk_file = tmp_path / "seedpass_passwords_db.json_checksum.txt"
|
chk_file = tmp_path / "seedpass_entries_db.json_checksum.txt"
|
||||||
|
|
||||||
checksum = chk_file.read_text().strip()
|
checksum = chk_file.read_text().strip()
|
||||||
assert re.fullmatch(r"[0-9a-f]{64}", checksum)
|
assert re.fullmatch(r"[0-9a-f]{64}", checksum)
|
||||||
|
@@ -20,7 +20,7 @@ def test_json_save_and_load_round_trip():
|
|||||||
loaded = manager.load_json_data()
|
loaded = manager.load_json_data()
|
||||||
assert loaded == data
|
assert loaded == data
|
||||||
|
|
||||||
file_path = Path(tmpdir) / "seedpass_passwords_db.json.enc"
|
file_path = Path(tmpdir) / "seedpass_entries_db.json.enc"
|
||||||
raw = file_path.read_bytes()
|
raw = file_path.read_bytes()
|
||||||
assert raw != json.dumps(data, indent=4).encode("utf-8")
|
assert raw != json.dumps(data, indent=4).encode("utf-8")
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
import pytest
|
||||||
from helpers import create_vault, TEST_SEED, TEST_PASSWORD
|
from helpers import create_vault, TEST_SEED, TEST_PASSWORD
|
||||||
|
|
||||||
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
||||||
@@ -30,3 +31,30 @@ def test_add_and_retrieve_entry():
|
|||||||
data = enc_mgr.load_json_data(entry_mgr.index_file)
|
data = enc_mgr.load_json_data(entry_mgr.index_file)
|
||||||
assert str(index) in data.get("entries", {})
|
assert str(index) in data.get("entries", {})
|
||||||
assert data["entries"][str(index)] == entry
|
assert data["entries"][str(index)] == entry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"method, expected_type",
|
||||||
|
[
|
||||||
|
("add_entry", "password"),
|
||||||
|
("add_totp", "totp"),
|
||||||
|
("add_ssh_key", "ssh"),
|
||||||
|
("add_seed", "seed"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_round_trip_entry_types(method, expected_type):
|
||||||
|
with TemporaryDirectory() as tmpdir:
|
||||||
|
vault, enc_mgr = create_vault(Path(tmpdir), TEST_SEED, TEST_PASSWORD)
|
||||||
|
entry_mgr = EntryManager(vault, Path(tmpdir))
|
||||||
|
|
||||||
|
if method == "add_entry":
|
||||||
|
index = entry_mgr.add_entry("example.com", 8)
|
||||||
|
else:
|
||||||
|
with pytest.raises(NotImplementedError):
|
||||||
|
getattr(entry_mgr, method)()
|
||||||
|
index = 0
|
||||||
|
|
||||||
|
entry = entry_mgr.retrieve_entry(index)
|
||||||
|
assert entry["type"] == expected_type
|
||||||
|
data = enc_mgr.load_json_data(entry_mgr.index_file)
|
||||||
|
assert data["entries"][str(index)]["type"] == expected_type
|
||||||
|
@@ -19,7 +19,7 @@ def test_update_checksum_writes_to_expected_path():
|
|||||||
vault.save_index({"entries": {}})
|
vault.save_index({"entries": {}})
|
||||||
entry_mgr.update_checksum()
|
entry_mgr.update_checksum()
|
||||||
|
|
||||||
expected = tmp_path / "seedpass_passwords_db_checksum.txt"
|
expected = tmp_path / "seedpass_entries_db_checksum.txt"
|
||||||
assert expected.exists()
|
assert expected.exists()
|
||||||
|
|
||||||
|
|
||||||
@@ -32,5 +32,5 @@ def test_backup_index_file_creates_backup_in_directory():
|
|||||||
vault.save_index({"entries": {}})
|
vault.save_index({"entries": {}})
|
||||||
entry_mgr.backup_index_file()
|
entry_mgr.backup_index_file()
|
||||||
|
|
||||||
backups = list(tmp_path.glob("passwords_db_backup_*.json.enc"))
|
backups = list(tmp_path.glob("entries_db_backup_*.json.enc"))
|
||||||
assert len(backups) == 1
|
assert len(backups) == 1
|
||||||
|
@@ -64,9 +64,9 @@ def test_manager_workflow(monkeypatch):
|
|||||||
|
|
||||||
pm.handle_add_password()
|
pm.handle_add_password()
|
||||||
assert pm.is_dirty is False
|
assert pm.is_dirty is False
|
||||||
backups = list(tmp_path.glob("passwords_db_backup_*.json.enc"))
|
backups = list(tmp_path.glob("entries_db_backup_*.json.enc"))
|
||||||
assert len(backups) == 1
|
assert len(backups) == 1
|
||||||
checksum_file = tmp_path / "seedpass_passwords_db_checksum.txt"
|
checksum_file = tmp_path / "seedpass_entries_db_checksum.txt"
|
||||||
assert checksum_file.exists()
|
assert checksum_file.exists()
|
||||||
checksum_after_add = checksum_file.read_text()
|
checksum_after_add = checksum_file.read_text()
|
||||||
first_post = pm.nostr_client.published[-1]
|
first_post = pm.nostr_client.published[-1]
|
||||||
@@ -79,7 +79,7 @@ def test_manager_workflow(monkeypatch):
|
|||||||
assert pm.is_dirty is False
|
assert pm.is_dirty is False
|
||||||
pm.backup_manager.create_backup()
|
pm.backup_manager.create_backup()
|
||||||
backup_dir = tmp_path / "backups"
|
backup_dir = tmp_path / "backups"
|
||||||
backups_mod = list(backup_dir.glob("passwords_db_backup_*.json.enc"))
|
backups_mod = list(backup_dir.glob("entries_db_backup_*.json.enc"))
|
||||||
assert backups_mod
|
assert backups_mod
|
||||||
checksum_after_modify = checksum_file.read_text()
|
checksum_after_modify = checksum_file.read_text()
|
||||||
assert checksum_after_modify != checksum_after_add
|
assert checksum_after_modify != checksum_after_add
|
||||||
|
@@ -23,6 +23,21 @@ def test_migrate_v0_to_v2(tmp_path: Path):
|
|||||||
assert data["entries"]["0"] == expected_entry
|
assert data["entries"]["0"] == expected_entry
|
||||||
|
|
||||||
|
|
||||||
|
def test_migrate_v1_to_v2(tmp_path: Path):
|
||||||
|
enc_mgr, vault = setup(tmp_path)
|
||||||
|
legacy = {"schema_version": 1, "passwords": {"0": {"website": "b", "length": 10}}}
|
||||||
|
enc_mgr.save_json_data(legacy)
|
||||||
|
data = vault.load_index()
|
||||||
|
assert data["schema_version"] == LATEST_VERSION
|
||||||
|
expected_entry = {
|
||||||
|
"website": "b",
|
||||||
|
"length": 10,
|
||||||
|
"type": "password",
|
||||||
|
"notes": "",
|
||||||
|
}
|
||||||
|
assert data["entries"]["0"] == expected_entry
|
||||||
|
|
||||||
|
|
||||||
def test_error_on_future_version(tmp_path: Path):
|
def test_error_on_future_version(tmp_path: Path):
|
||||||
enc_mgr, vault = setup(tmp_path)
|
enc_mgr, vault = setup(tmp_path)
|
||||||
future = {"schema_version": LATEST_VERSION + 1, "entries": {}}
|
future = {"schema_version": LATEST_VERSION + 1, "entries": {}}
|
||||||
|
@@ -6,6 +6,7 @@ from hypothesis import given, strategies as st, settings
|
|||||||
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
||||||
|
|
||||||
from password_manager.password_generation import PasswordGenerator
|
from password_manager.password_generation import PasswordGenerator
|
||||||
|
from password_manager.entry_types import EntryType
|
||||||
|
|
||||||
|
|
||||||
class DummyEnc:
|
class DummyEnc:
|
||||||
@@ -32,8 +33,10 @@ def make_generator():
|
|||||||
@settings(deadline=None)
|
@settings(deadline=None)
|
||||||
def test_password_properties(length, index):
|
def test_password_properties(length, index):
|
||||||
pg = make_generator()
|
pg = make_generator()
|
||||||
|
entry_type = EntryType.PASSWORD.value
|
||||||
pw1 = pg.generate_password(length=length, index=index)
|
pw1 = pg.generate_password(length=length, index=index)
|
||||||
pw2 = pg.generate_password(length=length, index=index)
|
pw2 = pg.generate_password(length=length, index=index)
|
||||||
|
assert entry_type == "password"
|
||||||
|
|
||||||
assert pw1 == pw2
|
assert pw1 == pw2
|
||||||
assert len(pw1) == length
|
assert len(pw1) == length
|
||||||
|
Reference in New Issue
Block a user