"""Schijfruimte: meten, drempels en het weigeren van een backup die niet past. Een volle schijf op een Docker-host breekt alles, niet alleen Server Up. En een backup die halverwege stopt omdat de ruimte op is, laat een archief achter dat er in een lijst compleet uitziet. """ import sys from pathlib import Path import pytest sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "server-up")) @pytest.fixture def ds(env): from core import diskspace return diskspace def test_usage_geeft_bruikbare_getallen(ds, tmp_path): u = ds.usage(tmp_path) assert u["ok"] is True assert u["total"] > 0 assert 0 <= u["pct_free"] <= 100 assert u["free_human"].endswith(("B", "KB", "MB", "GB", "TB")) def test_usage_werkt_voor_een_map_die_nog_niet_bestaat(ds, tmp_path): """De backupmap wordt pas bij de eerste backup aangemaakt, maar we willen de ruimte er vóór die tijd al van weten.""" u = ds.usage(tmp_path / "bestaat" / "nog" / "niet") assert u["ok"] is True assert u["total"] > 0 def test_onmeetbaar_pad_blokkeert_niets(ds, monkeypatch, tmp_path): """Kunnen we het niet meten, dan is tegenhouden erger dan doorlaten.""" def _stuk(_): raise OSError("geen idee") monkeypatch.setattr(ds.shutil, "disk_usage", _stuk) assert ds.usage(tmp_path)["ok"] is False ok, _ = ds.ruimte_voor_backup(tmp_path, tmp_path) assert ok is True @pytest.mark.parametrize("bytes_,verwacht", [ (0, "0 B"), (512, "512 B"), (1536, "1.5 KB"), (1024 ** 2, "1.0 MB"), (3 * 1024 ** 3, "3.0 GB"), ]) def test_leesbaar(ds, bytes_, verwacht): assert ds.leesbaar(bytes_) == verwacht def test_mapgrootte_telt_alleen_bestanden(ds, tmp_path): (tmp_path / "map").mkdir() (tmp_path / "map" / "a.txt").write_bytes(b"x" * 100) (tmp_path / "b.txt").write_bytes(b"y" * 50) assert ds.mapgrootte(tmp_path) == 150 def test_mapgrootte_volgt_geen_symlinks(ds, tmp_path): """Een link naar /var zou de schatting onbruikbaar maken.""" groot = tmp_path / "groot.bin" groot.write_bytes(b"z" * 1000) doel = tmp_path / "map" doel.mkdir() (doel / "link").symlink_to(groot) assert ds.mapgrootte(doel) == 0 def test_mapgrootte_stopt_bij_de_limiet(ds, tmp_path): for i in range(10): (tmp_path / f"{i}.bin").write_bytes(b"x" * 1000) # Zodra de limiet gepasseerd is hoeft hij niet door te tellen. assert ds.mapgrootte(tmp_path, limiet_bytes=2000) <= 10000 def test_ruimte_voor_backup_weigert_wat_niet_past(ds, env, monkeypatch, tmp_path): bron = tmp_path / "stack" bron.mkdir() (bron / "data.bin").write_bytes(b"x" * 5000) monkeypatch.setattr(ds.shutil, "disk_usage", lambda p: (1000, 900, 100)) # 100 bytes vrij ok, waarom = ds.ruimte_voor_backup(bron, tmp_path) assert ok is False assert "schijfruimte" in waarom.lower() def test_ruimte_voor_backup_houdt_de_reserve_vrij(ds, env, monkeypatch, tmp_path): """Er is genoeg voor het archief zelf, maar niet voor archief + reserve.""" bron = tmp_path / "stack" bron.mkdir() (bron / "data.bin").write_bytes(b"x" * 1000) env["core"].patch({"DISK_MIN_FREE_GB": 1}) monkeypatch.setattr(ds.shutil, "disk_usage", lambda p: (10 * ds.GB, 9 * ds.GB, 5000)) ok, waarom = ds.ruimte_voor_backup(bron, tmp_path) assert ok is False assert "reserve" in waarom def test_zonder_reserve_mag_het_krap(ds, env, monkeypatch, tmp_path): bron = tmp_path / "stack" bron.mkdir() (bron / "data.bin").write_bytes(b"x" * 1000) env["core"].patch({"DISK_MIN_FREE_GB": 0}) monkeypatch.setattr(ds.shutil, "disk_usage", lambda p: (10000, 8000, 2000)) assert ds.ruimte_voor_backup(bron, tmp_path)[0] is True def test_drempel_markeert_krappe_schijf(ds, env, monkeypatch, tmp_path): env["core"].patch({"DISK_MIN_FREE_GB": 0, "DISK_WARN_PCT": 20}) monkeypatch.setattr(ds.shutil, "disk_usage", lambda p: (1000, 900, 100)) assert ds.usage(tmp_path)["low"] is True monkeypatch.setattr(ds.shutil, "disk_usage", lambda p: (1000, 100, 900)) assert ds.usage(tmp_path)["low"] is False def test_status_toont_elk_filesystem_een_keer(ds, env): """Backups, stacks en appdata staan meestal op dezelfde schijf; drie identieke regels helpen niemand.""" st = ds.status() assert st["volumes"], st sleutels = [(v["total"], v["free"]) for v in st["volumes"]] assert len(sleutels) == len(set(sleutels)) def test_status_neemt_de_externe_bestemming_mee(ds, env, tmp_path): extern = tmp_path / "extern" extern.mkdir() env["core"].patch({"BACKUP_OFFSITE_DIR": str(extern)}) labels = [v["label"] for v in ds.status()["volumes"]] # Zelfde filesystem als de rest in de test, dus hij mag samenvallen — maar # de instelling mag nooit een fout opleveren. assert labels