182 lines
5.9 KiB
Python
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
|