Files
server-toolset/many-rsync/test_main.py
Yigid BALABAN a168b4cbea add project tooling and test suite for many-rsync
- Add pyproject.toml: hatchling build, many-rsync entrypoint, ruff/mypy/pytest config
with 60% coverage floor
- Add uv.lock for reproducible dev installs
- Add .pre-commit-config.yaml: ruff (with --fix) + mypy hooks
- Add test_main.py: unit tests for _build_rsync_cmd, _load_raw, and load_config
covering happy paths and FATAL exit cases
- Add explanation.md: architecture overview with flowchart
- main.py: refactor into typed, testable functions (_load_raw, _build_rsync_cmd
extracted); add RsyncParameters/Config TypedDicts; add rsync_parameters config support
(rsync_path, exclude_from); harden validation (n clamped, log_level validated)
- README.md: update install instructions and document all config fields including
rsync_parameters
2026-03-31 22:05:08 +03:00

106 lines
3.4 KiB
Python

from __future__ import annotations
from pathlib import Path
import pytest
from main import _build_rsync_cmd, _load_raw, load_config
# ── _build_rsync_cmd ─────────────────────────────────────────────────────────
def test_build_rsync_cmd_no_params() -> None:
cmd = _build_rsync_cmd({})
assert cmd == ("rsync", "-avh", "--progress", "--delete", "--stats")
def test_build_rsync_cmd_with_rsync_path() -> None:
cmd = _build_rsync_cmd({"rsync_path": "/usr/local/bin/rsync"})
assert "--rsync-path=/usr/local/bin/rsync" in cmd
def test_build_rsync_cmd_with_exclude_from() -> None:
cmd = _build_rsync_cmd({"exclude_from": ".gitignore"})
assert "--exclude-from=.gitignore" in cmd
def test_build_rsync_cmd_with_all_params() -> None:
cmd = _build_rsync_cmd({"rsync_path": "/opt/rsync", "exclude_from": "exc.txt"})
assert "--rsync-path=/opt/rsync" in cmd
assert "--exclude-from=exc.txt" in cmd
# ── _load_raw ────────────────────────────────────────────────────────────────
def test_load_raw_toml(tmp_path: Path) -> None:
p = tmp_path / "cfg.toml"
p.write_text('remote_folder = "host:/dst"\nlocal_folders = ["a"]\n')
raw = _load_raw(p)
assert raw["remote_folder"] == "host:/dst"
def test_load_raw_json(tmp_path: Path) -> None:
p = tmp_path / "cfg.json"
p.write_text('{"remote_folder": "/dst", "local_folders": ["a"]}')
raw = _load_raw(p)
assert raw["remote_folder"] == "/dst"
def test_load_raw_unsupported_format(tmp_path: Path) -> None:
p = tmp_path / "cfg.yaml"
p.write_text("key: val")
with pytest.raises(SystemExit):
_load_raw(p)
# ── load_config ──────────────────────────────────────────────────────────────
def test_load_config_valid(tmp_path: Path) -> None:
d = tmp_path / "src"
d.mkdir()
p = tmp_path / "cfg.toml"
p.write_text(f'remote_folder = "host:/dst"\nlocal_folders = ["{d}"]\nn = 1\n')
cfg = load_config(p)
assert cfg["remote_folder"] == "host:/dst"
assert cfg["local_folders"] == [d]
assert cfg["n"] == 1
assert cfg["log_level"] == "INFO"
def test_load_config_empty_folders(tmp_path: Path) -> None:
p = tmp_path / "cfg.toml"
p.write_text('remote_folder = "host:/dst"\nlocal_folders = []\n')
with pytest.raises(SystemExit):
load_config(p)
def test_load_config_missing_remote(tmp_path: Path) -> None:
d = tmp_path / "src"
d.mkdir()
p = tmp_path / "cfg.toml"
p.write_text(f'local_folders = ["{d}"]\n')
with pytest.raises(SystemExit):
load_config(p)
def test_load_config_n_clamped(tmp_path: Path) -> None:
d = tmp_path / "src"
d.mkdir()
p = tmp_path / "cfg.toml"
p.write_text(f'remote_folder = "host:/dst"\nlocal_folders = ["{d}"]\nn = 99\n')
cfg = load_config(p)
assert cfg["n"] == 1 # clamped to len(folders)
def test_load_config_invalid_log_level(tmp_path: Path) -> None:
d = tmp_path / "src"
d.mkdir()
p = tmp_path / "cfg.toml"
p.write_text(
f'remote_folder = "host:/dst"\nlocal_folders = ["{d}"]\nlog_level = "TRACE"\n'
)
with pytest.raises(SystemExit):
load_config(p)