SFM/tests/unit/test_file_manager.py
2026-07-10 15:19:40 +03:00

182 lines
5.9 KiB
Python

from __future__ import annotations
import shutil
from pathlib import Path
from app.pipeline.file_manager import (
check_dir_write_access,
create_run_context,
move_file_to_error,
next_run_number,
scan_input_files,
)
class FakeSamba:
def __init__(self, root: Path) -> None:
self.root = root
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb): # noqa: ANN001
return False
def _to_local(self, remote_path: str) -> Path:
cleaned = remote_path.replace("/", "\\").strip("\\")
return self.root / Path(cleaned)
def path_exists(self, path: str) -> bool:
return self._to_local(path).exists()
def listdir(self, path: str) -> list[str]:
target = self._to_local(path)
if not target.exists():
return []
return [item.name for item in target.iterdir()]
def is_file(self, path: str) -> bool:
return self._to_local(path).is_file()
def mkdir(self, path: str, parent: bool = False) -> None:
target = self._to_local(path)
if parent:
target.mkdir(parents=True, exist_ok=True)
else:
target.mkdir(exist_ok=True)
def copy(self, path_from: str, path_to: str, replace: bool = False) -> None:
source = self._to_local(path_from)
target = self._to_local(path_to)
target.parent.mkdir(parents=True, exist_ok=True)
if target.exists() and not replace:
raise FileExistsError(path_to)
shutil.copy2(source, target)
def delete(self, path: str) -> None:
target = self._to_local(path)
if target.exists():
target.unlink()
def rename(self, path_from: str, path_to: str, replace: bool = False) -> None:
source = self._to_local(path_from)
target = self._to_local(path_to)
target.parent.mkdir(parents=True, exist_ok=True)
if target.exists() and not replace:
raise FileExistsError(path_to)
source.rename(target)
def open(self, path: str, mode: str = "rb"):
target = self._to_local(path)
target.parent.mkdir(parents=True, exist_ok=True)
return target.open(mode)
def test_scan_input_files_splits_valid_and_invalid(tmp_path: Path) -> None:
samba = FakeSamba(tmp_path)
in_dir = "Exchange\\SMBDEMO\\test"
local_dir = tmp_path / "Exchange" / "SMBDEMO" / "test"
local_dir.mkdir(parents=True)
(local_dir / "SKO115FZ_01_123456789_20260616_X00001.xml").write_text(
"ok", encoding="utf-8"
)
(local_dir / "wrong_name.xml").write_text("bad", encoding="utf-8")
result = scan_input_files(in_dir, samba)
assert len(result.valid_files) == 1
assert len(result.invalid_files) == 1
assert len(result.skipped_files) == 0
def test_scan_input_files_skips_already_processed(tmp_path: Path) -> None:
samba = FakeSamba(tmp_path)
in_dir = "Exchange\\SMBDEMO\\test"
processed_dir = "Exchange\\SMBDEMO\\Processed"
local_in_dir = tmp_path / "Exchange" / "SMBDEMO" / "test"
local_processed_dir = tmp_path / "Exchange" / "SMBDEMO" / "Processed"
local_in_dir.mkdir(parents=True)
local_processed_dir.mkdir(parents=True)
file_name = "SKO115FZ_01_123456789_20260616_X00001.xml"
(local_in_dir / file_name).write_text("ok", encoding="utf-8")
(local_processed_dir / file_name).write_text("already done", encoding="utf-8")
result = scan_input_files(in_dir, samba, processed_dir=processed_dir)
assert len(result.valid_files) == 0
assert len(result.invalid_files) == 0
assert [item.name for item in result.skipped_files] == [file_name]
def test_next_run_number_reads_existing_dirs(tmp_path: Path) -> None:
samba = FakeSamba(tmp_path)
out_day = (
tmp_path / "Exchange" / "SMBDEMO" / "Out" / "2026" / "06_2026" / "20260616"
)
(out_day / "1").mkdir(parents=True)
(out_day / "2").mkdir(parents=True)
number = next_run_number("Exchange\\SMBDEMO\\Out\\2026\\06_2026\\20260616", samba)
assert number == 3
def test_create_run_context_creates_paths(tmp_path: Path) -> None:
samba = FakeSamba(tmp_path)
context = create_run_context("Exchange\\SMBDEMO\\test", samba)
assert context.run_number == 1
assert (tmp_path / Path(context.run_dir)).exists()
assert (tmp_path / Path(context.error_dir)).exists()
assert (tmp_path / Path(context.processed_dir)).exists()
def test_move_file_to_error_adds_suffix_on_collision(tmp_path: Path) -> None:
samba = FakeSamba(tmp_path)
error_dir = tmp_path / "Exchange" / "SMBDEMO" / "Err"
error_dir.mkdir(parents=True)
(error_dir / "file.xml").write_text("existing", encoding="utf-8")
source = tmp_path / "Exchange" / "SMBDEMO" / "test" / "file.xml"
source.parent.mkdir(parents=True)
source.write_text("new", encoding="utf-8")
target = move_file_to_error(
file_path="Exchange\\SMBDEMO\\test\\file.xml",
file_name="file.xml",
error_dir="Exchange\\SMBDEMO\\Err",
samba_conn=samba,
)
assert target.endswith("file_1.xml")
assert (error_dir / "file_1.xml").exists()
def test_check_dir_write_access_success(tmp_path: Path) -> None:
samba = FakeSamba(tmp_path)
target_dir = tmp_path / "Exchange" / "SMBDEMO" / "Out"
target_dir.mkdir(parents=True)
ok, error = check_dir_write_access(
target_dir="Exchange\\SMBDEMO\\Out",
samba_conn=samba,
source_name="Out",
)
assert ok is True
assert error is None
def test_check_dir_write_access_fails_without_open_or_upload(tmp_path: Path) -> None:
class NoWriteSamba(FakeSamba):
open = None # type: ignore[assignment]
samba = NoWriteSamba(tmp_path)
target_dir = tmp_path / "Exchange" / "SMBDEMO" / "Out"
target_dir.mkdir(parents=True)
ok, error = check_dir_write_access(
target_dir="Exchange\\SMBDEMO\\Out",
samba_conn=samba,
source_name="Out",
)
assert ok is False
assert error is not None