"""Ссылки на результат обработки: на файл -> на папку запуска. Ссылка на папку запуска собирается по пути в формате адресной строки веб-интерфейса OpenCloud и есть всегда, поэтому без ссылки пользователь не останется, а обработка из-за ссылок не падает. """ from __future__ import annotations from pathlib import Path from unittest.mock import Mock import requests from app import opencloud_access from app.opencloud_access import OpenCloudUploadResult from app.pipeline import service from app.pipeline.config import build_date_parts from app.pipeline.service import BatchResult, run_batch from main import _result_links from tests.smoke.test_batch_smoke import _patch_opencloud, _valid_xml WEB_SPACE = "https://docs.raisa.go.rshbank.ru/files/spaces/personal" def _loose_xml(tmp_path: Path) -> Path: path = tmp_path / "SKO115FZ_01_123456789_20260616_X00001.xml" path.write_text(_valid_xml(), encoding="utf-8") return path def _run(tmp_path: Path): return run_batch( input_paths=[_loose_xml(tmp_path)], launcher="tester", opencloud_username="test-user", opencloud_access_token="token-123", ) def _run_folder_path() -> str: year, month, day, _ = build_date_parts() return f"Out/{year}/{month}/{day}/1" def test_links_open_files_and_run_folder_in_web_ui(tmp_path: Path, monkeypatch) -> None: _patch_opencloud(monkeypatch) folder_requests: list[str] = [] def _folder_file_id(dav_url, username, token): folder_requests.append(dav_url) return "store$space!folder" monkeypatch.setattr(service, "resolve_opencloud_file_id", _folder_file_id) result = _run(tmp_path) assert result.status == "success" assert result.report_url.startswith("https://opencloud.test/f/") assert result.report_url.endswith(".xlsx") assert result.protocol_url.startswith("https://opencloud.test/f/") assert result.log_url.endswith(".log") # Формат — как в адресной строке интерфейса: путь + fileId папки. assert result.folder_url == ( f"{WEB_SPACE}/test-user/{_run_folder_path()}" "?fileId=store%24space%21folder" ) # fileId запрашивается именно для папки запуска Out/год/месяц/день/номер. assert folder_requests == [ f"https://docs.raisa.go.rshbank.ru/dav/spaces/personal-space-1/" f"{_run_folder_path()}" ] # WebDAV-адреса остаются для протокола и отладки. assert "/Out/" in result.report_path assert not any("веб-интерфейсе" in message for message in result.messages) def test_file_without_link_falls_back_to_run_folder( tmp_path: Path, monkeypatch ) -> None: _patch_opencloud(monkeypatch) def _upload_without_link( local_path, space_id, folder_segments, file_name, username, token ): return OpenCloudUploadResult(dav_url=f"https://dav.test/{file_name}") monkeypatch.setattr(service, "upload_local_file_to_opencloud", _upload_without_link) result = _run(tmp_path) assert result.status == "success" assert result.folder_url.startswith(f"{WEB_SPACE}/test-user/Out/") assert result.report_url == result.folder_url assert result.protocol_url == result.folder_url assert result.log_url == result.folder_url assert any("веб-интерфейсе" in message for message in result.messages) def test_nothing_resolved_still_gives_run_folder_link( tmp_path: Path, monkeypatch ) -> None: """Сквозной сценарий на уровне requests: в ответе на PUT нет OC-FileId, PROPFIND за ссылками падает, Graph API не вернул driveAlias — обработка успешна, все кнопки ведут в папку запуска (путь из логина, без fileId).""" calls: list[tuple[str, str]] = [] def _get(url, **kwargs): return Mock( status_code=200, json=lambda: {"value": [{"id": "s$p", "driveType": "personal"}]}, ) def _request(method, url, **kwargs): depth = kwargs.get("headers", {}).get("Depth") calls.append((method, depth)) if method == "PROPFIND" and depth == "1": return Mock(status_code=404, text="") if method == "MKCOL": return Mock(status_code=201, text="") raise requests.ConnectionError("PROPFIND за ссылкой не прошёл") def _put(url, **kwargs): return Mock(status_code=201, text="", headers={}) monkeypatch.setattr(opencloud_access.requests, "get", _get) monkeypatch.setattr(opencloud_access.requests, "request", _request) monkeypatch.setattr(opencloud_access.requests, "put", _put) result = _run(tmp_path) assert result.status == "success" assert result.folder_url == f"{WEB_SPACE}/test-user/{_run_folder_path()}" assert result.report_url == result.folder_url assert result.protocol_url == result.folder_url assert result.log_url == result.folder_url assert result.report_path.startswith( "https://docs.raisa.go.rshbank.ru/dav/spaces/s$p/Out/" ) # Три файла без OC-FileId + папка запуска: по одному PROPFIND (Depth: 0). assert calls.count(("PROPFIND", "0")) == 4 def test_result_link_buttons_skip_missing_addresses() -> None: result = BatchResult( status="success", processed=1, partial=0, errors=0, report_path="dav-report", protocol_path="dav-protocol", log_path="dav-log", report_url="https://x/f/1", protocol_url="https://x/f/2", log_url="", folder_url="https://x/f/3", ) assert _result_links(result) == [ ("Открыть отчёт", "https://x/f/1"), ("Протокол", "https://x/f/2"), ("Папка с результатами", "https://x/f/3"), ]