test(v2): define repository safety contracts
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
from backup_tool.api.app import create_app
|
||||
from backup_tool.config import Settings
|
||||
from backup_tool.repository import RepositoryError, initialize, inspect_repository
|
||||
|
||||
|
||||
def settings_for(tmp_path: Path) -> Settings:
|
||||
key = tmp_path / "key"
|
||||
key.write_bytes(b"x" * 32)
|
||||
key.chmod(0o600)
|
||||
root = tmp_path / "repos"
|
||||
root.mkdir()
|
||||
return Settings(
|
||||
repository_roots=(root,),
|
||||
local_source_roots=(tmp_path,),
|
||||
restore_roots=(tmp_path,),
|
||||
master_key_file=key,
|
||||
data_dir=tmp_path,
|
||||
database_url=f"sqlite+aiosqlite:///{tmp_path / 'db.sqlite'}",
|
||||
min_free_bytes=1,
|
||||
)
|
||||
|
||||
|
||||
def test_partial_initialization_is_removed_on_publish_failure(tmp_path: Path) -> None:
|
||||
settings = settings_for(tmp_path)
|
||||
with (
|
||||
patch("backup_tool.repository.os.replace", side_effect=OSError("disk failure")),
|
||||
pytest.raises(OSError),
|
||||
):
|
||||
initialize(settings, "main", "none", "none")
|
||||
assert not (settings.repository_roots[0] / "main").exists()
|
||||
assert not list(settings.repository_roots[0].glob(".main.staging-*"))
|
||||
|
||||
|
||||
def test_repository_inspection_rejects_noncanonical_metadata(tmp_path: Path) -> None:
|
||||
settings = settings_for(tmp_path)
|
||||
result = initialize(settings, "main", "none", "none")
|
||||
(result.root / "repository.json").write_text(
|
||||
'{"encryption":"none","compression":"none","format_version":1}\n'
|
||||
)
|
||||
with pytest.raises(RepositoryError, match="canonical"):
|
||||
inspect_repository(result.root)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_can_list_and_inspect_repositories(tmp_path: Path) -> None:
|
||||
settings = settings_for(tmp_path)
|
||||
app = create_app(settings)
|
||||
from backup_tool.db.models import Base
|
||||
|
||||
async with app.state.engine.begin() as connection:
|
||||
await connection.run_sync(Base.metadata.create_all)
|
||||
async with httpx.AsyncClient(
|
||||
transport=httpx.ASGITransport(app=app), base_url="https://test"
|
||||
) as client:
|
||||
assert (
|
||||
await client.post(
|
||||
"/api/v2/setup", json={"username": "admin", "password": "a secure password"}
|
||||
)
|
||||
).status_code == 201
|
||||
csrf = client.cookies["backup_tool_csrf"]
|
||||
created = await client.post(
|
||||
"/api/v2/repositories",
|
||||
json={
|
||||
"name": "main",
|
||||
"relative_path": "main",
|
||||
"compression": "none",
|
||||
"encryption": "none",
|
||||
},
|
||||
headers={"X-CSRF-Token": csrf},
|
||||
)
|
||||
assert created.status_code == 201
|
||||
listed = await client.get("/api/v2/repositories")
|
||||
assert listed.status_code == 200
|
||||
assert listed.json()["items"][0]["id"] == created.json()["id"]
|
||||
inspection = await client.get(f"/api/v2/repositories/{created.json()['id']}/inspection")
|
||||
assert inspection.status_code == 200
|
||||
assert inspection.json()["format_version"] == 1
|
||||
await app.state.engine.dispose()
|
||||
Reference in New Issue
Block a user