Files
versioning/tests/test_dbsafety.py
T

127 lines
4.1 KiB
Python
Raw Normal View History

"""Zero-error policy for live databases: only verified snapshots are stored."""
import sqlite3
import pytest
from versiond import dbsafe
from versiond.db import Database
from versiond.filters import Filters
from versiond.ingest import Rejected, Repository
from versiond.store import BlobStore
def _repo(tmp_path):
db = Database(tmp_path / "index.sqlite")
return Repository(db, BlobStore(tmp_path / "spool"), Filters())
def _open_db(path, **kw):
conn = sqlite3.connect(path, **kw)
conn.execute("CREATE TABLE IF NOT EXISTS t(id INTEGER PRIMARY KEY, v TEXT)")
return conn
def _read_snapshot_bytes(repo, path):
data, _ = repo.read_file(path)
return data
def _rows_in_snapshot(data):
import tempfile, os
fd, tmp = tempfile.mkstemp(suffix=".sqlite")
try:
with os.fdopen(fd, "wb") as fh:
fh.write(data)
conn = sqlite3.connect(f"file:{tmp}?mode=ro", uri=True)
try:
assert conn.execute("PRAGMA integrity_check").fetchone()[0] == "ok"
return conn.execute("SELECT v FROM t ORDER BY id").fetchall()
finally:
conn.close()
finally:
os.unlink(tmp)
def test_sqlite_snapshot_roundtrip(tmp_path):
repo = _repo(tmp_path)
db_path = str(tmp_path / "app.db")
conn = _open_db(db_path)
conn.execute("INSERT INTO t(v) VALUES ('a'), ('b')")
conn.commit()
data = _read_snapshot_bytes(repo, db_path)
assert _rows_in_snapshot(data) == [("a",), ("b",)]
result = repo.commit(db_path, data, "test", "edit")
assert result["status"] == "committed"
conn.close()
def test_snapshot_during_uncommitted_write_is_consistent(tmp_path):
"""A writer with an open transaction must not produce a torn version."""
repo = _repo(tmp_path)
db_path = str(tmp_path / "live.db")
writer = _open_db(db_path, isolation_level=None)
writer.execute("INSERT INTO t(v) VALUES ('committed')")
writer.execute("BEGIN IMMEDIATE")
writer.execute("INSERT INTO t(v) VALUES ('uncommitted')")
try:
data = _read_snapshot_bytes(repo, db_path)
finally:
writer.execute("ROLLBACK")
writer.close()
rows = _rows_in_snapshot(data)
assert rows == [("committed",)] # committed state only, verified clean
def test_wal_mode_snapshot(tmp_path):
repo = _repo(tmp_path)
db_path = str(tmp_path / "wal.db")
conn = _open_db(db_path)
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("INSERT INTO t(v) VALUES ('w1')")
conn.commit()
conn.execute("INSERT INTO t(v) VALUES ('w2')") # may sit in -wal
data = _read_snapshot_bytes(repo, db_path)
conn.close()
rows = _rows_in_snapshot(data)
assert ("w1",) in rows # committed data present, snapshot verified
def test_corrupt_sqlite_magic_rejected(tmp_path):
repo = _repo(tmp_path)
bad = tmp_path / "bad.db"
bad.write_bytes(dbsafe.SQLITE_MAGIC + b"\x00" * 100 + b"garbage")
with pytest.raises(Rejected) as exc:
repo.read_file(str(bad))
assert exc.value.reason in ("database-corrupt", "database-unreadable")
def test_locked_database_rejected_not_stored(tmp_path):
repo = _repo(tmp_path)
db_path = str(tmp_path / "locked.db")
holder = _open_db(db_path, isolation_level=None)
holder.execute("INSERT INTO t(v) VALUES ('x')")
holder.execute("BEGIN EXCLUSIVE")
try:
with pytest.raises(dbsafe.DatabaseUnsafe) as exc:
dbsafe.snapshot_sqlite(db_path, 10_485_760, timeout=0.3)
assert exc.value.reason == "database-locked"
finally:
holder.execute("ROLLBACK")
holder.close()
# After the lock clears, the same file snapshots fine.
assert _rows_in_snapshot(_read_snapshot_bytes(repo, db_path)) == [("x",)]
def test_pushed_sqlite_bytes_verified(tmp_path):
repo = _repo(tmp_path)
db_path = str(tmp_path / "push.db")
with pytest.raises(Rejected):
repo.check_content(dbsafe.SQLITE_MAGIC + b"\x00" * 100 + b"garbage")
# Genuine DB bytes pass.
conn = _open_db(db_path)
conn.execute("INSERT INTO t(v) VALUES ('ok')")
conn.commit()
conn.close()
repo.check_content((tmp_path / "push.db").read_bytes())