Files
sdb2-backend/tests/api/v1/test_vlc_endpoints.py
JSC dd10ef5d41 feat: Add VLC player API endpoints and associated tests
- Implemented VLC player API endpoints for playing and stopping sounds.
- Added tests for successful playback, error handling, and authentication scenarios.
- Created utility function to get sound file paths based on sound properties.
- Refactored player service to utilize shared sound path utility.
- Enhanced test coverage for sound file path utility with various sound types.
- Introduced tests for VLC player service, including subprocess handling and play count tracking.
2025-07-30 20:46:49 +02:00

305 lines
12 KiB
Python

"""Tests for VLC player API endpoints."""
from unittest.mock import AsyncMock, patch
import pytest
from httpx import AsyncClient
from app.models.sound import Sound
from app.models.user import User
class TestVLCEndpoints:
"""Test VLC player API endpoints."""
@pytest.mark.asyncio
async def test_play_sound_with_vlc_success(
self,
authenticated_client: AsyncClient,
authenticated_user: User,
):
"""Test successful sound playback via VLC."""
# Mock the VLC player service and sound repository methods
with patch("app.services.vlc_player.VLCPlayerService.play_sound") as mock_play_sound:
mock_play_sound.return_value = True
with patch("app.repositories.sound.SoundRepository.get_by_id") as mock_get_by_id:
mock_sound = Sound(
id=1,
type="SDB",
name="Test Sound",
filename="test.mp3",
duration=5000,
size=1024,
hash="test_hash",
)
mock_get_by_id.return_value = mock_sound
response = await authenticated_client.post("/api/v1/sounds/vlc/play/1")
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["sound_id"] == 1
assert data["sound_name"] == "Test Sound"
assert "Test Sound" in data["message"]
# Verify service calls
mock_get_by_id.assert_called_once_with(1)
mock_play_sound.assert_called_once_with(mock_sound)
@pytest.mark.asyncio
async def test_play_sound_with_vlc_sound_not_found(
self,
authenticated_client: AsyncClient,
authenticated_user: User,
):
"""Test VLC playback when sound is not found."""
# Mock the sound repository to return None
with patch("app.repositories.sound.SoundRepository.get_by_id") as mock_get_by_id:
mock_get_by_id.return_value = None
response = await authenticated_client.post("/api/v1/sounds/vlc/play/999")
assert response.status_code == 404
data = response.json()
assert "Sound with ID 999 not found" in data["detail"]
@pytest.mark.asyncio
async def test_play_sound_with_vlc_launch_failure(
self,
authenticated_client: AsyncClient,
authenticated_user: User,
):
"""Test VLC playback when VLC launch fails."""
# Mock the VLC player service to fail
with patch("app.services.vlc_player.VLCPlayerService.play_sound") as mock_play_sound:
mock_play_sound.return_value = False
with patch("app.repositories.sound.SoundRepository.get_by_id") as mock_get_by_id:
mock_sound = Sound(
id=1,
type="SDB",
name="Test Sound",
filename="test.mp3",
duration=5000,
size=1024,
hash="test_hash",
)
mock_get_by_id.return_value = mock_sound
response = await authenticated_client.post("/api/v1/sounds/vlc/play/1")
assert response.status_code == 500
data = response.json()
assert "Failed to launch VLC for sound playback" in data["detail"]
@pytest.mark.asyncio
async def test_play_sound_with_vlc_service_exception(
self,
authenticated_client: AsyncClient,
authenticated_user: User,
):
"""Test VLC playback when service raises an exception."""
# Mock the sound repository to raise an exception
with patch("app.repositories.sound.SoundRepository.get_by_id") as mock_get_by_id:
mock_get_by_id.side_effect = Exception("Database error")
response = await authenticated_client.post("/api/v1/sounds/vlc/play/1")
assert response.status_code == 500
data = response.json()
assert "Failed to play sound" in data["detail"]
@pytest.mark.asyncio
async def test_play_sound_with_vlc_unauthenticated(
self,
client: AsyncClient,
):
"""Test VLC playback without authentication."""
response = await client.post("/api/v1/sounds/vlc/play/1")
assert response.status_code == 401
@pytest.mark.asyncio
async def test_stop_all_vlc_instances_success(
self,
authenticated_client: AsyncClient,
authenticated_user: User,
):
"""Test successful stopping of all VLC instances."""
# Mock the VLC player service
with patch("app.services.vlc_player.VLCPlayerService.stop_all_vlc_instances") as mock_stop_all:
mock_result = {
"success": True,
"processes_found": 3,
"processes_killed": 3,
"processes_remaining": 0,
"message": "Killed 3 VLC processes",
}
mock_stop_all.return_value = mock_result
response = await authenticated_client.post("/api/v1/sounds/vlc/stop-all")
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["processes_found"] == 3
assert data["processes_killed"] == 3
assert data["processes_remaining"] == 0
assert "Killed 3 VLC processes" in data["message"]
# Verify service call
mock_stop_all.assert_called_once()
@pytest.mark.asyncio
async def test_stop_all_vlc_instances_no_processes(
self,
authenticated_client: AsyncClient,
authenticated_user: User,
):
"""Test stopping VLC instances when none are running."""
# Mock the VLC player service
with patch("app.services.vlc_player.VLCPlayerService.stop_all_vlc_instances") as mock_stop_all:
mock_result = {
"success": True,
"processes_found": 0,
"processes_killed": 0,
"message": "No VLC processes found",
}
mock_stop_all.return_value = mock_result
response = await authenticated_client.post("/api/v1/sounds/vlc/stop-all")
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["processes_found"] == 0
assert data["processes_killed"] == 0
assert data["message"] == "No VLC processes found"
@pytest.mark.asyncio
async def test_stop_all_vlc_instances_partial_success(
self,
authenticated_client: AsyncClient,
authenticated_user: User,
):
"""Test stopping VLC instances with partial success."""
# Mock the VLC player service
with patch("app.services.vlc_player.VLCPlayerService.stop_all_vlc_instances") as mock_stop_all:
mock_result = {
"success": True,
"processes_found": 3,
"processes_killed": 2,
"processes_remaining": 1,
"message": "Killed 2 VLC processes",
}
mock_stop_all.return_value = mock_result
response = await authenticated_client.post("/api/v1/sounds/vlc/stop-all")
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["processes_found"] == 3
assert data["processes_killed"] == 2
assert data["processes_remaining"] == 1
@pytest.mark.asyncio
async def test_stop_all_vlc_instances_failure(
self,
authenticated_client: AsyncClient,
authenticated_user: User,
):
"""Test stopping VLC instances when service fails."""
# Mock the VLC player service
with patch("app.services.vlc_player.VLCPlayerService.stop_all_vlc_instances") as mock_stop_all:
mock_result = {
"success": False,
"processes_found": 0,
"processes_killed": 0,
"error": "Command failed",
"message": "Failed to stop VLC processes",
}
mock_stop_all.return_value = mock_result
response = await authenticated_client.post("/api/v1/sounds/vlc/stop-all")
assert response.status_code == 200
data = response.json()
assert data["success"] is False
assert data["error"] == "Command failed"
assert data["message"] == "Failed to stop VLC processes"
@pytest.mark.asyncio
async def test_stop_all_vlc_instances_service_exception(
self,
authenticated_client: AsyncClient,
authenticated_user: User,
):
"""Test stopping VLC instances when service raises an exception."""
# Mock the VLC player service to raise an exception
with patch("app.services.vlc_player.VLCPlayerService.stop_all_vlc_instances") as mock_stop_all:
mock_stop_all.side_effect = Exception("Service error")
response = await authenticated_client.post("/api/v1/sounds/vlc/stop-all")
assert response.status_code == 500
data = response.json()
assert "Failed to stop VLC instances" in data["detail"]
@pytest.mark.asyncio
async def test_stop_all_vlc_instances_unauthenticated(
self,
client: AsyncClient,
):
"""Test stopping VLC instances without authentication."""
response = await client.post("/api/v1/sounds/vlc/stop-all")
assert response.status_code == 401
@pytest.mark.asyncio
async def test_vlc_endpoints_with_admin_user(
self,
authenticated_admin_client: AsyncClient,
admin_user: User,
):
"""Test VLC endpoints work with admin user."""
# Test play endpoint with admin
with patch("app.services.vlc_player.VLCPlayerService.play_sound") as mock_play_sound:
mock_play_sound.return_value = True
with patch("app.repositories.sound.SoundRepository.get_by_id") as mock_get_by_id:
mock_sound = Sound(
id=1,
type="SDB",
name="Admin Test Sound",
filename="admin_test.mp3",
duration=3000,
size=512,
hash="admin_hash",
)
mock_get_by_id.return_value = mock_sound
response = await authenticated_admin_client.post("/api/v1/sounds/vlc/play/1")
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["sound_name"] == "Admin Test Sound"
# Test stop-all endpoint with admin
with patch("app.services.vlc_player.VLCPlayerService.stop_all_vlc_instances") as mock_stop_all:
mock_result = {
"success": True,
"processes_found": 1,
"processes_killed": 1,
"processes_remaining": 0,
"message": "Killed 1 VLC processes",
}
mock_stop_all.return_value = mock_result
response = await authenticated_admin_client.post("/api/v1/sounds/vlc/stop-all")
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["processes_killed"] == 1