from __future__ import annotations import shutil from pathlib import Path from app.pipeline.service import run_batch 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 open(self, path: str, mode: str): return self._to_local(path).open(mode) def upload(self, path_from: str, path_to: str) -> None: source = Path(path_from) target = self._to_local(path_to) target.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(source, target) class FakeSambaDeleteDeniedForXml(FakeSamba): def delete(self, path: str) -> None: normalized = path.replace("/", "\\").lower() if normalized.endswith(".xml") and "\\test\\" in normalized: raise PermissionError("STATUS_ACCESS_DENIED") super().delete(path) def _valid_xml() -> str: return """ <СообщОперКО xmlns="urn:test"> <СлужЧасть><ИдФайл>ID1 <ИнформЧасть> <СведКО> <Операция> <ИдентификаторЗаписи>OP1 <УчастникОп><Тип>01 """ def _fake_opencloud_upload( local_path, space_id, folder_segments, file_name, username, token ) -> str: _ = (local_path, space_id, username, token) return f"https://opencloud.test/{'/'.join(folder_segments)}/{file_name}" def _fake_resolve_personal_drive_id(username, token) -> str: _ = (username, token) return "personal-space-1" def _fake_ensure_opencloud_folder(space_id, folder_segments, username, token) -> None: _ = (space_id, folder_segments, username, token) def test_run_batch_smoke_and_idempotency(tmp_path: Path, monkeypatch) -> None: samba = FakeSamba(tmp_path) in_dir_local = tmp_path / "Exchange" / "SMBDEMO" / "test" in_dir_local.mkdir(parents=True) (in_dir_local / "SKO115FZ_01_123456789_20260616_X00001.xml").write_text( _valid_xml(), encoding="utf-8", ) from app.pipeline import service monkeypatch.setattr( service, "connect_samba", lambda smb_base_path, user_params: samba ) monkeypatch.setattr( service, "upload_local_file_to_opencloud", _fake_opencloud_upload ) monkeypatch.setattr( service, "resolve_personal_drive_id", _fake_resolve_personal_drive_id ) monkeypatch.setattr( service, "ensure_opencloud_folder", _fake_ensure_opencloud_folder ) first = run_batch( smb_base_path="//test-server/share", input_dir="Exchange\\SMBDEMO\\test", samba_user="user", samba_password="pass", launcher="tester", opencloud_username="test-user", opencloud_access_token="token-123", ) assert first.status in {"success", "partial"} assert first.protocol_path assert first.report_path second = run_batch( smb_base_path="//test-server/share", input_dir="Exchange\\SMBDEMO\\test", samba_user="user", samba_password="pass", launcher="tester", opencloud_username="test-user", opencloud_access_token="token-123", ) assert second.status == "success" assert second.processed == 0 def test_run_batch_success_when_delete_denied_after_copy( tmp_path: Path, monkeypatch ) -> None: samba = FakeSambaDeleteDeniedForXml(tmp_path) in_dir_local = tmp_path / "Exchange" / "SMBDEMO" / "test" in_dir_local.mkdir(parents=True) file_name = "SKO115FZ_01_123456789_20260616_X00001.xml" (in_dir_local / file_name).write_text(_valid_xml(), encoding="utf-8") from app.pipeline import service monkeypatch.setattr( service, "connect_samba", lambda smb_base_path, user_params: samba ) monkeypatch.setattr( service, "upload_local_file_to_opencloud", _fake_opencloud_upload ) monkeypatch.setattr( service, "resolve_personal_drive_id", _fake_resolve_personal_drive_id ) monkeypatch.setattr( service, "ensure_opencloud_folder", _fake_ensure_opencloud_folder ) result = run_batch( smb_base_path="//test-server/share", input_dir="Exchange\\SMBDEMO\\test", samba_user="user", samba_password="pass", launcher="tester", opencloud_username="test-user", opencloud_access_token="token-123", ) assert result.status == "success" assert result.processed == 1