Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion app/publishers/FTP/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,23 @@
FTP_PASSWORD = config.FTP_PASSWORD
FTP_POSTSHOW_DIR = config.FTP_POSTSHOW_DIR

# type_episode в state/UI — "aftershow", в именах файлов и enum — "postshow"
# (см. bot/utils/podcast_methods.py). Принимаем оба, чтобы маршрутизация не
# зависела от того, какой алиас доедет до publisher'а.
_POSTSHOW_ALIASES = frozenset({"aftershow", "postshow"})


def _remote_path(file_name: str, type_episode: str | None) -> str:
"""Куда класть файл на SFTP относительно домашнего каталога.

Postshow-эпизоды уходят в отдельную подпапку FTP_POSTSHOW_DIR; основные —
в корень (home). Раньше всё валилось в корень, потому что FTP_POSTSHOW_DIR
читался, но не применялся — отсюда «aftershow лёг не в ту папку».
"""
if type_episode in _POSTSHOW_ALIASES:
return f"{FTP_POSTSHOW_DIR}/{file_name}"
return file_name


async def upload_to_ftp(
path: str,
Expand Down Expand Up @@ -53,7 +70,12 @@ async def upload_to_ftp(
conn.start_sftp_client() as sftp,
aiofiles.open(path, "rb") as f,
):
async with sftp.open(file_name, "wb") as remote_file:
remote_path = _remote_path(file_name, type_episode)
if remote_path != file_name:
# Подпапка может ещё не существовать на сервере — создаём идемпотентно.
await sftp.makedirs(FTP_POSTSHOW_DIR, exist_ok=True)

async with sftp.open(remote_path, "wb") as remote_file:
while True:
data = await f.read(chunk_size)
if not data:
Expand Down
21 changes: 21 additions & 0 deletions tests/unit/publishers/ftp/test_ftp_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,27 @@ async def test_invalid_payload_skipped(self, mock_producer):
mock_producer.send.assert_not_called()


class TestRemotePath:
"""Маршрутизация файла по подпапкам в зависимости от type_episode."""

def test_main_episode_goes_to_root(self):
from app.publishers.FTP.main import _remote_path

assert _remote_path("0042_rz_13062026.mp3", "main") == "0042_rz_13062026.mp3"

def test_none_type_goes_to_root(self):
from app.publishers.FTP.main import _remote_path

assert _remote_path("0042_rz_13062026.mp3", None) == "0042_rz_13062026.mp3"

@pytest.mark.parametrize("type_episode", ["aftershow", "postshow"])
def test_postshow_goes_to_subdir(self, type_episode):
from app.publishers.FTP.main import FTP_POSTSHOW_DIR, _remote_path

result = _remote_path("0042_postshow_13062026.mp3", type_episode)
assert result == f"{FTP_POSTSHOW_DIR}/0042_postshow_13062026.mp3"


class TestUploadEventModel:
def test_valid_event(self, sample_upload_event_dict):
event = UploadEvent(**sample_upload_event_dict)
Expand Down
Loading