food-records/tests/test_storage.py

135 lines
4.7 KiB
Python
Raw Normal View History

import json
from decimal import Decimal
import pytest
from app import crypto, journal
from app.storage import Vault, content_hash, migrate
def test_create_and_reopen(tmp_path):
path = tmp_path / "vault.fmdb"
vault = Vault.create(path, "pw")
journal.create_product(vault, "Булка с маком", cost_price=20, retail_price=35)
vault.save()
reopened = Vault.open(path, "pw")
assert [p.name for p in reopened.doc.products] == ["Булка с маком"]
assert reopened.doc.products[0].cost_price == Decimal("20.00")
def test_open_with_wrong_password(tmp_path):
path = tmp_path / "vault.fmdb"
Vault.create(path, "pw")
with pytest.raises(crypto.WrongPassword):
Vault.open(path, "nope")
def test_create_refuses_to_clobber(tmp_path):
path = tmp_path / "vault.fmdb"
Vault.create(path, "pw")
with pytest.raises(crypto.VaultError):
Vault.create(path, "other")
def test_save_skips_write_when_nothing_changed(tmp_path):
"""Главная защита от мусорных коммитов в git.
Шифртекст каждый раз новый, поэтому решение «писать или нет» принимается
по хешу открытых данных, а не по байтам файла.
"""
path = tmp_path / "vault.fmdb"
vault = Vault.create(path, "pw")
journal.create_product(vault, "Булка", cost_price=10, retail_price=20)
assert vault.save() is True
assert vault.save() is False
assert vault.save() is False
def test_save_detects_real_change(tmp_path):
vault = Vault.create(tmp_path / "vault.fmdb", "pw")
vault.save()
journal.create_product(vault, "Булка", cost_price=10, retail_price=20)
assert vault.is_dirty is True
assert vault.save() is True
assert vault.is_dirty is False
def test_freshly_opened_vault_is_not_dirty(tmp_path):
"""Иначе каждое открытие программы порождало бы коммит на пустом месте."""
path = tmp_path / "vault.fmdb"
vault = Vault.create(path, "pw")
journal.create_product(vault, "Булка", cost_price=10, retail_price=20)
vault.save()
reopened = Vault.open(path, "pw")
assert reopened.is_dirty is False
assert reopened.save() is False
def test_content_hash_ignores_key_order():
assert content_hash({"a": 1, "b": 2}) == content_hash({"b": 2, "a": 1})
def test_change_password(tmp_path):
path = tmp_path / "vault.fmdb"
vault = Vault.create(path, "old")
journal.create_product(vault, "Булка", cost_price=10, retail_price=20)
vault.save()
vault.change_password("new")
with pytest.raises(crypto.WrongPassword):
Vault.open(path, "old")
assert Vault.open(path, "new").doc.products[0].name == "Булка"
def test_atomic_write_leaves_backup(tmp_path):
path = tmp_path / "vault.fmdb"
vault = Vault.create(path, "pw")
journal.create_product(vault, "Булка", cost_price=10, retail_price=20)
vault.save()
assert path.with_suffix(".fmdb.bak").exists()
assert not path.with_suffix(".fmdb.tmp").exists()
def test_decimals_survive_round_trip(tmp_path):
"""Деньги ходят через строки, поэтому копейки не должны разъезжаться."""
path = tmp_path / "vault.fmdb"
vault = Vault.create(path, "pw")
journal.create_product(vault, "Булка", cost_price="33.33", retail_price="66.67")
vault.save()
product = Vault.open(path, "pw").doc.products[0]
assert product.cost_price == Decimal("33.33")
assert product.retail_price == Decimal("66.67")
def test_export_plain_json(tmp_path):
vault = Vault.create(tmp_path / "vault.fmdb", "pw")
journal.create_product(vault, "Булка с маком", cost_price=20, retail_price=35)
out = vault.export_plain_json(tmp_path / "export.json")
data = json.loads(out.read_text(encoding="utf-8"))
assert data["products"][0]["name"] == "Булка с маком"
def test_migration_adds_journal():
raw = migrate({"schema_version": 0, "products": []})
assert raw["schema_version"] == 1
assert raw["journal"] == []
def test_future_schema_refuses_to_open():
with pytest.raises(crypto.UnsupportedFormat, match="новой версией"):
migrate({"schema_version": 999})
def test_corrupted_json_inside_valid_encryption(tmp_path):
"""GCM подтвердил целостность, но внутри не JSON — сообщаем внятно."""
path = tmp_path / "vault.fmdb"
path.write_bytes(crypto.encrypt(b"not json at all", "pw"))
with pytest.raises(crypto.VaultError, match="повреждено"):
Vault.open(path, "pw")