102 lines
3.4 KiB
Python
102 lines
3.4 KiB
Python
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from serato_doctor.repair import (
|
|
BACKUP_FOLDER,
|
|
apply_duplicate_repair,
|
|
apply_duplicate_repair_batch,
|
|
list_backups,
|
|
plan_duplicate_repair,
|
|
restore_backup,
|
|
rotate_backups,
|
|
)
|
|
|
|
|
|
def library(tmp_path):
|
|
serato = tmp_path / "_Serato_"
|
|
crate = serato / "Subcrates" / "House.crate"
|
|
crate.parent.mkdir(parents=True)
|
|
crate.write_bytes(b"crate-data")
|
|
(serato / "database V2").write_bytes(b"database-data")
|
|
music = tmp_path / "Music"
|
|
keeper = music / "Main" / "Track.mp3"
|
|
duplicate = music / "Old" / "Track.mp3"
|
|
keeper.parent.mkdir(parents=True)
|
|
duplicate.parent.mkdir(parents=True)
|
|
keeper.write_bytes(b"keeper")
|
|
duplicate.write_bytes(b"duplicate")
|
|
return serato, keeper, duplicate
|
|
|
|
|
|
def test_duplicate_repair_backs_up_then_preserves_old_path_as_link(tmp_path):
|
|
serato, keeper, duplicate = library(tmp_path)
|
|
plan = plan_duplicate_repair(keeper, (keeper, duplicate), serato)
|
|
|
|
receipt = apply_duplicate_repair(plan, serato, backup_limit=3)
|
|
|
|
assert duplicate.is_symlink()
|
|
assert duplicate.resolve() == keeper.resolve()
|
|
assert (receipt.backup / "manifest.json").is_file()
|
|
assert any((receipt.backup / "serato-metadata").rglob("House.crate"))
|
|
assert any((receipt.backup / "serato-metadata").rglob("database V2"))
|
|
|
|
history = list_backups(serato)
|
|
assert len(history) == 1
|
|
assert history[0].status == "ready"
|
|
assert history[0].size_bytes > 0
|
|
|
|
restored = restore_backup(receipt.backup)
|
|
assert restored == (duplicate.resolve(),)
|
|
assert not duplicate.is_symlink()
|
|
assert duplicate.read_bytes() == b"duplicate"
|
|
assert list_backups(serato)[0].status == "restored"
|
|
|
|
|
|
def test_plan_rejects_keeper_outside_duplicate_group(tmp_path):
|
|
serato, keeper, duplicate = library(tmp_path)
|
|
outsider = tmp_path / "outsider.mp3"
|
|
outsider.write_bytes(b"other")
|
|
|
|
with pytest.raises(ValueError, match="must belong"):
|
|
plan_duplicate_repair(outsider, (keeper, duplicate), serato)
|
|
|
|
|
|
def test_backup_rotation_can_be_limited_or_unlimited(tmp_path):
|
|
root = tmp_path / BACKUP_FOLDER
|
|
for name in ("001", "002", "003"):
|
|
backup = root / name
|
|
backup.mkdir(parents=True)
|
|
(backup / "manifest.json").write_text("{}", encoding="utf-8")
|
|
|
|
rotate_backups(root, None)
|
|
assert len(list(root.iterdir())) == 3
|
|
rotate_backups(root, 2)
|
|
assert {path.name for path in root.iterdir()} == {"002", "003"}
|
|
|
|
|
|
def test_batch_repair_uses_one_backup_and_restores_every_group(tmp_path):
|
|
serato, first_keeper, first_duplicate = library(tmp_path)
|
|
second_keeper = tmp_path / "Music" / "Main" / "Other.mp3"
|
|
second_duplicate = tmp_path / "Music" / "Old" / "Other.mp3"
|
|
second_keeper.write_bytes(b"other-keeper")
|
|
second_duplicate.write_bytes(b"other-duplicate")
|
|
plans = (
|
|
plan_duplicate_repair(
|
|
first_keeper, (first_keeper, first_duplicate), serato
|
|
),
|
|
plan_duplicate_repair(
|
|
second_keeper, (second_keeper, second_duplicate), serato
|
|
),
|
|
)
|
|
|
|
receipt = apply_duplicate_repair_batch(plans, serato, backup_limit=10)
|
|
|
|
assert first_duplicate.is_symlink()
|
|
assert second_duplicate.is_symlink()
|
|
assert len(list((serato / BACKUP_FOLDER).iterdir())) == 1
|
|
assert len(receipt.replaced) == 2
|
|
restore_backup(receipt.backup)
|
|
assert first_duplicate.read_bytes() == b"duplicate"
|
|
assert second_duplicate.read_bytes() == b"other-duplicate"
|