mirror of
https://github.com/lfnovo/open-notebook.git
synced 2026-04-28 11:30:00 +00:00
Merge pull request #666 from lfnovo/fix/podcast-uuid-directory
Some checks failed
Tests / Backend Tests (push) Has been cancelled
Tests / Frontend Tests (push) Has been cancelled
Development Build / extract-version (push) Has been cancelled
Development Build / build-regular (push) Has been cancelled
Development Build / build-single (push) Has been cancelled
Development Build / summary (push) Has been cancelled
Some checks failed
Tests / Backend Tests (push) Has been cancelled
Tests / Frontend Tests (push) Has been cancelled
Development Build / extract-version (push) Has been cancelled
Development Build / build-regular (push) Has been cancelled
Development Build / build-single (push) Has been cancelled
Development Build / summary (push) Has been cancelled
fix: use UUID for podcast episode directory names
This commit is contained in:
commit
a42e2a347e
2 changed files with 86 additions and 3 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import time
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
|
@ -22,6 +23,20 @@ except ImportError as e:
|
|||
raise ValueError("podcast_creator library not available")
|
||||
|
||||
|
||||
def build_episode_output_dir(data_folder: str) -> tuple[str, Path]:
|
||||
"""Build a filesystem-safe output directory path for a podcast episode.
|
||||
|
||||
Uses a UUID as the directory name so the path is safe regardless of
|
||||
what the user typed as episode name (spaces, special chars, etc.).
|
||||
|
||||
Returns:
|
||||
A tuple of (episode_dir_name, output_dir_path).
|
||||
"""
|
||||
episode_dir_name = str(uuid.uuid4())
|
||||
output_dir = Path(f"{data_folder}/podcasts/episodes/{episode_dir_name}")
|
||||
return episode_dir_name, output_dir
|
||||
|
||||
|
||||
def full_model_dump(model):
|
||||
if isinstance(model, BaseModel):
|
||||
return model.model_dump()
|
||||
|
|
@ -220,8 +235,8 @@ async def generate_podcast_command(
|
|||
|
||||
logger.info(f"Generated briefing (length: {len(briefing)} chars)")
|
||||
|
||||
# 7. Create output directory
|
||||
output_dir = Path(f"{DATA_FOLDER}/podcasts/episodes/{input_data.episode_name}")
|
||||
# 7. Create output directory using UUID for filesystem-safe paths
|
||||
episode_dir_name, output_dir = build_episode_output_dir(DATA_FOLDER)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
logger.info(f"Created output directory: {output_dir}")
|
||||
|
|
@ -232,7 +247,7 @@ async def generate_podcast_command(
|
|||
result = await create_podcast(
|
||||
content=input_data.content,
|
||||
briefing=briefing,
|
||||
episode_name=input_data.episode_name,
|
||||
episode_name=episode_dir_name,
|
||||
output_dir=str(output_dir),
|
||||
speaker_config=speaker_profile.name,
|
||||
episode_profile=episode_profile.name,
|
||||
|
|
|
|||
68
tests/test_podcast_path.py
Normal file
68
tests/test_podcast_path.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
"""
|
||||
Tests for podcast episode directory path generation.
|
||||
|
||||
Verifies that episode output directories use UUID-based names
|
||||
instead of raw episode names, preventing filesystem issues with
|
||||
spaces and special characters (GitHub issue #663).
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from pathlib import PurePosixPath
|
||||
|
||||
from commands.podcast_commands import build_episode_output_dir
|
||||
|
||||
|
||||
class TestBuildEpisodeOutputDir:
|
||||
"""Test the actual production helper that builds episode output paths."""
|
||||
|
||||
def test_directory_name_is_valid_uuid(self):
|
||||
dir_name, _ = build_episode_output_dir("/data")
|
||||
parsed = uuid.UUID(dir_name)
|
||||
assert str(parsed) == dir_name
|
||||
|
||||
def test_path_structure(self):
|
||||
dir_name, output_dir = build_episode_output_dir("/data")
|
||||
assert str(output_dir) == f"/data/podcasts/episodes/{dir_name}"
|
||||
|
||||
def test_no_collision_between_calls(self):
|
||||
dir1, _ = build_episode_output_dir("/data")
|
||||
dir2, _ = build_episode_output_dir("/data")
|
||||
assert dir1 != dir2
|
||||
|
||||
def test_path_is_independent_of_episode_name(self):
|
||||
"""The returned path must never contain user-supplied episode names.
|
||||
|
||||
Since build_episode_output_dir does not accept an episode name at all,
|
||||
any name the user types is structurally excluded from the path.
|
||||
"""
|
||||
problematic_names = [
|
||||
"My Episode Name",
|
||||
"Episode: Part 1",
|
||||
'test "quotes"',
|
||||
"path/traversal",
|
||||
"café résumé",
|
||||
" spaces ",
|
||||
"?*<>|",
|
||||
]
|
||||
for name in problematic_names:
|
||||
_, output_dir = build_episode_output_dir("/data")
|
||||
path_str = str(output_dir)
|
||||
# The episode name must not appear anywhere in the path
|
||||
assert name not in path_str
|
||||
# UUID paths contain only hex digits and hyphens after the base
|
||||
dir_component = output_dir.name
|
||||
assert all(c in "0123456789abcdef-" for c in dir_component), (
|
||||
f"Unexpected chars in directory name: {dir_component}"
|
||||
)
|
||||
|
||||
def test_path_works_on_posix(self):
|
||||
dir_name, output_dir = build_episode_output_dir("/data")
|
||||
posix = PurePosixPath(str(output_dir))
|
||||
assert posix.parts == ("/", "data", "podcasts", "episodes", dir_name)
|
||||
|
||||
def test_directory_can_be_created(self, tmp_path):
|
||||
"""Create the directory on the real filesystem."""
|
||||
_, output_dir = build_episode_output_dir(str(tmp_path))
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
assert output_dir.exists()
|
||||
assert output_dir.is_dir()
|
||||
Loading…
Add table
Add a link
Reference in a new issue