Switch OpenCloud uploads to per-user personal drives
Files no longer go into a shared test space with a guessed folder name. Instead, resolve each user's personal drive id via GET /graph/v1.0/me/drives (Basic Auth, same App Token as uploads) on every run, and upload into Out/год/месяц/день/номер_запуска inside that drive, creating the path via MKCOL since a fresh personal drive won't have it yet. Removes the now-obsolete OPENCLOUD_SPACE_ID / resolve_opencloud_user_folder and the shared-space smoke script. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
ccb92fd85f
commit
54bf2abc52
@ -10,14 +10,6 @@ SFM_SMB_BASE_PATH = "//sgo-fc01-r06/sfm/ОТЧЕТ_7081У"
|
|||||||
SFM_DEFAULT_INPUT_DIR = r"//sgo-fc01-r06/sfm/ОТЧЕТ_7081У"
|
SFM_DEFAULT_INPUT_DIR = r"//sgo-fc01-r06/sfm/ОТЧЕТ_7081У"
|
||||||
ACCESS_SUPPORT_CONTACT = "администратору СФМ"
|
ACCESS_SUPPORT_CONTACT = "администратору СФМ"
|
||||||
|
|
||||||
# TODO(BIQ-36593): подтвердить SPACE_ID для прода (сейчас — значение,
|
|
||||||
# проверенное вручную на тестовом пространстве "AURORA"). По наблюдениям
|
|
||||||
# ID может быть составным ("storageId$spaceId") — храним как есть, без
|
|
||||||
# разбора, через env-переменную, чтобы не хардкодить прод-значение в коде.
|
|
||||||
OPENCLOUD_SPACE_ID = os.getenv(
|
|
||||||
"SFM_OPENCLOUD_SPACE_ID", "28c0cecf-1e20-4664-b165-5c9d5515e70f"
|
|
||||||
)
|
|
||||||
|
|
||||||
EDIT_PATH_OPEN_KEY = "edit_path_open"
|
EDIT_PATH_OPEN_KEY = "edit_path_open"
|
||||||
EDIT_PATH_VALUE_KEY = "edit_path_value"
|
EDIT_PATH_VALUE_KEY = "edit_path_value"
|
||||||
# Runtime-only user paths storage. In bank contour we use tmpfs.
|
# Runtime-only user paths storage. In bank contour we use tmpfs.
|
||||||
@ -81,24 +73,3 @@ def resolve_opencloud_access_token() -> str | None:
|
|||||||
except Exception: # noqa: BLE001
|
except Exception: # noqa: BLE001
|
||||||
# Локальный тест без поднятой БД/переменных окружения — fallback.
|
# Локальный тест без поднятой БД/переменных окружения — fallback.
|
||||||
return os.getenv("SFM_OPENCLOUD_ACCESS_TOKEN") or None
|
return os.getenv("SFM_OPENCLOUD_ACCESS_TOKEN") or None
|
||||||
|
|
||||||
|
|
||||||
def resolve_opencloud_user_folder() -> str | None:
|
|
||||||
"""Возвращает предполагаемое имя личной папки пользователя в OpenCloud.
|
|
||||||
|
|
||||||
ОТКРЫТЫЙ ВОПРОС: неизвестно, откуда в проде берётся имя папки вида
|
|
||||||
"Райков М.С." — это предположение, что claim "name" из Keycloak уже
|
|
||||||
приходит в этом формате (совпадает с тем, что руками использовалось в
|
|
||||||
open_cloud_upload_fixed.py). Альтернатива — папки создаются заранее
|
|
||||||
администратором по другому принципу (email/preferred_username), это
|
|
||||||
нужно сверить в проде.
|
|
||||||
|
|
||||||
Для локального теста — fallback на переменную окружения
|
|
||||||
SFM_OPENCLOUD_FOLDER из .env.
|
|
||||||
"""
|
|
||||||
profile = st.session_state.get("user_profile", {})
|
|
||||||
if isinstance(profile, dict):
|
|
||||||
value = profile.get("name")
|
|
||||||
if value:
|
|
||||||
return str(value)
|
|
||||||
return os.getenv("SFM_OPENCLOUD_FOLDER") or None
|
|
||||||
|
|||||||
@ -24,6 +24,63 @@ class OpenCloudUploadError(RuntimeError):
|
|||||||
"""Ошибка загрузки файла в OpenCloud по WebDAV."""
|
"""Ошибка загрузки файла в OpenCloud по WebDAV."""
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_personal_drive_id(username: str, token: str) -> str:
|
||||||
|
"""Находит ID личного пространства пользователя через Graph API.
|
||||||
|
|
||||||
|
Больше не грузим файлы в общее тестовое пространство по фиксированному
|
||||||
|
SPACE_ID — у каждого заказчика есть личное пространство (driveType ==
|
||||||
|
"personal" в GET /graph/v1.0/me/drives), и его ID нужно получать заново
|
||||||
|
перед каждой загрузкой: пространство может быть пересоздано, а хранить
|
||||||
|
потенциально устаревший ID рискованнее, чем сделать лишний HTTP-запрос.
|
||||||
|
|
||||||
|
Авторизация — та же Basic Auth (username + App Token), что и для самой
|
||||||
|
загрузки по WebDAV, см. upload_local_file_to_opencloud.
|
||||||
|
"""
|
||||||
|
if not username:
|
||||||
|
raise OpenCloudUploadError(
|
||||||
|
"Не задан username для поиска личного пространства в OpenCloud."
|
||||||
|
)
|
||||||
|
if not token:
|
||||||
|
raise OpenCloudUploadError(
|
||||||
|
"Отсутствует App Token для поиска личного пространства в OpenCloud."
|
||||||
|
)
|
||||||
|
|
||||||
|
url = f"https://{OPENCLOUD_DOMAIN}/graph/v1.0/me/drives"
|
||||||
|
try:
|
||||||
|
response = requests.get(
|
||||||
|
url,
|
||||||
|
auth=HTTPBasicAuth(username, token),
|
||||||
|
timeout=OPENCLOUD_UPLOAD_TIMEOUT_SECONDS,
|
||||||
|
)
|
||||||
|
except requests.RequestException as exc:
|
||||||
|
raise OpenCloudUploadError(
|
||||||
|
f"Ошибка сети при поиске личного пространства OpenCloud: {exc}"
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise OpenCloudUploadError(
|
||||||
|
"Не удалось получить список пространств OpenCloud: "
|
||||||
|
f"HTTP {response.status_code}, body={response.text[:500]!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
drives = response.json().get("value", [])
|
||||||
|
personal_drives = [
|
||||||
|
drive for drive in drives if drive.get("driveType") == "personal"
|
||||||
|
]
|
||||||
|
if not personal_drives:
|
||||||
|
raise OpenCloudUploadError(
|
||||||
|
f"Не найдено личное пространство (driveType='personal') для "
|
||||||
|
f"пользователя '{username}' в OpenCloud."
|
||||||
|
)
|
||||||
|
|
||||||
|
drive_id = personal_drives[0].get("id")
|
||||||
|
if not drive_id:
|
||||||
|
raise OpenCloudUploadError(
|
||||||
|
"Личное пространство найдено в OpenCloud, но у него отсутствует id."
|
||||||
|
)
|
||||||
|
return str(drive_id)
|
||||||
|
|
||||||
|
|
||||||
def build_opencloud_url(
|
def build_opencloud_url(
|
||||||
space_id: str, folder_segments: tuple[str, ...] | list[str], file_name: str
|
space_id: str, folder_segments: tuple[str, ...] | list[str], file_name: str
|
||||||
) -> str:
|
) -> str:
|
||||||
@ -54,6 +111,47 @@ def build_opencloud_url(
|
|||||||
return f"https://{OPENCLOUD_DOMAIN}{path}"
|
return f"https://{OPENCLOUD_DOMAIN}{path}"
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_opencloud_folder(
|
||||||
|
space_id: str,
|
||||||
|
folder_segments: tuple[str, ...] | list[str],
|
||||||
|
username: str,
|
||||||
|
token: str,
|
||||||
|
) -> None:
|
||||||
|
"""Создаёт папку folder_segments в личном пространстве через MKCOL.
|
||||||
|
|
||||||
|
WebDAV PUT возвращает 409 Conflict, если хотя бы одна промежуточная
|
||||||
|
коллекция ещё не существует, поэтому перед первой загрузкой в новый
|
||||||
|
путь (Out/год/месяц/день/номер_запуска) создаём его сегмент за
|
||||||
|
сегментом — так же, как ensure_dir_exists делает это для SMB.
|
||||||
|
"""
|
||||||
|
segments = [segment for segment in folder_segments if segment]
|
||||||
|
accumulated: list[str] = []
|
||||||
|
for segment in segments:
|
||||||
|
accumulated.append(segment)
|
||||||
|
encoded_path = "/".join(quote(part, safe="") for part in accumulated)
|
||||||
|
url = f"https://{OPENCLOUD_DOMAIN}/dav/spaces/{space_id}/{encoded_path}"
|
||||||
|
try:
|
||||||
|
response = requests.request(
|
||||||
|
"MKCOL",
|
||||||
|
url,
|
||||||
|
auth=HTTPBasicAuth(username, token),
|
||||||
|
timeout=OPENCLOUD_UPLOAD_TIMEOUT_SECONDS,
|
||||||
|
)
|
||||||
|
except requests.RequestException as exc:
|
||||||
|
raise OpenCloudUploadError(
|
||||||
|
f"Ошибка сети при создании папки в OpenCloud: url='{url}', {exc}"
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
# 201 — папка создана, 405 — уже существует (MKCOL на существующую
|
||||||
|
# коллекцию запрещён спецификацией WebDAV, это не ошибка для нас).
|
||||||
|
if response.status_code not in (201, 405):
|
||||||
|
raise OpenCloudUploadError(
|
||||||
|
"Не удалось создать папку в OpenCloud: "
|
||||||
|
f"HTTP {response.status_code}, url='{url}', "
|
||||||
|
f"body={response.text[:500]!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def upload_local_file_to_opencloud(
|
def upload_local_file_to_opencloud(
|
||||||
local_path: Path,
|
local_path: Path,
|
||||||
space_id: str,
|
space_id: str,
|
||||||
@ -74,11 +172,9 @@ def upload_local_file_to_opencloud(
|
|||||||
OpenCloud (несовпадение audience) — App Token через Basic Auth работает,
|
OpenCloud (несовпадение audience) — App Token через Basic Auth работает,
|
||||||
т.к. идёт отдельным от OIDC путём авторизации.
|
т.к. идёт отдельным от OIDC путём авторизации.
|
||||||
|
|
||||||
ВРЕМЕННО (тестовый прогон): папку НЕ создаём (без MKCOL) — просто
|
Промежуточные папки (folder_segments) должны быть уже созданы через
|
||||||
кладём файл прямо в личную папку участника (folder_segments — как
|
ensure_opencloud_folder — сам PUT их не создаёт и вернёт 409, если
|
||||||
правило один сегмент, например "Райков М.С."). Структуру Out/год/
|
хотя бы одна коллекция на пути отсутствует.
|
||||||
месяц/день/номер_запуска вернём отдельным шагом после того, как
|
|
||||||
подтвердим на тесте базовый сценарий "файл долетает в нужную папку".
|
|
||||||
"""
|
"""
|
||||||
if not username:
|
if not username:
|
||||||
raise OpenCloudUploadError(
|
raise OpenCloudUploadError(
|
||||||
|
|||||||
@ -26,6 +26,9 @@ class RunContext:
|
|||||||
processed_dir: str
|
processed_dir: str
|
||||||
run_number: int
|
run_number: int
|
||||||
date_key: str
|
date_key: str
|
||||||
|
year: str
|
||||||
|
month: str
|
||||||
|
day: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -273,7 +276,7 @@ def create_run_context(
|
|||||||
run_number = next_run_number(out_day_dir, samba_conn)
|
run_number = next_run_number(out_day_dir, samba_conn)
|
||||||
run_dir = join_path(out_day_dir, str(run_number))
|
run_dir = join_path(out_day_dir, str(run_number))
|
||||||
ensure_dir_exists(run_dir, samba_conn)
|
ensure_dir_exists(run_dir, samba_conn)
|
||||||
_, _, _, date_key = build_date_parts(now)
|
year, month, day, date_key = build_date_parts(now)
|
||||||
return RunContext(
|
return RunContext(
|
||||||
run_dir=run_dir,
|
run_dir=run_dir,
|
||||||
out_day_dir=out_day_dir,
|
out_day_dir=out_day_dir,
|
||||||
@ -281,6 +284,9 @@ def create_run_context(
|
|||||||
processed_dir=processed_dir,
|
processed_dir=processed_dir,
|
||||||
run_number=run_number,
|
run_number=run_number,
|
||||||
date_key=date_key,
|
date_key=date_key,
|
||||||
|
year=year,
|
||||||
|
month=month,
|
||||||
|
day=day,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -7,8 +7,14 @@ from dataclasses import dataclass, field
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ..opencloud_access import OpenCloudUploadError, upload_local_file_to_opencloud
|
from ..opencloud_access import (
|
||||||
|
OpenCloudUploadError,
|
||||||
|
ensure_opencloud_folder,
|
||||||
|
resolve_personal_drive_id,
|
||||||
|
upload_local_file_to_opencloud,
|
||||||
|
)
|
||||||
from .config import (
|
from .config import (
|
||||||
|
DEFAULT_OUTPUT_DIR_NAME,
|
||||||
build_date_parts,
|
build_date_parts,
|
||||||
build_process_log_file_name,
|
build_process_log_file_name,
|
||||||
build_protocol_file_name,
|
build_protocol_file_name,
|
||||||
@ -52,8 +58,6 @@ def run_batch(
|
|||||||
samba_user: str,
|
samba_user: str,
|
||||||
samba_password: str,
|
samba_password: str,
|
||||||
launcher: str,
|
launcher: str,
|
||||||
opencloud_space_id: str,
|
|
||||||
opencloud_folder: str | None,
|
|
||||||
opencloud_username: str | None,
|
opencloud_username: str | None,
|
||||||
opencloud_access_token: str | None,
|
opencloud_access_token: str | None,
|
||||||
artifacts_dir: str | None = None,
|
artifacts_dir: str | None = None,
|
||||||
@ -96,17 +100,6 @@ def run_batch(
|
|||||||
"(проверьте таблицу токенов)."
|
"(проверьте таблицу токенов)."
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
if not opencloud_folder:
|
|
||||||
return BatchResult(
|
|
||||||
status="error",
|
|
||||||
processed=0,
|
|
||||||
partial=0,
|
|
||||||
errors=1,
|
|
||||||
report_path="",
|
|
||||||
protocol_path="",
|
|
||||||
log_path="",
|
|
||||||
messages=["Не удалось определить личную папку пользователя в OpenCloud."],
|
|
||||||
)
|
|
||||||
|
|
||||||
samba_conn = connect_samba(
|
samba_conn = connect_samba(
|
||||||
smb_base_path=smb_base_path,
|
smb_base_path=smb_base_path,
|
||||||
@ -256,10 +249,12 @@ def run_batch(
|
|||||||
report_file_name=report_file_name,
|
report_file_name=report_file_name,
|
||||||
protocol_file_name=protocol_file_name,
|
protocol_file_name=protocol_file_name,
|
||||||
log_file_name=log_file_name,
|
log_file_name=log_file_name,
|
||||||
opencloud_space_id=opencloud_space_id,
|
|
||||||
opencloud_folder=opencloud_folder,
|
|
||||||
opencloud_username=opencloud_username,
|
opencloud_username=opencloud_username,
|
||||||
opencloud_access_token=opencloud_access_token,
|
opencloud_access_token=opencloud_access_token,
|
||||||
|
year=run_context.year,
|
||||||
|
month=run_context.month,
|
||||||
|
day=run_context.day,
|
||||||
|
run_number=run_context.run_number,
|
||||||
)
|
)
|
||||||
except OpenCloudUploadError as exc:
|
except OpenCloudUploadError as exc:
|
||||||
return BatchResult(
|
return BatchResult(
|
||||||
@ -312,8 +307,6 @@ def run_batch(
|
|||||||
def run_local_zip_batch(
|
def run_local_zip_batch(
|
||||||
zip_path: Path,
|
zip_path: Path,
|
||||||
launcher: str,
|
launcher: str,
|
||||||
opencloud_space_id: str,
|
|
||||||
opencloud_folder: str | None,
|
|
||||||
opencloud_username: str | None,
|
opencloud_username: str | None,
|
||||||
opencloud_access_token: str | None,
|
opencloud_access_token: str | None,
|
||||||
) -> BatchResult:
|
) -> BatchResult:
|
||||||
@ -352,17 +345,6 @@ def run_local_zip_batch(
|
|||||||
"(проверьте таблицу токенов)."
|
"(проверьте таблицу токенов)."
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
if not opencloud_folder:
|
|
||||||
return BatchResult(
|
|
||||||
status="error",
|
|
||||||
processed=0,
|
|
||||||
partial=0,
|
|
||||||
errors=1,
|
|
||||||
report_path="",
|
|
||||||
protocol_path="",
|
|
||||||
log_path="",
|
|
||||||
messages=["Не удалось определить личную папку пользователя в OpenCloud."],
|
|
||||||
)
|
|
||||||
|
|
||||||
protocol_rows: list[ProtocolRow] = []
|
protocol_rows: list[ProtocolRow] = []
|
||||||
log_lines: list[str] = [f"{_now()} | local zip batch start: {zip_path.name}"]
|
log_lines: list[str] = [f"{_now()} | local zip batch start: {zip_path.name}"]
|
||||||
@ -452,7 +434,7 @@ def run_local_zip_batch(
|
|||||||
)
|
)
|
||||||
log_lines.append(f"{_now()} | empty batch")
|
log_lines.append(f"{_now()} | empty batch")
|
||||||
|
|
||||||
_, _, _, date_key = build_date_parts()
|
year, month, day, date_key = build_date_parts()
|
||||||
run_number = 1
|
run_number = 1
|
||||||
report_file_name = build_report_file_name(date_key, run_number)
|
report_file_name = build_report_file_name(date_key, run_number)
|
||||||
protocol_file_name = build_protocol_file_name(date_key, run_number)
|
protocol_file_name = build_protocol_file_name(date_key, run_number)
|
||||||
@ -476,10 +458,12 @@ def run_local_zip_batch(
|
|||||||
report_file_name=report_file_name,
|
report_file_name=report_file_name,
|
||||||
protocol_file_name=protocol_file_name,
|
protocol_file_name=protocol_file_name,
|
||||||
log_file_name=log_file_name,
|
log_file_name=log_file_name,
|
||||||
opencloud_space_id=opencloud_space_id,
|
|
||||||
opencloud_folder=opencloud_folder,
|
|
||||||
opencloud_username=opencloud_username,
|
opencloud_username=opencloud_username,
|
||||||
opencloud_access_token=opencloud_access_token,
|
opencloud_access_token=opencloud_access_token,
|
||||||
|
year=year,
|
||||||
|
month=month,
|
||||||
|
day=day,
|
||||||
|
run_number=run_number,
|
||||||
)
|
)
|
||||||
except OpenCloudUploadError as exc:
|
except OpenCloudUploadError as exc:
|
||||||
return BatchResult(
|
return BatchResult(
|
||||||
@ -691,22 +675,30 @@ def _upload_artifacts(
|
|||||||
report_file_name: str,
|
report_file_name: str,
|
||||||
protocol_file_name: str,
|
protocol_file_name: str,
|
||||||
log_file_name: str,
|
log_file_name: str,
|
||||||
opencloud_space_id: str,
|
|
||||||
opencloud_folder: str,
|
|
||||||
opencloud_username: str,
|
opencloud_username: str,
|
||||||
opencloud_access_token: str,
|
opencloud_access_token: str,
|
||||||
|
year: str,
|
||||||
|
month: str,
|
||||||
|
day: str,
|
||||||
|
run_number: int,
|
||||||
) -> tuple[str, str, str]:
|
) -> tuple[str, str, str]:
|
||||||
# ВРЕМЕННО (тестовый прогон): грузим плоско, прямо в личную папку
|
# Личное пространство ищем заново перед каждой загрузкой (не кешируем
|
||||||
# участника, без MKCOL и без структуры Out/год/месяц/день/номер запуска.
|
# ID) — см. resolve_personal_drive_id. Кладём файлы по той же структуре
|
||||||
# Сначала проверяем на тесте, что файл вообще долетает и попадает в
|
# Out/год/месяц/день/номер_запуска, что и на SMB (RunContext.run_dir).
|
||||||
# нужную папку — структуру подпапок вернём отдельным шагом.
|
|
||||||
#
|
#
|
||||||
# opencloud_access_token здесь — App Token заказчика (не Bearer/ID-токен),
|
# opencloud_access_token здесь — App Token заказчика (не Bearer/ID-токен),
|
||||||
# авторизация идёт через HTTP Basic Auth вместе с opencloud_username.
|
# авторизация идёт через HTTP Basic Auth вместе с opencloud_username.
|
||||||
folder_segments = (opencloud_folder,)
|
space_id = resolve_personal_drive_id(opencloud_username, opencloud_access_token)
|
||||||
|
folder_segments = (DEFAULT_OUTPUT_DIR_NAME, year, month, day, str(run_number))
|
||||||
|
ensure_opencloud_folder(
|
||||||
|
space_id,
|
||||||
|
folder_segments,
|
||||||
|
username=opencloud_username,
|
||||||
|
token=opencloud_access_token,
|
||||||
|
)
|
||||||
report_url = upload_local_file_to_opencloud(
|
report_url = upload_local_file_to_opencloud(
|
||||||
local_report,
|
local_report,
|
||||||
space_id=opencloud_space_id,
|
space_id=space_id,
|
||||||
folder_segments=folder_segments,
|
folder_segments=folder_segments,
|
||||||
file_name=report_file_name,
|
file_name=report_file_name,
|
||||||
username=opencloud_username,
|
username=opencloud_username,
|
||||||
@ -714,7 +706,7 @@ def _upload_artifacts(
|
|||||||
)
|
)
|
||||||
protocol_url = upload_local_file_to_opencloud(
|
protocol_url = upload_local_file_to_opencloud(
|
||||||
local_protocol,
|
local_protocol,
|
||||||
space_id=opencloud_space_id,
|
space_id=space_id,
|
||||||
folder_segments=folder_segments,
|
folder_segments=folder_segments,
|
||||||
file_name=protocol_file_name,
|
file_name=protocol_file_name,
|
||||||
username=opencloud_username,
|
username=opencloud_username,
|
||||||
@ -722,7 +714,7 @@ def _upload_artifacts(
|
|||||||
)
|
)
|
||||||
log_url = upload_local_file_to_opencloud(
|
log_url = upload_local_file_to_opencloud(
|
||||||
local_log,
|
local_log,
|
||||||
space_id=opencloud_space_id,
|
space_id=space_id,
|
||||||
folder_segments=folder_segments,
|
folder_segments=folder_segments,
|
||||||
file_name=log_file_name,
|
file_name=log_file_name,
|
||||||
username=opencloud_username,
|
username=opencloud_username,
|
||||||
|
|||||||
4
main.py
4
main.py
@ -3,10 +3,8 @@ from dotenv import load_dotenv
|
|||||||
|
|
||||||
from app.app_config import (
|
from app.app_config import (
|
||||||
ACCESS_SUPPORT_CONTACT,
|
ACCESS_SUPPORT_CONTACT,
|
||||||
OPENCLOUD_SPACE_ID,
|
|
||||||
SFM_SMB_BASE_PATH,
|
SFM_SMB_BASE_PATH,
|
||||||
resolve_opencloud_access_token,
|
resolve_opencloud_access_token,
|
||||||
resolve_opencloud_user_folder,
|
|
||||||
resolve_opencloud_username,
|
resolve_opencloud_username,
|
||||||
resolve_user_id,
|
resolve_user_id,
|
||||||
)
|
)
|
||||||
@ -87,8 +85,6 @@ def main() -> None:
|
|||||||
samba_user=samba_user,
|
samba_user=samba_user,
|
||||||
samba_password=samba_password,
|
samba_password=samba_password,
|
||||||
launcher=launcher_name,
|
launcher=launcher_name,
|
||||||
opencloud_space_id=OPENCLOUD_SPACE_ID,
|
|
||||||
opencloud_folder=resolve_opencloud_user_folder(),
|
|
||||||
opencloud_username=resolve_opencloud_username(),
|
opencloud_username=resolve_opencloud_username(),
|
||||||
opencloud_access_token=resolve_opencloud_access_token(),
|
opencloud_access_token=resolve_opencloud_access_token(),
|
||||||
artifacts_dir=resolved_input_dir,
|
artifacts_dir=resolved_input_dir,
|
||||||
|
|||||||
@ -1,55 +0,0 @@
|
|||||||
"""Локальный ручной тест: чисто attach в OpenCloud, без Streamlit.
|
|
||||||
|
|
||||||
Использование:
|
|
||||||
1. Впишите ниже ACCESS_TOKEN, SPACE_ID (если отличается от дефолта),
|
|
||||||
FOLDER и путь к тестовому файлу.
|
|
||||||
2. Запустите: python scripts/local_opencloud_smoke.py
|
|
||||||
|
|
||||||
Использует тот же app/opencloud_access.py, что и продакшн-код в
|
|
||||||
app/pipeline/service.py — так что если тут сработает, значит и в
|
|
||||||
приложении сработает (сама логика загрузки не отличается).
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
||||||
|
|
||||||
from app.opencloud_access import OpenCloudUploadError, upload_local_file_to_opencloud
|
|
||||||
|
|
||||||
# --- Заполните перед запуском ---
|
|
||||||
USERNAME = "PASTE_USERNAME_HERE" # логин, под которым создан App Token в OpenCloud
|
|
||||||
APP_TOKEN = "PASTE_APP_TOKEN_HERE" # App Token из веб-интерфейса OpenCloud (6 слов через пробел)
|
|
||||||
SPACE_ID = "28c0cecf-1e20-4664-b165-5c9d5515e70f" # тестовое пространство AURORA
|
|
||||||
FOLDER = "Райков М.С." # ваша личная папка в OpenCloud
|
|
||||||
LOCAL_FILE = Path(__file__).resolve().parent / "test_upload.xlsx"
|
|
||||||
REMOTE_FILE_NAME = "test_upload.xlsx"
|
|
||||||
# ---------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
if not LOCAL_FILE.exists():
|
|
||||||
LOCAL_FILE.write_bytes(b"local opencloud smoke test")
|
|
||||||
print(f"Тестовый файл создан: {LOCAL_FILE}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
url = upload_local_file_to_opencloud(
|
|
||||||
LOCAL_FILE,
|
|
||||||
space_id=SPACE_ID,
|
|
||||||
folder_segments=(FOLDER,),
|
|
||||||
file_name=REMOTE_FILE_NAME,
|
|
||||||
username=USERNAME,
|
|
||||||
token=APP_TOKEN,
|
|
||||||
)
|
|
||||||
except OpenCloudUploadError as exc:
|
|
||||||
print(f"ОШИБКА: {exc}")
|
|
||||||
raise SystemExit(1) from exc
|
|
||||||
|
|
||||||
print(f"OK, загружено: {url}")
|
|
||||||
print("Проверьте файл в веб-интерфейсе OpenCloud, в папке", FOLDER)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@ -8,8 +8,10 @@
|
|||||||
Запуск:
|
Запуск:
|
||||||
streamlit run scripts/local_zip_test_app.py
|
streamlit run scripts/local_zip_test_app.py
|
||||||
|
|
||||||
Токен/папку можно один раз прописать в .env (см. .env.example) — поля
|
Username/токен можно один раз прописать в .env (см. .env.example) — поля
|
||||||
на странице предзаполнятся, но их всегда можно поменять руками.
|
на странице предзаполнятся, но их всегда можно поменять руками. Папка не
|
||||||
|
запрашивается — файлы летят в личное пространство пользователя (id
|
||||||
|
находится через GET /graph/v1.0/me/drives, см. app/opencloud_access.py).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@ -24,7 +26,6 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|||||||
import streamlit as st
|
import streamlit as st
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from app.app_config import OPENCLOUD_SPACE_ID
|
|
||||||
from app.pipeline.service import run_local_zip_batch
|
from app.pipeline.service import run_local_zip_batch
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
@ -53,15 +54,6 @@ access_token = st.text_input(
|
|||||||
"SFM_OPENCLOUD_ACCESS_TOKEN."
|
"SFM_OPENCLOUD_ACCESS_TOKEN."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
space_id = st.text_input(
|
|
||||||
"SPACE_ID",
|
|
||||||
value=os.getenv("SFM_OPENCLOUD_SPACE_ID", OPENCLOUD_SPACE_ID),
|
|
||||||
)
|
|
||||||
folder = st.text_input(
|
|
||||||
"Папка (личная папка участника в OpenCloud)",
|
|
||||||
value=os.getenv("SFM_OPENCLOUD_FOLDER", ""),
|
|
||||||
placeholder="Райков М.С.",
|
|
||||||
)
|
|
||||||
launcher = st.text_input("Запустил (для протокола)", value="local-test")
|
launcher = st.text_input("Запустил (для протокола)", value="local-test")
|
||||||
|
|
||||||
uploaded_zip = st.file_uploader("Архив с XML-файлами (.zip)", type=["zip"])
|
uploaded_zip = st.file_uploader("Архив с XML-файлами (.zip)", type=["zip"])
|
||||||
@ -71,8 +63,6 @@ if st.button("Начать обработку", type="primary", use_container_wi
|
|||||||
st.error("Укажите username.")
|
st.error("Укажите username.")
|
||||||
elif not access_token:
|
elif not access_token:
|
||||||
st.error("Укажите App Token.")
|
st.error("Укажите App Token.")
|
||||||
elif not folder:
|
|
||||||
st.error("Укажите папку.")
|
|
||||||
elif uploaded_zip is None:
|
elif uploaded_zip is None:
|
||||||
st.error("Прикрепите .zip-архив.")
|
st.error("Прикрепите .zip-архив.")
|
||||||
else:
|
else:
|
||||||
@ -83,8 +73,6 @@ if st.button("Начать обработку", type="primary", use_container_wi
|
|||||||
result = run_local_zip_batch(
|
result = run_local_zip_batch(
|
||||||
zip_path=zip_path,
|
zip_path=zip_path,
|
||||||
launcher=launcher or "local-test",
|
launcher=launcher or "local-test",
|
||||||
opencloud_space_id=space_id,
|
|
||||||
opencloud_folder=folder,
|
|
||||||
opencloud_username=username,
|
opencloud_username=username,
|
||||||
opencloud_access_token=access_token,
|
opencloud_access_token=access_token,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -93,6 +93,15 @@ def _fake_opencloud_upload(
|
|||||||
return f"https://opencloud.test/{'/'.join(folder_segments)}/{file_name}"
|
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:
|
def test_run_batch_smoke_and_idempotency(tmp_path: Path, monkeypatch) -> None:
|
||||||
samba = FakeSamba(tmp_path)
|
samba = FakeSamba(tmp_path)
|
||||||
in_dir_local = tmp_path / "Exchange" / "SMBDEMO" / "test"
|
in_dir_local = tmp_path / "Exchange" / "SMBDEMO" / "test"
|
||||||
@ -110,6 +119,12 @@ def test_run_batch_smoke_and_idempotency(tmp_path: Path, monkeypatch) -> None:
|
|||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
service, "upload_local_file_to_opencloud", _fake_opencloud_upload
|
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(
|
first = run_batch(
|
||||||
smb_base_path="//test-server/share",
|
smb_base_path="//test-server/share",
|
||||||
@ -117,8 +132,6 @@ def test_run_batch_smoke_and_idempotency(tmp_path: Path, monkeypatch) -> None:
|
|||||||
samba_user="user",
|
samba_user="user",
|
||||||
samba_password="pass",
|
samba_password="pass",
|
||||||
launcher="tester",
|
launcher="tester",
|
||||||
opencloud_space_id="space-1",
|
|
||||||
opencloud_folder="Тестов Т.Т.",
|
|
||||||
opencloud_username="test-user",
|
opencloud_username="test-user",
|
||||||
opencloud_access_token="token-123",
|
opencloud_access_token="token-123",
|
||||||
)
|
)
|
||||||
@ -132,8 +145,6 @@ def test_run_batch_smoke_and_idempotency(tmp_path: Path, monkeypatch) -> None:
|
|||||||
samba_user="user",
|
samba_user="user",
|
||||||
samba_password="pass",
|
samba_password="pass",
|
||||||
launcher="tester",
|
launcher="tester",
|
||||||
opencloud_space_id="space-1",
|
|
||||||
opencloud_folder="Тестов Т.Т.",
|
|
||||||
opencloud_username="test-user",
|
opencloud_username="test-user",
|
||||||
opencloud_access_token="token-123",
|
opencloud_access_token="token-123",
|
||||||
)
|
)
|
||||||
@ -158,6 +169,12 @@ def test_run_batch_success_when_delete_denied_after_copy(
|
|||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
service, "upload_local_file_to_opencloud", _fake_opencloud_upload
|
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(
|
result = run_batch(
|
||||||
smb_base_path="//test-server/share",
|
smb_base_path="//test-server/share",
|
||||||
@ -165,8 +182,6 @@ def test_run_batch_success_when_delete_denied_after_copy(
|
|||||||
samba_user="user",
|
samba_user="user",
|
||||||
samba_password="pass",
|
samba_password="pass",
|
||||||
launcher="tester",
|
launcher="tester",
|
||||||
opencloud_space_id="space-1",
|
|
||||||
opencloud_folder="Тестов Т.Т.",
|
|
||||||
opencloud_username="test-user",
|
opencloud_username="test-user",
|
||||||
opencloud_access_token="token-123",
|
opencloud_access_token="token-123",
|
||||||
)
|
)
|
||||||
|
|||||||
@ -30,10 +30,29 @@ def _fake_opencloud_upload(
|
|||||||
return f"https://opencloud.test/{'/'.join(folder_segments)}/{file_name}"
|
return f"https://opencloud.test/{'/'.join(folder_segments)}/{file_name}"
|
||||||
|
|
||||||
|
|
||||||
def test_run_local_zip_batch_success(tmp_path: Path, monkeypatch) -> None:
|
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 _patch_opencloud(monkeypatch) -> None:
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
service, "upload_local_file_to_opencloud", _fake_opencloud_upload
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_local_zip_batch_success(tmp_path: Path, monkeypatch) -> None:
|
||||||
|
_patch_opencloud(monkeypatch)
|
||||||
|
|
||||||
zip_path = tmp_path / "batch.zip"
|
zip_path = tmp_path / "batch.zip"
|
||||||
with zipfile.ZipFile(zip_path, "w") as archive:
|
with zipfile.ZipFile(zip_path, "w") as archive:
|
||||||
@ -42,8 +61,6 @@ def test_run_local_zip_batch_success(tmp_path: Path, monkeypatch) -> None:
|
|||||||
result = run_local_zip_batch(
|
result = run_local_zip_batch(
|
||||||
zip_path=zip_path,
|
zip_path=zip_path,
|
||||||
launcher="tester",
|
launcher="tester",
|
||||||
opencloud_space_id="space-1",
|
|
||||||
opencloud_folder="Тестов Т.Т.",
|
|
||||||
opencloud_username="test-user",
|
opencloud_username="test-user",
|
||||||
opencloud_access_token="token-123",
|
opencloud_access_token="token-123",
|
||||||
)
|
)
|
||||||
@ -59,9 +76,7 @@ def test_run_local_zip_batch_success(tmp_path: Path, monkeypatch) -> None:
|
|||||||
def test_run_local_zip_batch_bad_file_name_is_error(
|
def test_run_local_zip_batch_bad_file_name_is_error(
|
||||||
tmp_path: Path, monkeypatch
|
tmp_path: Path, monkeypatch
|
||||||
) -> None:
|
) -> None:
|
||||||
monkeypatch.setattr(
|
_patch_opencloud(monkeypatch)
|
||||||
service, "upload_local_file_to_opencloud", _fake_opencloud_upload
|
|
||||||
)
|
|
||||||
|
|
||||||
zip_path = tmp_path / "batch.zip"
|
zip_path = tmp_path / "batch.zip"
|
||||||
with zipfile.ZipFile(zip_path, "w") as archive:
|
with zipfile.ZipFile(zip_path, "w") as archive:
|
||||||
@ -70,8 +85,6 @@ def test_run_local_zip_batch_bad_file_name_is_error(
|
|||||||
result = run_local_zip_batch(
|
result = run_local_zip_batch(
|
||||||
zip_path=zip_path,
|
zip_path=zip_path,
|
||||||
launcher="tester",
|
launcher="tester",
|
||||||
opencloud_space_id="space-1",
|
|
||||||
opencloud_folder="Тестов Т.Т.",
|
|
||||||
opencloud_username="test-user",
|
opencloud_username="test-user",
|
||||||
opencloud_access_token="token-123",
|
opencloud_access_token="token-123",
|
||||||
)
|
)
|
||||||
@ -82,9 +95,7 @@ def test_run_local_zip_batch_bad_file_name_is_error(
|
|||||||
|
|
||||||
|
|
||||||
def test_run_local_zip_batch_empty_archive(tmp_path: Path, monkeypatch) -> None:
|
def test_run_local_zip_batch_empty_archive(tmp_path: Path, monkeypatch) -> None:
|
||||||
monkeypatch.setattr(
|
_patch_opencloud(monkeypatch)
|
||||||
service, "upload_local_file_to_opencloud", _fake_opencloud_upload
|
|
||||||
)
|
|
||||||
|
|
||||||
zip_path = tmp_path / "empty.zip"
|
zip_path = tmp_path / "empty.zip"
|
||||||
with zipfile.ZipFile(zip_path, "w") as archive:
|
with zipfile.ZipFile(zip_path, "w") as archive:
|
||||||
@ -93,8 +104,6 @@ def test_run_local_zip_batch_empty_archive(tmp_path: Path, monkeypatch) -> None:
|
|||||||
result = run_local_zip_batch(
|
result = run_local_zip_batch(
|
||||||
zip_path=zip_path,
|
zip_path=zip_path,
|
||||||
launcher="tester",
|
launcher="tester",
|
||||||
opencloud_space_id="space-1",
|
|
||||||
opencloud_folder="Тестов Т.Т.",
|
|
||||||
opencloud_username="test-user",
|
opencloud_username="test-user",
|
||||||
opencloud_access_token="token-123",
|
opencloud_access_token="token-123",
|
||||||
)
|
)
|
||||||
@ -111,8 +120,6 @@ def test_run_local_zip_batch_missing_token(tmp_path: Path) -> None:
|
|||||||
result = run_local_zip_batch(
|
result = run_local_zip_batch(
|
||||||
zip_path=zip_path,
|
zip_path=zip_path,
|
||||||
launcher="tester",
|
launcher="tester",
|
||||||
opencloud_space_id="space-1",
|
|
||||||
opencloud_folder="Тестов Т.Т.",
|
|
||||||
opencloud_username="test-user",
|
opencloud_username="test-user",
|
||||||
opencloud_access_token="",
|
opencloud_access_token="",
|
||||||
)
|
)
|
||||||
@ -129,8 +136,6 @@ def test_run_local_zip_batch_missing_username(tmp_path: Path) -> None:
|
|||||||
result = run_local_zip_batch(
|
result = run_local_zip_batch(
|
||||||
zip_path=zip_path,
|
zip_path=zip_path,
|
||||||
launcher="tester",
|
launcher="tester",
|
||||||
opencloud_space_id="space-1",
|
|
||||||
opencloud_folder="Тестов Т.Т.",
|
|
||||||
opencloud_username="",
|
opencloud_username="",
|
||||||
opencloud_access_token="token-123",
|
opencloud_access_token="token-123",
|
||||||
)
|
)
|
||||||
@ -146,8 +151,6 @@ def test_run_local_zip_batch_bad_zip_file(tmp_path: Path) -> None:
|
|||||||
result = run_local_zip_batch(
|
result = run_local_zip_batch(
|
||||||
zip_path=zip_path,
|
zip_path=zip_path,
|
||||||
launcher="tester",
|
launcher="tester",
|
||||||
opencloud_space_id="space-1",
|
|
||||||
opencloud_folder="Тестов Т.Т.",
|
|
||||||
opencloud_username="test-user",
|
opencloud_username="test-user",
|
||||||
opencloud_access_token="token-123",
|
opencloud_access_token="token-123",
|
||||||
)
|
)
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import pytest
|
|||||||
from app.opencloud_access import (
|
from app.opencloud_access import (
|
||||||
OpenCloudUploadError,
|
OpenCloudUploadError,
|
||||||
build_opencloud_url,
|
build_opencloud_url,
|
||||||
|
ensure_opencloud_folder,
|
||||||
|
resolve_personal_drive_id,
|
||||||
upload_local_file_to_opencloud,
|
upload_local_file_to_opencloud,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -102,6 +104,88 @@ def test_upload_local_file_to_opencloud_missing_local_file(tmp_path: Path) -> No
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_personal_drive_id_returns_personal_drive() -> None:
|
||||||
|
fake_response = Mock(
|
||||||
|
status_code=200,
|
||||||
|
json=lambda: {
|
||||||
|
"value": [
|
||||||
|
{"id": "storage1$shared1", "driveType": "project"},
|
||||||
|
{"id": "storage1$personal1", "driveType": "personal"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
with patch("app.opencloud_access.requests.get", return_value=fake_response) as get:
|
||||||
|
drive_id = resolve_personal_drive_id("raykovmse", "app token here")
|
||||||
|
|
||||||
|
assert drive_id == "storage1$personal1"
|
||||||
|
args, kwargs = get.call_args
|
||||||
|
assert args[0] == "https://docs.raisa.go.rshbank.ru/graph/v1.0/me/drives"
|
||||||
|
auth = kwargs["auth"]
|
||||||
|
assert auth.username == "raykovmse"
|
||||||
|
assert auth.password == "app token here"
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_personal_drive_id_missing_personal_drive_raises() -> None:
|
||||||
|
fake_response = Mock(
|
||||||
|
status_code=200,
|
||||||
|
json=lambda: {"value": [{"id": "storage1$shared1", "driveType": "project"}]},
|
||||||
|
)
|
||||||
|
with patch("app.opencloud_access.requests.get", return_value=fake_response):
|
||||||
|
with pytest.raises(OpenCloudUploadError):
|
||||||
|
resolve_personal_drive_id("raykovmse", "app token here")
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_personal_drive_id_http_error_raises() -> None:
|
||||||
|
fake_response = Mock(status_code=401, text="Unauthorized")
|
||||||
|
with patch("app.opencloud_access.requests.get", return_value=fake_response):
|
||||||
|
with pytest.raises(OpenCloudUploadError):
|
||||||
|
resolve_personal_drive_id("raykovmse", "app token here")
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_personal_drive_id_requires_username_and_token() -> None:
|
||||||
|
with pytest.raises(OpenCloudUploadError):
|
||||||
|
resolve_personal_drive_id("", "app token here")
|
||||||
|
with pytest.raises(OpenCloudUploadError):
|
||||||
|
resolve_personal_drive_id("raykovmse", "")
|
||||||
|
|
||||||
|
|
||||||
|
def test_ensure_opencloud_folder_creates_each_segment_via_mkcol() -> None:
|
||||||
|
fake_response = Mock(status_code=201, text="")
|
||||||
|
with patch(
|
||||||
|
"app.opencloud_access.requests.request", return_value=fake_response
|
||||||
|
) as request:
|
||||||
|
ensure_opencloud_folder(
|
||||||
|
"abc$def",
|
||||||
|
("Out", "2026", "9", "17", "1"),
|
||||||
|
username="raykovmse",
|
||||||
|
token="app token here",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert request.call_count == 5
|
||||||
|
method, url = request.call_args_list[-1].args
|
||||||
|
assert method == "MKCOL"
|
||||||
|
assert url == (
|
||||||
|
"https://docs.raisa.go.rshbank.ru/dav/spaces/abc$def/" "Out/2026/9/17/1"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ensure_opencloud_folder_tolerates_already_existing_collection() -> None:
|
||||||
|
fake_response = Mock(status_code=405, text="")
|
||||||
|
with patch("app.opencloud_access.requests.request", return_value=fake_response):
|
||||||
|
ensure_opencloud_folder(
|
||||||
|
"abc", ("Out",), username="raykovmse", token="token-123"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ensure_opencloud_folder_raises_on_unexpected_status() -> None:
|
||||||
|
fake_response = Mock(status_code=500, text="boom")
|
||||||
|
with patch("app.opencloud_access.requests.request", return_value=fake_response):
|
||||||
|
with pytest.raises(OpenCloudUploadError):
|
||||||
|
ensure_opencloud_folder(
|
||||||
|
"abc", ("Out",), username="raykovmse", token="token-123"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_upload_local_file_to_opencloud_http_error(tmp_path: Path) -> None:
|
def test_upload_local_file_to_opencloud_http_error(tmp_path: Path) -> None:
|
||||||
local_file = tmp_path / "report.xlsx"
|
local_file = tmp_path / "report.xlsx"
|
||||||
local_file.write_bytes(b"data")
|
local_file.write_bytes(b"data")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user