Files
versioning/tests/test_units.py
T
retoor 3499f6cda0 Remove encryption; user-data filters; SQLite safety; recovery, retention, scheduler
- No client-side encryption: plaintext content-addressed remote (SHA-256
  names), no backup key, no cryptography dependency (server disk encryption
  is the trust model).
- Filters back user data: images, archives, databases, PDFs accepted; 10 MiB
  cap; binary sniffing removed; temp names hardened (~$, #..#, .temp).
- SQLite zero-error policy: backup-API snapshots + integrity_check, journal
  folding, locked/corrupt loud skips, verified restores.
- Recovery: reindex from manifests, remote adopt, on-demand blob fetch,
  5-day retention + thinning, GC, date-guarded remote purge, metrics.
- Scheduler with WebDAV quota signal and 70% pressure backstop (floor kept).
- 37 tests incl. live-monitor capture safety and DB safety.
2026-10-10 03:41:36 +02:00

118 lines
4.4 KiB
Python

import asyncio
from pathlib import PurePath
from versiond.db import Database
from versiond.diff import hunks, unified
from versiond.filters import Filters
from versiond.ingest import Coalescer, Repository
from versiond.store import BlobStore
def test_filters_paths():
f = Filters()
ok = ["app.py", "src/main.go", ".env", ".env.production", "Makefile", "pkg/package.json", ".gitignore",
# user data: images, archives, databases, documents (backed up as raw files)
"photos/vacation.jpg", "img/logo.png", "backup/site.tar.gz", "data/archive.zip",
"app.db", "app.sqlite3", "report.pdf", "song.mp3", "clip.mp4"]
for p in ok:
assert f.path_reason(PurePath(p)) is None, p
journals = ["app.db-wal", "app.db-shm", "app.sqlite-journal", "data/x-wal"]
for p in journals:
assert f.path_reason(PurePath(p)) == "database-journal", p
rejected = {
"node_modules/x/index.js": "ignored-directory",
".git/config": "ignored-directory",
"src/__pycache__/a.cpython-312.pyc": "ignored-directory",
"a.pyc": "ignored-extension",
".bashrc_local": "hidden-file",
"notes.txt~": "temporary-file",
"main.py.swp": "ignored-extension",
"draft.temp": "ignored-extension",
"x.kate-swp": "ignored-extension",
"~$lock.docx": "temporary-file",
"#autosave#": "temporary-file",
"venv/lib/x.py": "ignored-directory",
"target/debug/build.rs": "ignored-directory",
"4913": "temporary-file",
"src/pkg.egg-info/PKG-INFO": "ignored-directory",
}
for p, reason in rejected.items():
assert f.path_reason(PurePath(p)) == reason, p
assert f.size_reason(10_485_760) is None
assert f.size_reason(10_485_761) == "file-too-large"
def test_filter_patterns():
f = Filters(patterns=("*.log", "secret/*"))
assert f.path_reason(PurePath("x/debug.log")) == "ignored-pattern"
assert f.path_reason(PurePath("secret/key.txt")) == "ignored-pattern"
assert f.path_reason(PurePath("src/key.txt")) is None
def test_directory_patterns():
f = Filters(patterns=("app/data/uploads",))
assert f.skip_dir("uploads", "app/data/uploads")
assert not f.skip_dir("uploads", "other/uploads")
assert f.path_reason(PurePath("app/data/uploads/a/b.txt")) == "ignored-pattern"
assert f.path_reason(PurePath("app/data/config.json")) is None
def test_diff_unified():
a = b"one\ntwo\nthree\n"
b = b"one\n2\nthree\nfour\n"
out = unified(hunks(a, b), "a", "b")
assert "-two\n+2\n" in out and "+four\n" in out
assert hunks(b"x y\n", b"x y\n", ignore_whitespace=True) == []
def _repo(tmp_path):
db = Database(tmp_path / "index.sqlite")
return Repository(db, BlobStore(tmp_path / "spool"), Filters())
def test_coalescer_keeps_first_and_last(tmp_path):
repo = _repo(tmp_path)
path = str(tmp_path / "f.py")
async def run():
c = Coalescer(repo, window=0.2, max_hold=1.0, per_hour=100)
statuses = [c.submit(path, f"v{i}\n".encode(), "test", "edit")["status"] for i in range(6)]
await asyncio.sleep(0.5)
return statuses
statuses = asyncio.run(run())
assert statuses[0] == "committed"
assert set(statuses[1:]) == {"coalesced"}
file = repo.db.file_by_path(path)
contents = [repo.blobs.get(v["blob_sha256"]) for v in reversed(repo.db.history(file["id"]))]
assert contents == [b"v0\n", b"v5\n"]
def test_coalescer_rate_limit_keeps_newest(tmp_path):
repo = _repo(tmp_path)
path = str(tmp_path / "f.py")
async def run():
c = Coalescer(repo, window=0.05, max_hold=0.1, per_hour=2)
c.submit(path, b"a", "t", "e")
await asyncio.sleep(0.1)
c.submit(path, b"b", "t", "e")
await asyncio.sleep(0.2)
status = c.submit(path, b"c", "t", "e")["status"]
await asyncio.sleep(0.2)
flushed = c.flush()
return status, flushed
status, flushed = asyncio.run(run())
assert status == "coalesced"
assert flushed == 1 # over the limit it waits, and shutdown still stores it
file = repo.db.file_by_path(path)
assert repo.blobs.get(repo.db.latest_version(file["id"])["blob_sha256"]) == b"c"
def test_dedupe_unchanged(tmp_path):
repo = _repo(tmp_path)
path = str(tmp_path / "f.py")
assert repo.commit(path, b"x", "t", "e")["status"] == "committed"
assert repo.commit(path, b"x", "t", "e")["status"] == "unchanged"