47baee854b
Test coverage: - test_utils.py: formatting helpers (human_size, ticks_to_minutes, format_duration, format_bitrate, ffprobe summaries, stream parsing) - test_path_utils.py: path resolution (prefix, media root mapping, edge cases with spaces/special chars) - test_domain_media.py: Jellyfin item normalization (HDR detection, media sources, stream extraction, display formatting) - test_jobs.py: job template rendering and shell quoting safety - test_media_index.py: SQLite index CRUD, querying, filtering, sorting, pagination - test_config.py: pydantic-settings env loading - test_api.py: full FastAPI integration tests with mocked SSH/Jellyfin (all endpoints: dashboard, monitoring, media, files, jobs) All tests run without network/SSH dependencies using mocks.
56 lines
2.0 KiB
Python
56 lines
2.0 KiB
Python
"""Unit tests for jobs.py template rendering and safety."""
|
|
|
|
import pytest
|
|
from media_library_viewer_api.jobs import JOB_TEMPLATES, JobTemplate, run_job
|
|
|
|
|
|
class TestJobTemplate:
|
|
def test_render_basic(self):
|
|
template = JobTemplate(
|
|
name="Test",
|
|
description="A test job",
|
|
command_template="echo {path}",
|
|
)
|
|
result = template.render({"path": "/media/file.mkv"})
|
|
assert result == "echo /media/file.mkv"
|
|
|
|
def test_render_quotes_spaces(self):
|
|
template = JobTemplate(
|
|
name="Test",
|
|
description="A test job",
|
|
command_template="du -sh {path}",
|
|
)
|
|
result = template.render({"path": "/media/My Movie (2024)/file.mkv"})
|
|
# shlex.quote wraps in single quotes
|
|
assert "'" in result or "\\" in result
|
|
assert "My Movie (2024)" in result
|
|
|
|
def test_render_quotes_special_chars(self):
|
|
template = JobTemplate(
|
|
name="Test",
|
|
description="A test job",
|
|
command_template="stat {path}",
|
|
)
|
|
result = template.render({"path": "/media/file;rm -rf /"})
|
|
# Injection attempt should be safely quoted
|
|
assert "rm -rf" in result # it's there but quoted
|
|
assert result.startswith("stat ")
|
|
# Should not be executable as separate command
|
|
assert ";" not in result or "'" in result
|
|
|
|
|
|
class TestBuiltinTemplates:
|
|
def test_all_templates_exist(self):
|
|
assert "disk_usage" in JOB_TEMPLATES
|
|
assert "ffprobe" in JOB_TEMPLATES
|
|
assert "dry_run_find_empty_dirs" in JOB_TEMPLATES
|
|
|
|
def test_all_templates_renderable(self):
|
|
for key, template in JOB_TEMPLATES.items():
|
|
result = template.render({"path": "/test/path"})
|
|
assert "/test/path" in result or "'/test/path'" in result
|
|
|
|
def test_no_destructive_in_phase1(self):
|
|
for key, template in JOB_TEMPLATES.items():
|
|
assert template.destructive is False, f"Template {key} is marked destructive"
|