166 lines
4.4 KiB
Python
166 lines
4.4 KiB
Python
import os
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import tkinter as tk
|
|
|
|
from main import (
|
|
BackgroundFileCopyApp,
|
|
find_latest_file_in_folder,
|
|
should_copy_file,
|
|
compute_file_checksum,
|
|
verify_copy,
|
|
)
|
|
|
|
|
|
|
|
def _touch(path: Path, mtime: float) -> None:
|
|
path.write_text('data', encoding='utf-8')
|
|
os.utime(path, (mtime, mtime))
|
|
|
|
|
|
|
|
def test_find_latest_file_returns_none_for_missing_folder(tmp_path):
|
|
missing = tmp_path / 'missing'
|
|
assert find_latest_file_in_folder(str(missing)) is None
|
|
|
|
|
|
|
|
def test_find_latest_file_picks_newest(tmp_path):
|
|
folder = tmp_path / 'src'
|
|
folder.mkdir()
|
|
|
|
now = time.time()
|
|
older = folder / 'older.bak'
|
|
newer = folder / 'newer.bak'
|
|
other = folder / 'other.sql'
|
|
|
|
_touch(older, now - 10)
|
|
_touch(newer, now - 5)
|
|
_touch(other, now - 1)
|
|
|
|
latest = find_latest_file_in_folder(str(folder))
|
|
assert latest is not None
|
|
assert latest.name == 'other.sql'
|
|
|
|
|
|
|
|
def test_should_copy_file_when_target_missing(tmp_path):
|
|
src = tmp_path / 'src.bak'
|
|
src.write_text('x', encoding='utf-8')
|
|
dst = tmp_path / 'dst.bak'
|
|
|
|
assert should_copy_file(src, dst) is True
|
|
|
|
|
|
|
|
def test_should_copy_file_when_source_newer(tmp_path):
|
|
now = time.time()
|
|
src = tmp_path / 'src.bak'
|
|
dst = tmp_path / 'dst.bak'
|
|
|
|
_touch(dst, now - 10)
|
|
_touch(src, now)
|
|
|
|
assert should_copy_file(src, dst) is True
|
|
|
|
|
|
|
|
def test_should_copy_file_when_target_newer_or_equal(tmp_path):
|
|
now = time.time()
|
|
src = tmp_path / 'src.bak'
|
|
dst = tmp_path / 'dst.bak'
|
|
|
|
_touch(src, now)
|
|
_touch(dst, now)
|
|
|
|
assert should_copy_file(src, dst) is False
|
|
|
|
|
|
def test_compute_file_checksum_stable(tmp_path):
|
|
src = tmp_path / 'a.bak'
|
|
src.write_text('hello', encoding='utf-8')
|
|
c1 = compute_file_checksum(src)
|
|
c2 = compute_file_checksum(src)
|
|
assert c1 == c2
|
|
|
|
|
|
def test_verify_copy_true_for_equal_files(tmp_path):
|
|
src = tmp_path / 'src.bak'
|
|
dst = tmp_path / 'dst.bak'
|
|
src.write_text('data', encoding='utf-8')
|
|
dst.write_text('data', encoding='utf-8')
|
|
assert verify_copy(src, dst) is True
|
|
|
|
|
|
def test_verify_copy_false_for_different_files(tmp_path):
|
|
src = tmp_path / 'src.bak'
|
|
dst = tmp_path / 'dst.bak'
|
|
src.write_text('data1', encoding='utf-8')
|
|
dst.write_text('data2', encoding='utf-8')
|
|
assert verify_copy(src, dst) is False
|
|
|
|
|
|
def _create_app():
|
|
root = tk.Tk()
|
|
root.withdraw()
|
|
app = BackgroundFileCopyApp(root)
|
|
return app, root
|
|
|
|
|
|
def test_copy_full_folder(tmp_path):
|
|
app, root = _create_app()
|
|
try:
|
|
src = tmp_path / "src"
|
|
dst = tmp_path / "dst"
|
|
(src / "sub").mkdir(parents=True)
|
|
(src / "a.bak").write_text("a", encoding="utf-8")
|
|
(src / "sub" / "b.sql").write_text("b", encoding="utf-8")
|
|
|
|
app.min_free_gb_var.set(0)
|
|
app.include_masks_var.set("*.bak;*.sql")
|
|
app.exclude_masks_var.set("")
|
|
|
|
copied, skipped, errors = app.copy_full_folder(src, dst)
|
|
assert errors == 0
|
|
assert copied == 2
|
|
assert (dst / "a.bak").exists()
|
|
assert (dst / "sub" / "b.sql").exists()
|
|
finally:
|
|
root.destroy()
|
|
|
|
|
|
def test_save_and_load_settings(tmp_path, monkeypatch):
|
|
app, root = _create_app()
|
|
try:
|
|
settings_path = tmp_path / "settings.json"
|
|
monkeypatch.setattr(app, "get_settings_path", lambda: str(settings_path))
|
|
|
|
app.add_path_pair("C:/src", "D:/dst", False)
|
|
app.include_masks_var.set("*.bak")
|
|
app.exclude_masks_var.set("*.tmp")
|
|
app.min_free_gb_var.set(2)
|
|
app.max_workers_var.set(4)
|
|
app.schedules = [{"time": "01:00", "days": ["Mon", "Wed"]}]
|
|
app.refresh_schedules_tree()
|
|
app.save_settings()
|
|
|
|
app2, root2 = _create_app()
|
|
try:
|
|
monkeypatch.setattr(app2, "get_settings_path", lambda: str(settings_path))
|
|
app2.load_settings()
|
|
profile = app2.profiles.get(app2.active_profile.get())
|
|
assert profile is not None
|
|
assert profile.get("include_masks") == "*.bak"
|
|
assert profile.get("exclude_masks") == "*.tmp"
|
|
assert int(profile.get("max_workers")) == 4
|
|
assert profile.get("min_free_gb") == 2
|
|
assert profile.get("schedules") == [{"time": "01:00", "days": ["Mon", "Wed"]}]
|
|
assert len(profile.get("pairs")) == 1
|
|
finally:
|
|
root2.destroy()
|
|
finally:
|
|
root.destroy()
|