Files
versioning/tests/test_maintenance.py
T

104 lines
4.2 KiB
Python
Raw Normal View History

"""Scheduler, quota signal and capacity backstop."""
import asyncio
import time
import httpx
import pytest
from fastapi.testclient import TestClient
from versiond import maintenance
from versiond.api import create_app
from versiond.config import Config, Paths
from versiond.remote import quota as remote_quota
from test_service import CONFIG, history, wait_for
from test_purge import FakeDAV, REMOTE
@pytest.fixture
def maint_env(tmp_path, monkeypatch):
monkeypatch.setenv("VERSIOND_HOME", str(tmp_path / "home"))
paths = Paths.resolve()
paths.ensure()
paths.config_file.write_text(CONFIG + "\n[upload]\nmanifest_interval_seconds = 0.2\n")
cfg = Config.load(paths)
dav = FakeDAV()
project = tmp_path / "proj"
project.mkdir()
(project / "main.py").write_text("print('hello')\n")
app = create_app(cfg, remote_transport=httpx.MockTransport(dav.handler))
with TestClient(app, base_url="http://127.0.0.1:9922") as client:
client.headers["Authorization"] = f"Bearer {cfg.api_token()}"
app.state.services.uploader.offline_sleep = 0.3
yield client, dav, project, cfg, app.state.services
def test_quota_signal_and_fallback(maint_env):
client, dav, project, cfg, svc = maint_env
directory = client.put("/api/v1/config/remote", json=REMOTE).json()["directory"]
async def get_quota():
from versiond.remote import WebDAV
settings = svc.uploader.settings()
dav_client = WebDAV(settings, transport=svc.uploader.transport)
try:
return await remote_quota(dav_client, directory)
finally:
await dav_client.close()
used, available = asyncio.run(get_quota())
assert used is not None and used >= 0 and available == dav.capacity_bytes - used
async def refresh():
return await maintenance.refresh_usage(svc)
usage = asyncio.run(refresh())
assert usage["source"] == "webdav-quota"
assert usage["percent"] == round(100 * used / dav.capacity_bytes, 1)
assert maintenance.over_limit(svc) is False
assert client.get("/health").json()["remote_pressure"] is False
def test_pressure_triggers_pass_but_floor_holds(maint_env):
client, dav, project, cfg, svc = maint_env
directory = client.put("/api/v1/config/remote", json=REMOTE).json()["directory"]
client.post("/api/v1/roots", json={"path": str(project)})
wait_for(lambda: history(client, project / "main.py"), timeout=10)
# Tiny remote: everything ever uploaded already exceeds 70%.
dav.capacity_bytes = 1
result = asyncio.run(maintenance.run_scheduled_pass(svc, "test-pressure"))
assert result["pressure"] is True
assert result["versions_deleted"] == 0 # all young: the 5-day floor holds
assert len(history(client, project / "main.py")) == 1
assert client.get("/health").json()["remote_pressure"] is True
assert "remote-over-capacity-limit" in client.get("/health").json()["degraded_roots"]
upload = client.get("/api/v1/progress").json()["upload"]
assert upload["usage"]["source"] == "webdav-quota"
assert upload["usage"]["percent"] >= 70
def test_scheduled_pass_cleans_old_and_reports(maint_env):
import sqlite3
client, dav, project, cfg, svc = maint_env
client.put("/api/v1/config/remote", json=REMOTE)
client.post("/api/v1/roots", json={"path": str(project)})
wait_for(lambda: history(client, project / "main.py"), timeout=10)
(project / "main.py").write_text("v2\n")
wait_for(lambda: len(history(client, project / "main.py")) == 2, timeout=10)
import time as _t
_t.sleep(0.5)
(project / "main.py").write_text("v3\n")
wait_for(lambda: len(history(client, project / "main.py")) == 3, timeout=10)
old = time.time() - 10 * 86400
conn = sqlite3.connect(cfg.paths.index_file, timeout=5.0)
vids = [v["id"] for v in reversed(history(client, project / "main.py"))]
for vid in vids[:2]: # two oldest share one old day: keep daily + latest
conn.execute("UPDATE versions SET captured_at = ? WHERE id = ?", (old, vid))
conn.commit()
conn.close()
result = asyncio.run(maintenance.run_scheduled_pass(svc, "test-scheduled"))
assert result["versions_deleted"] == 1
assert len(history(client, project / "main.py")) == 2