from __future__ import annotations import importlib import stat from pathlib import Path import pytest from backup_tool.config import Settings snapshot = importlib.import_module("backup_tool.snapshot") def settings_for(tmp_path: Path) -> Settings: key = tmp_path / "master.key" key.write_bytes(b"m6-restore-path-test-master-key-material") key.chmod(0o600) repositories = tmp_path / "repositories" sources = tmp_path / "sources" restores = tmp_path / "restores" for directory in (repositories, sources, restores): directory.mkdir() return Settings( data_dir=tmp_path, database_url=f"sqlite+aiosqlite:///{tmp_path / 'metadata.db'}", repository_roots=(repositories,), local_source_roots=(sources,), restore_roots=(restores,), master_key_file=key, min_free_bytes=1, ) @pytest.mark.parametrize("name", ["existing", "outside", "root"]) def test_restore_destination_rejects_existing_or_outside_paths(tmp_path: Path, name: str) -> None: settings = settings_for(tmp_path) root = settings.restore_roots[0] outside = tmp_path / "outside" outside.mkdir() existing = root / "existing" existing.mkdir() destinations = { "existing": existing, "outside": outside / "restore", "root": root, } with pytest.raises(snapshot.SnapshotError): snapshot.validate_restore_destination(settings, str(destinations[name])) def test_restore_strips_special_permission_bits(tmp_path: Path) -> None: target = tmp_path / "restored" target.write_bytes(b"content") snapshot._apply_metadata( target, {"metadata_support": ["mode"], "mode": 0o7777}, ) mode = stat.S_IMODE(target.stat().st_mode) assert mode == 0o777 assert mode & (stat.S_ISUID | stat.S_ISGID | stat.S_ISVTX) == 0 def test_restore_destination_rejects_a_symlinked_parent(tmp_path: Path) -> None: settings = settings_for(tmp_path) outside = tmp_path / "outside" outside.mkdir() (settings.restore_roots[0] / "linked").symlink_to(outside, target_is_directory=True) with pytest.raises(snapshot.SnapshotError): snapshot.validate_restore_destination( settings, str(settings.restore_roots[0] / "linked" / "restore") )