Dev #2

Merged
unai merged 15 commits from Dev into main 2026-02-01 19:39:49 +00:00
2 changed files with 84 additions and 78 deletions
Showing only changes of commit cbf22422e3 - Show all commits

View File

@ -10,6 +10,9 @@ from m3u_list_builder.config import settings
logger = logging.getLogger(__name__)
# Directorio dedicado para servir archivos
PUBLIC_DIR = Path("public")
class PlaylistManager:
"""Clase para gestionar la generación y actualización de listas M3U."""
@ -17,6 +20,8 @@ class PlaylistManager:
def __init__(self):
"""Inicialize the PlaylistManager."""
self.running = False
# Asegurar que el directorio público existe
PUBLIC_DIR.mkdir(exist_ok=True)
def fetch_and_generate(self):
"""Descarga datos y regenera el archivo M3U."""
@ -46,8 +51,8 @@ class PlaylistManager:
def _write_m3u(self, channels: list):
"""Escribe el archivo M3U en disco de forma atómica."""
temp_file = Path(f"{settings.output_file}.tmp")
final_file = Path(settings.output_file)
temp_file = PUBLIC_DIR / f"{settings.output_file}.tmp"
final_file = PUBLIC_DIR / settings.output_file
with open(temp_file, "w", encoding="utf-8") as f:
f.write("#EXTM3U\n")

View File

@ -219,51 +219,49 @@ class TestPlaylistManager:
def test_write_m3u_empty_channels(self, temp_dir):
"""Test: _write_m3u genera archivo con solo header para lista vacía."""
output_file = temp_dir / "playlist.m3u"
with patch("m3u_list_builder.playlist.PUBLIC_DIR", temp_dir):
with patch("m3u_list_builder.playlist.settings") as mock_settings:
mock_settings.host = "http://test-iptv.com"
mock_settings.username = "testuser"
mock_settings.password = "testpass"
mock_settings.output_file = "playlist.m3u"
with patch("m3u_list_builder.playlist.settings") as mock_settings:
mock_settings.host = "http://test-iptv.com"
mock_settings.username = "testuser"
mock_settings.password = "testpass"
mock_settings.output_file = str(output_file)
from m3u_list_builder.playlist import PlaylistManager
from m3u_list_builder.playlist import PlaylistManager
manager = PlaylistManager()
manager._write_m3u([])
manager = PlaylistManager()
manager._write_m3u([])
content = output_file.read_text()
assert content == "#EXTM3U\n"
output_file = temp_dir / "playlist.m3u"
content = output_file.read_text()
assert content == "#EXTM3U\n"
def test_write_m3u_multiple_channels(self, temp_dir, sample_channels):
"""Test: _write_m3u genera archivo correcto con múltiples canales."""
output_file = temp_dir / "playlist.m3u"
with patch("m3u_list_builder.playlist.PUBLIC_DIR", temp_dir):
with patch("m3u_list_builder.playlist.settings") as mock_settings:
mock_settings.host = "http://test-iptv.com"
mock_settings.username = "testuser"
mock_settings.password = "testpass"
mock_settings.output_file = "playlist.m3u"
with patch("m3u_list_builder.playlist.settings") as mock_settings:
mock_settings.host = "http://test-iptv.com"
mock_settings.username = "testuser"
mock_settings.password = "testpass"
mock_settings.output_file = str(output_file)
from m3u_list_builder.playlist import PlaylistManager
from m3u_list_builder.playlist import PlaylistManager
manager = PlaylistManager()
manager._write_m3u(sample_channels)
manager = PlaylistManager()
manager._write_m3u(sample_channels)
output_file = temp_dir / "playlist.m3u"
content = output_file.read_text()
content = output_file.read_text()
# Verificar header
assert content.startswith("#EXTM3U\n")
# Verificar header
assert content.startswith("#EXTM3U\n")
# Verificar cada canal
for channel in sample_channels:
assert channel["name"] in content
assert str(channel["stream_id"]) in content
# Verificar cada canal
for channel in sample_channels:
assert channel["name"] in content
assert str(channel["stream_id"]) in content
def test_write_m3u_channel_format(self, temp_dir):
"""Test: _write_m3u genera formato EXTINF correcto."""
output_file = temp_dir / "playlist.m3u"
channel = [
{
"name": "Test Channel",
@ -273,52 +271,54 @@ class TestPlaylistManager:
}
]
with patch("m3u_list_builder.playlist.settings") as mock_settings:
mock_settings.host = "http://iptv.com"
mock_settings.username = "user"
mock_settings.password = "pass"
mock_settings.output_file = str(output_file)
with patch("m3u_list_builder.playlist.PUBLIC_DIR", temp_dir):
with patch("m3u_list_builder.playlist.settings") as mock_settings:
mock_settings.host = "http://iptv.com"
mock_settings.username = "user"
mock_settings.password = "pass"
mock_settings.output_file = "playlist.m3u"
from m3u_list_builder.playlist import PlaylistManager
from m3u_list_builder.playlist import PlaylistManager
manager = PlaylistManager()
manager._write_m3u(channel)
manager = PlaylistManager()
manager._write_m3u(channel)
content = output_file.read_text()
lines = content.strip().split("\n")
output_file = temp_dir / "playlist.m3u"
content = output_file.read_text()
lines = content.strip().split("\n")
assert len(lines) == 3 # Header + EXTINF + URL
assert len(lines) == 3 # Header + EXTINF + URL
# Verificar EXTINF
assert '#EXTINF:-1 tvg-id="Test Channel"' in lines[1]
assert 'tvg-logo="http://icon.com/test.png"' in lines[1]
assert 'group-title="Cat_5"' in lines[1]
assert lines[1].endswith(",Test Channel")
# Verificar EXTINF
assert '#EXTINF:-1 tvg-id="Test Channel"' in lines[1]
assert 'tvg-logo="http://icon.com/test.png"' in lines[1]
assert 'group-title="Cat_5"' in lines[1]
assert lines[1].endswith(",Test Channel")
# Verificar URL
assert lines[2] == "http://iptv.com/live/user/pass/123.ts"
# Verificar URL
assert lines[2] == "http://iptv.com/live/user/pass/123.ts"
def test_write_m3u_missing_fields_uses_defaults(self, temp_dir, minimal_channel):
"""Test: _write_m3u usa valores por defecto para campos faltantes."""
output_file = temp_dir / "playlist.m3u"
with patch("m3u_list_builder.playlist.PUBLIC_DIR", temp_dir):
with patch("m3u_list_builder.playlist.settings") as mock_settings:
mock_settings.host = "http://test-iptv.com"
mock_settings.username = "testuser"
mock_settings.password = "testpass"
mock_settings.output_file = "playlist.m3u"
with patch("m3u_list_builder.playlist.settings") as mock_settings:
mock_settings.host = "http://test-iptv.com"
mock_settings.username = "testuser"
mock_settings.password = "testpass"
mock_settings.output_file = str(output_file)
from m3u_list_builder.playlist import PlaylistManager
from m3u_list_builder.playlist import PlaylistManager
manager = PlaylistManager()
manager._write_m3u(minimal_channel)
manager = PlaylistManager()
manager._write_m3u(minimal_channel)
output_file = temp_dir / "playlist.m3u"
content = output_file.read_text()
content = output_file.read_text()
# Debe usar "Unknown" como nombre por defecto
assert "Unknown" in content
# Debe tener icono vacío
assert 'tvg-logo=""' in content
# Debe usar "Unknown" como nombre por defecto
assert "Unknown" in content
# Debe tener icono vacío
assert 'tvg-logo=""' in content
def test_write_m3u_atomic_replacement(self, temp_dir):
"""Test: _write_m3u reemplaza atómicamente (usa archivo temporal)."""
@ -328,22 +328,23 @@ class TestPlaylistManager:
# Crear archivo existente
output_file.write_text("OLD CONTENT")
with patch("m3u_list_builder.playlist.settings") as mock_settings:
mock_settings.host = "http://test-iptv.com"
mock_settings.username = "testuser"
mock_settings.password = "testpass"
mock_settings.output_file = str(output_file)
with patch("m3u_list_builder.playlist.PUBLIC_DIR", temp_dir):
with patch("m3u_list_builder.playlist.settings") as mock_settings:
mock_settings.host = "http://test-iptv.com"
mock_settings.username = "testuser"
mock_settings.password = "testpass"
mock_settings.output_file = "playlist.m3u"
from m3u_list_builder.playlist import PlaylistManager
from m3u_list_builder.playlist import PlaylistManager
manager = PlaylistManager()
manager._write_m3u([])
manager = PlaylistManager()
manager._write_m3u([])
# El archivo temporal no debe existir después
assert not temp_file.exists()
# El archivo temporal no debe existir después
assert not temp_file.exists()
# El archivo final debe tener el nuevo contenido
assert output_file.read_text() == "#EXTM3U\n"
# El archivo final debe tener el nuevo contenido
assert output_file.read_text() == "#EXTM3U\n"
# ========================================
# Tests para loop - Complejidad Media