update
This commit is contained in:
@@ -9,10 +9,11 @@ from fastapi.testclient import TestClient
|
||||
def client(tmp_path):
|
||||
os.environ["DB_PATH"] = str(tmp_path / "test.db")
|
||||
os.environ["SYNC_TOKEN"] = "geheim"
|
||||
os.environ["FILES_PAGE_BYTES"] = "150"
|
||||
from app import config, db, main
|
||||
os.environ["FILES_DIR"] = str(tmp_path / "files")
|
||||
from app import config, db, files, main
|
||||
importlib.reload(config)
|
||||
importlib.reload(db)
|
||||
importlib.reload(files)
|
||||
importlib.reload(main)
|
||||
with TestClient(main.app) as c:
|
||||
yield c
|
||||
@@ -94,31 +95,53 @@ def test_seit_cursor_liefert_nur_neues(client):
|
||||
|
||||
|
||||
def test_dateien_nur_auf_anfrage(client):
|
||||
sync(client, files=[{"id": "-home-x/s1.jsonl", "updated": "2026-09-22T10:00:00.000Z", "content": "zeile"}])
|
||||
sync(client, files=[{"id": "-home-x/s1.jsonl", "updated": "2026-09-22T10:00:00.000Z", "content": "zeile\n"}])
|
||||
# Handy und Extension fragen ohne files: nichts davon in der Antwort
|
||||
r = sync(client, events=[], todos=[], notes=[], folders=[])
|
||||
assert "files" not in r and r["events"] == []
|
||||
assert "files" not in sync(client)
|
||||
# Nur Dateien angefragt: nur Dateien zurück, Stand zählt trotzdem
|
||||
# Nur Dateien angefragt: nur der Steckbrief zurück, kein Inhalt
|
||||
r = sync(client, files=[])
|
||||
assert [f["id"] for f in r["files"]] == ["-home-x/s1.jsonl"] and "events" not in r
|
||||
assert r["files"] == [{"id": "-home-x/s1.jsonl", "updated": "2026-09-22T10:00:00.000Z", "size": 6}] and "events" not in r
|
||||
assert r["cursor"] == 1
|
||||
# Tombstone gewinnt mit neuerem updated
|
||||
r = sync(client, files=[{"id": "-home-x/s1.jsonl", "updated": "2026-09-22T11:00:00.000Z", "deleted": True}])
|
||||
assert r["files"][-1]["deleted"] is True
|
||||
|
||||
|
||||
def test_dateien_seitenweise(client):
|
||||
for i in range(3):
|
||||
sync(client, files=[{"id": f"s{i}.jsonl", "updated": "2026-09-22T10:00:00.000Z", "content": "x" * 60}])
|
||||
# Jede Antwort höchstens 150 Bytes: ein Eintrag pro Seite, more zeigt an, dass es weitergeht
|
||||
seen, since = [], 0
|
||||
for _ in range(5):
|
||||
r = sync(client, since=since, files=[])
|
||||
seen += [f["id"] for f in r["files"]]
|
||||
since = r["cursor"]
|
||||
if not r.get("more"):
|
||||
break
|
||||
assert seen == ["s0.jsonl", "s1.jsonl", "s2.jsonl"] and since == 3
|
||||
# Basis-Sammlungen bleiben ungeteilt
|
||||
assert "more" not in sync(client)
|
||||
def test_dateien_inhalt_auf_platte_und_anhaengen(client, tmp_path):
|
||||
import hashlib
|
||||
sync(client, files=[{"id": "p/s.jsonl", "updated": "2026-09-22T10:00:00.000Z", "content": "a\n"}])
|
||||
assert (tmp_path / "files" / "p" / "s.jsonl").read_bytes() == b"a\n"
|
||||
# Anhang mit passendem Stand
|
||||
base = {"size": 2, "hash": hashlib.sha256(b"a\n").hexdigest()}
|
||||
r = sync(client, files=[{"id": "p/s.jsonl", "updated": "2026-09-22T11:00:00.000Z", "base": base, "append": "b\n"}])
|
||||
assert (tmp_path / "files" / "p" / "s.jsonl").read_bytes() == b"a\nb\n"
|
||||
assert r["files"][-1]["size"] == 4
|
||||
# Anhang mit falschem Stand: abgelehnt, Server-Stand samt Hash kommt zurück
|
||||
r = sync(client, since=99, files=[{"id": "p/s.jsonl", "updated": "2026-09-22T12:00:00.000Z", "base": base, "append": "c\n"}])
|
||||
assert r["files"] == [{"id": "p/s.jsonl", "updated": "2026-09-22T11:00:00.000Z", "size": 4, "hash": hashlib.sha256(b"a\nb\n").hexdigest()}]
|
||||
assert (tmp_path / "files" / "p" / "s.jsonl").read_bytes() == b"a\nb\n"
|
||||
# Inhalt holen: ganz, oder nur den Rest ab bekanntem Stand
|
||||
r = client.get("/api/files/p/s.jsonl", headers=AUTH)
|
||||
assert r.content == b"a\nb\n" and r.headers["X-From"] == "0" and r.headers["X-Size"] == "4"
|
||||
r = client.get("/api/files/p/s.jsonl", params={"from": 2, "hash": base["hash"]}, headers=AUTH)
|
||||
assert r.content == b"b\n" and r.headers["X-From"] == "2"
|
||||
r = client.get("/api/files/p/s.jsonl", params={"from": 2, "hash": "falsch"}, headers=AUTH)
|
||||
assert r.content == b"a\nb\n" and r.headers["X-From"] == "0"
|
||||
# Löschen räumt die Platte, Tombstone bleibt, Inhalt weg
|
||||
sync(client, files=[{"id": "p/s.jsonl", "updated": "2026-09-22T13:00:00.000Z", "deleted": True}])
|
||||
assert not (tmp_path / "files" / "p" / "s.jsonl").exists()
|
||||
assert client.get("/api/files/p/s.jsonl", headers=AUTH).status_code == 404
|
||||
assert sync(client, files=[])["files"][-1]["deleted"] is True
|
||||
|
||||
|
||||
def test_dateien_aus_datenbank_auf_platte(client, tmp_path):
|
||||
import json
|
||||
from app import db, files
|
||||
# Alter Stand: Inhalt in der Datenbank
|
||||
with db.connect(write=True) as conn:
|
||||
conn.execute("INSERT INTO items VALUES ('files', 'alt/s.jsonl', '2026-09-01T00:00:00.000Z', 0, ?, 1)",
|
||||
(json.dumps({"id": "alt/s.jsonl", "updated": "2026-09-01T00:00:00.000Z", "content": "alt\n"}),))
|
||||
with db.connect(write=True) as conn:
|
||||
assert files.migrate(conn) == 1
|
||||
assert files.migrate(conn) == 0
|
||||
assert (tmp_path / "files" / "alt" / "s.jsonl").read_bytes() == b"alt\n"
|
||||
assert sync(client, files=[])["files"] == [{"id": "alt/s.jsonl", "updated": "2026-09-01T00:00:00.000Z", "size": 4}]
|
||||
|
||||
Reference in New Issue
Block a user