from pathlib import Path import pytest from PyQt6.QtWidgets import QApplication from syncvito.config_store import ConfigStore from syncvito.coordinator import SyncCoordinator from syncvito.models import AppConfig, SyncTask, TaskRuntimeState from syncvito.sync_engine import BlockedFile, SyncResult @pytest.fixture def coordinator(tmp_path: Path): application = QApplication.instance() or QApplication([]) task = SyncTask(name="AppData", source=str(tmp_path / "source"), target=str(tmp_path / "target")) coordinator = SyncCoordinator(AppConfig(tasks=[task]), ConfigStore(tmp_path / "config.json")) yield coordinator coordinator._schedule_timer.stop() coordinator._retry_timer.stop() coordinator.stop() assert application is not None def test_many_locks_have_compact_banner_and_full_paths_in_log(coordinator: SyncCoordinator) -> None: banners = [] logs = [] coordinator.lock_changed.connect(banners.append) coordinator.log_created.connect(lambda *entry: logs.append(entry)) blocked = [BlockedFile(Path(f"AppData/Programm{index}/Cookies")) for index in range(300)] task = coordinator.config.tasks[0] coordinator._on_finished(task.id, SyncResult(copied=123, blocked=blocked)) assert len(banners[-1]) < 400 assert "123 kopiert" in banners[-1] assert "300 Dateien gesperrt" in banners[-1] assert "unvollständig" in banners[-1] assert len(logs) == 301 assert str(blocked[-1].path) in logs[-1][2] assert coordinator.task_state(task.id) is TaskRuntimeState.WAITING_FOR_RELEASE coordinator._on_finished(task.id, SyncResult(copied=300)) assert banners[-1] == "" assert task.id not in coordinator._retry_tasks assert coordinator.task_state(task.id) is TaskRuntimeState.ACTIVE def test_errors_are_visible_even_when_other_files_are_locked(coordinator: SyncCoordinator) -> None: logs = [] coordinator.log_created.connect(lambda *entry: logs.append(entry)) task = coordinator.config.tasks[0] coordinator._on_finished(task.id, SyncResult(blocked=[BlockedFile(Path("Cookies"))], errors=["Zugriff verweigert"])) assert coordinator.task_state(task.id) is TaskRuntimeState.ERROR assert any(level == "Fehler" and detail == "Zugriff verweigert" for _, level, detail in logs) @pytest.mark.parametrize("all_locked", [False, True]) def test_retry_can_run_after_partial_release( coordinator: SyncCoordinator, monkeypatch: pytest.MonkeyPatch, all_locked: bool ) -> None: task = coordinator.config.tasks[0] coordinator._retry_tasks[task.id] = [Path("dauerhaft"), Path("freigegeben")] queued = [] monkeypatch.setattr(coordinator, "enqueue", queued.append) monkeypatch.setattr("syncvito.coordinator.is_file_locked", lambda path: all_locked or path.name == "dauerhaft") coordinator._check_retries() assert queued == ([] if all_locked else [task.id]) def test_retry_queues_all_ready_tasks(coordinator: SyncCoordinator, monkeypatch: pytest.MonkeyPatch) -> None: coordinator._retry_tasks = {"first": [], "second": []} queued = [] monkeypatch.setattr(coordinator, "enqueue", queued.append) coordinator._check_retries() assert queued == ["first", "second"]