Add comprehensive test suite for backend (133 tests)
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.
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
"""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"
|
||||
Reference in New Issue
Block a user