SFM/tests/unit/test_file_manager.py
2026-07-29 12:04:26 +03:00

210 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import shutil
from pathlib import Path
from app.pipeline.file_manager import (
SMBClientConnection,
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
def test_smb_connection_registers_sessions_for_multiple_servers(monkeypatch) -> None:
class FakeSmbClient:
def __init__(self) -> None:
self.registered: list[str] = []
def register_session(self, server: str, username: str, password: str) -> None:
_ = username, password
self.registered.append(server)
def stat(self, _path: str) -> None:
return None
fake = FakeSmbClient()
monkeypatch.setattr("app.pipeline.file_manager._import_smbclient", lambda: fake)
conn = SMBClientConnection(
smb_base_path="//sgo-fc01-r06/sfm/ОТЧЕТ_7081У",
user_params={"user_name": "user", "password": "pass"},
)
with conn:
pass
conn.path_exists("//sgo-fc01-r13/inbox-intech/Exchange/Raykov/Input")
assert "sgo-fc01-r06" in fake.registered
assert "sgo-fc01-r13" in fake.registered