Initial commit: TVH2Jellyfin application
A Docker-based Python application that transfers completed recordings from TVHeadend to Jellyfin media storage via SCP. Features: - Monitors TVHeadend for completed recordings via JSON API - Transfers files via SCP with SSH key authentication - Organizes files by show name - Deletes recordings from TVHeadend after successful transfer - Tracks transferred files to avoid duplicates - Dry run mode for testing without making changes
This commit is contained in:
150
tests/test_jellyfin.py
Normal file
150
tests/test_jellyfin.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""Tests for Jellyfin client."""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from tvh2jellyfin.jellyfin import JellyfinClient, JellyfinConfig
|
||||
|
||||
|
||||
class TestJellyfinConfig:
|
||||
"""Tests for JellyfinConfig dataclass."""
|
||||
|
||||
def test_config_defaults(self):
|
||||
"""Test default values."""
|
||||
config = JellyfinConfig(
|
||||
url="http://localhost:8096",
|
||||
api_key="test_key",
|
||||
)
|
||||
assert config.library_id is None
|
||||
assert config.enabled is True
|
||||
|
||||
def test_config_with_library_id(self):
|
||||
"""Test config with specific library ID."""
|
||||
config = JellyfinConfig(
|
||||
url="http://localhost:8096",
|
||||
api_key="test_key",
|
||||
library_id="abc123",
|
||||
)
|
||||
assert config.library_id == "abc123"
|
||||
|
||||
|
||||
class TestJellyfinClient:
|
||||
"""Tests for Jellyfin API client."""
|
||||
|
||||
@pytest.fixture
|
||||
def config(self):
|
||||
return JellyfinConfig(
|
||||
url="http://localhost:8096",
|
||||
api_key="test_api_key",
|
||||
)
|
||||
|
||||
@pytest.fixture
|
||||
def config_with_library(self):
|
||||
return JellyfinConfig(
|
||||
url="http://localhost:8096",
|
||||
api_key="test_api_key",
|
||||
library_id="lib123",
|
||||
)
|
||||
|
||||
@pytest.fixture
|
||||
def client(self, config):
|
||||
return JellyfinClient(config)
|
||||
|
||||
@pytest.fixture
|
||||
def client_with_library(self, config_with_library):
|
||||
return JellyfinClient(config_with_library)
|
||||
|
||||
def test_url_normalization(self):
|
||||
"""Test that trailing slashes are removed from URL."""
|
||||
config = JellyfinConfig(
|
||||
url="http://localhost:8096/",
|
||||
api_key="test_key",
|
||||
)
|
||||
client = JellyfinClient(config)
|
||||
assert client.base_url == "http://localhost:8096"
|
||||
|
||||
def test_headers_set(self, client):
|
||||
"""Test that auth headers are set correctly."""
|
||||
assert client.session.headers["X-MediaBrowser-Token"] == "test_api_key"
|
||||
assert client.session.headers["Content-Type"] == "application/json"
|
||||
|
||||
@patch("requests.Session.get")
|
||||
def test_test_connection_success(self, mock_get, client):
|
||||
"""Test successful connection test."""
|
||||
mock_get.return_value.json.return_value = {
|
||||
"ServerName": "Test Server",
|
||||
"Version": "10.8.0",
|
||||
}
|
||||
mock_get.return_value.raise_for_status = Mock()
|
||||
|
||||
result = client.test_connection()
|
||||
|
||||
assert result is True
|
||||
mock_get.assert_called_once()
|
||||
assert "/System/Info" in mock_get.call_args[0][0]
|
||||
|
||||
@patch("requests.Session.get")
|
||||
def test_test_connection_failure(self, mock_get, client):
|
||||
"""Test connection test failure."""
|
||||
mock_get.side_effect = requests.exceptions.ConnectionError("Connection refused")
|
||||
|
||||
result = client.test_connection()
|
||||
|
||||
assert result is False
|
||||
|
||||
@patch("requests.Session.post")
|
||||
def test_trigger_library_scan_full(self, mock_post, client):
|
||||
"""Test triggering full library scan."""
|
||||
mock_post.return_value.raise_for_status = Mock()
|
||||
|
||||
result = client.trigger_library_scan()
|
||||
|
||||
assert result is True
|
||||
mock_post.assert_called_once()
|
||||
assert "/Library/Refresh" in mock_post.call_args[0][0]
|
||||
|
||||
@patch("requests.Session.post")
|
||||
def test_trigger_library_scan_specific_library(self, mock_post, client_with_library):
|
||||
"""Test triggering scan for specific library."""
|
||||
mock_post.return_value.raise_for_status = Mock()
|
||||
|
||||
result = client_with_library.trigger_library_scan()
|
||||
|
||||
assert result is True
|
||||
mock_post.assert_called_once()
|
||||
assert "/Items/lib123/Refresh" in mock_post.call_args[0][0]
|
||||
|
||||
@patch("requests.Session.post")
|
||||
def test_trigger_library_scan_failure(self, mock_post, client):
|
||||
"""Test library scan trigger failure."""
|
||||
mock_post.side_effect = requests.exceptions.RequestException("API error")
|
||||
|
||||
result = client.trigger_library_scan()
|
||||
|
||||
assert result is False
|
||||
|
||||
@patch("requests.Session.get")
|
||||
def test_get_libraries_success(self, mock_get, client):
|
||||
"""Test getting list of libraries."""
|
||||
mock_get.return_value.json.return_value = [
|
||||
{"Name": "TV Shows", "ItemId": "tv123"},
|
||||
{"Name": "Movies", "ItemId": "mov456"},
|
||||
]
|
||||
mock_get.return_value.raise_for_status = Mock()
|
||||
|
||||
result = client.get_libraries()
|
||||
|
||||
assert len(result) == 2
|
||||
assert result[0] == {"name": "TV Shows", "id": "tv123"}
|
||||
assert result[1] == {"name": "Movies", "id": "mov456"}
|
||||
|
||||
@patch("requests.Session.get")
|
||||
def test_get_libraries_failure(self, mock_get, client):
|
||||
"""Test getting libraries when API fails."""
|
||||
mock_get.side_effect = requests.exceptions.RequestException("API error")
|
||||
|
||||
result = client.get_libraries()
|
||||
|
||||
assert result == []
|
||||
Reference in New Issue
Block a user