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
151 lines
4.7 KiB
Python
151 lines
4.7 KiB
Python
"""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 == []
|