Ctrl K
ComunidadGuías…

backend/tests/test_bazarr_migrator.py

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Unit tests for bazarr_migrator.py — config parsing, DB reading, transformation."""

import json
import os
import sqlite3
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, call, patch

# ---------------------------------------------------------------------------
# parse_bazarr_config — config parsing
# ---------------------------------------------------------------------------


def test_parse_empty_config():
    from bazarr_migrator import parse_bazarr_config

    result = parse_bazarr_config("", "config.yaml")
    assert result["sonarr"] == {}
    assert result["radarr"] == {}
    assert result["general"] == {}
    assert len(result["warnings"]) > 0


def test_parse_yaml_config():
    from bazarr_migrator import parse_bazarr_config

    yaml_content = """
sonarr:
  ip: "192.168.1.10"
  port: "8989"
  apikey: "sonarr-key-abc"
radarr:
  ip: "192.168.1.10"
  port: "7878"
  apikey: "radarr-key-xyz"
general:
  serie_default_language: "en"
"""
    result = parse_bazarr_config(yaml_content, "config.yaml")
    assert result["sonarr"]["url"] == "192.168.1.10"
    assert result["sonarr"]["api_key"] == "sonarr-key-abc"
    assert result["radarr"]["api_key"] == "radarr-key-xyz"
    assert result["general"]["source_language"] == "en"


def test_parse_ini_config():
    from bazarr_migrator import parse_bazarr_config

    ini_content = """
[sonarr]
ip = 10.0.0.1
port = 8989
apikey = sonarr-ini-key

[radarr]
ip = 10.0.0.1
port = 7878
apikey = radarr-ini-key

[general]
serie_default_language = en
"""
    result = parse_bazarr_config(ini_content, "config.ini")
    assert result["sonarr"]["url"] == "10.0.0.1"
    assert result["sonarr"]["api_key"] == "sonarr-ini-key"
    assert result["radarr"]["api_key"] == "radarr-ini-key"


def test_parse_config_missing_sonarr_section():
    from bazarr_migrator import parse_bazarr_config

    yaml_content = """
general:
  serie_default_language: "en"
"""
    result = parse_bazarr_config(yaml_content, "config.yaml")
    # When sonarr section is absent the dict has empty-string values
    assert result["sonarr"].get("api_key", "") == ""
    assert result["sonarr"].get("url", "") == ""


def test_parse_config_missing_radarr_section():
    from bazarr_migrator import parse_bazarr_config

    yaml_content = """
sonarr:
  ip: "10.0.0.1"
  apikey: "key"
"""
    result = parse_bazarr_config(yaml_content, "config.yaml")
    # radarr section absent → radarr dict should be empty or have empty values
    assert result["radarr"].get("api_key", "") == ""


def test_parse_config_opensubtitles_credentials():
    from bazarr_migrator import parse_bazarr_config

    yaml_content = """
opensubtitles:
  username: "myuser"
  password: "mypass"
  apikey: "oskey"
general:
  serie_default_language: "en"
"""
    result = parse_bazarr_config(yaml_content, "config.yaml")
    assert result["general"]["opensubtitles_username"] == "myuser"
    assert result["general"]["opensubtitles_api_key"] == "oskey"


def test_parse_config_unknown_extension_tries_yaml():
    from bazarr_migrator import parse_bazarr_config

    yaml_content = """
sonarr:
  apikey: "key"
"""
    # Unknown extension → tries YAML first
    result = parse_bazarr_config(yaml_content, "config.txt")
    assert result["sonarr"]["api_key"] == "key"


def test_parse_config_raw_preserved():
    from bazarr_migrator import parse_bazarr_config

    yaml_content = """
sonarr:
  ip: "10.0.0.1"
"""
    result = parse_bazarr_config(yaml_content, "config.yaml")
    assert "raw" in result
    assert isinstance(result["raw"], dict)


# ---------------------------------------------------------------------------
# _normalize_config — internal normalization
# ---------------------------------------------------------------------------


def test_normalize_config_handles_case_variants():
    from bazarr_migrator import _normalize_config

    data = {
        "Sonarr": {"ip": "host", "apikey": "key"},
        "Radarr": {"ip": "host2", "apikey": "key2"},
        "General": {"serie_default_language": "en"},
    }
    result = _normalize_config(data, [])
    assert result["sonarr"]["url"] == "host"
    assert result["radarr"]["url"] == "host2"
    assert result["general"]["source_language"] == "en"


def test_normalize_config_url_with_port():
    from bazarr_migrator import parse_bazarr_config

    yaml_content = """
sonarr:
  ip: "192.168.1.1"
  port: "8989"
  apikey: "abc"
"""
    result = parse_bazarr_config(yaml_content, "config.yaml")
    # Port should be stored separately, not concatenated in url by parse
    assert result["sonarr"]["port"] == "8989"


# ---------------------------------------------------------------------------
# _validate_table_name — SQL injection prevention
# ---------------------------------------------------------------------------


def test_validate_table_name_valid():
    from bazarr_migrator import _validate_table_name

    # Should not raise
    _validate_table_name("table_shows")
    _validate_table_name("my_table123")
    _validate_table_name("TableName")


def test_validate_table_name_invalid():
    import pytest

    from bazarr_migrator import _validate_table_name

    with pytest.raises(ValueError):
        _validate_table_name("table; DROP TABLE users")

    with pytest.raises(ValueError):
        _validate_table_name("1invalid")  # starts with digit

    with pytest.raises(ValueError):
        _validate_table_name("")


def test_validate_table_name_rejects_spaces():
    import pytest

    from bazarr_migrator import _validate_table_name

    with pytest.raises(ValueError):
        _validate_table_name("table name")


# ---------------------------------------------------------------------------
# migrate_bazarr_db — DB reading with real SQLite
# ---------------------------------------------------------------------------


def _create_bazarr_db(path: str) -> None:
    """Create a minimal Bazarr-like SQLite database for testing."""
    conn = sqlite3.connect(path)
    conn.executescript("""
        CREATE TABLE IF NOT EXISTS table_languages_profiles (
            profileId INTEGER PRIMARY KEY,
            name TEXT,
            items TEXT,
            cutoff INTEGER
        );
        CREATE TABLE IF NOT EXISTS table_blacklist (
            id INTEGER PRIMARY KEY,
            provider TEXT,
            subs_id TEXT,
            timestamp TEXT,
            language TEXT
        );
        CREATE TABLE IF NOT EXISTS table_settings_sonarr (
            ip TEXT,
            port TEXT,
            apikey TEXT
        );
        CREATE TABLE IF NOT EXISTS table_history (
            id INTEGER PRIMARY KEY,
            provider TEXT,
            score INTEGER,
            subs_id TEXT,
            video_path TEXT,
            language TEXT,
            timestamp TEXT
        );
        CREATE TABLE IF NOT EXISTS table_shows (
            id INTEGER PRIMARY KEY,
            title TEXT,
            path TEXT,
            profileId INTEGER,
            audio_language TEXT,
            sonarrSeriesId INTEGER
        );
    """)
    # Insert sample profile
    conn.execute(
        "INSERT INTO table_languages_profiles (profileId, name, items, cutoff) VALUES (?, ?, ?, ?)",
        (1, "German", json.dumps([{"language": "de", "hi": "False", "forced": "False"}]), None),
    )
    # Insert blacklist entry
    conn.execute(
        "INSERT INTO table_blacklist (provider, subs_id, timestamp, language) VALUES (?, ?, ?, ?)",
        ("opensubtitles", "12345", "2024-01-01", "de"),
    )
    # Insert history entry
    conn.execute(
        "INSERT INTO table_history (provider, score, subs_id, video_path, language, timestamp)"
        " VALUES (?, ?, ?, ?, ?, ?)",
        ("animetosho", 80, "abc", "/media/ep.mkv", "de", "2024-01-01"),
    )
    # Insert show
    conn.execute(
        "INSERT INTO table_shows (title, path, profileId, audio_language, sonarrSeriesId)"
        " VALUES (?, ?, ?, ?, ?)",
        ("My Anime", "/media/anime", 1, "en", 101),
    )
    conn.commit()
    conn.close()


def test_migrate_db_profiles(temp_dir):
    from bazarr_migrator import migrate_bazarr_db

    db_path = os.path.join(temp_dir, "bazarr.db")
    _create_bazarr_db(db_path)

    result = migrate_bazarr_db(db_path)
    assert len(result["profiles"]) == 1
    profile = result["profiles"][0]
    assert profile["name"] == "German"
    assert len(profile["languages"]) == 1
    assert profile["languages"][0]["language"] == "de"


def test_migrate_db_blacklist(temp_dir):
    from bazarr_migrator import migrate_bazarr_db

    db_path = os.path.join(temp_dir, "bazarr.db")
    _create_bazarr_db(db_path)

    result = migrate_bazarr_db(db_path)
    assert len(result["blacklist"]) == 1
    entry = result["blacklist"][0]
    assert entry["provider"] == "opensubtitles"
    assert entry["subtitle_id"] == "12345"
    assert entry["language"] == "de"


def test_migrate_db_history(temp_dir):
    from bazarr_migrator import migrate_bazarr_db

    db_path = os.path.join(temp_dir, "bazarr.db")
    _create_bazarr_db(db_path)

    result = migrate_bazarr_db(db_path)
    assert len(result["history"]) == 1
    entry = result["history"][0]
    assert entry["provider"] == "animetosho"
    assert entry["language"] == "de"


def test_migrate_db_shows(temp_dir):
    from bazarr_migrator import migrate_bazarr_db

    db_path = os.path.join(temp_dir, "bazarr.db")
    _create_bazarr_db(db_path)

    result = migrate_bazarr_db(db_path)
    assert len(result["shows"]) == 1
    show = result["shows"][0]
    assert show["title"] == "My Anime"
    assert show["sonarrSeriesId"] == 101


def test_migrate_db_nonexistent_file():
    from bazarr_migrator import migrate_bazarr_db

    result = migrate_bazarr_db("/nonexistent/path/to/bazarr.db")
    assert len(result["warnings"]) > 0
    assert result["profiles"] == []
    assert result["blacklist"] == []


def test_migrate_db_missing_table(temp_dir):
    from bazarr_migrator import migrate_bazarr_db

    # Create empty DB (no tables)
    db_path = os.path.join(temp_dir, "empty.db")
    conn = sqlite3.connect(db_path)
    conn.close()

    result = migrate_bazarr_db(db_path)
    # Should have warnings about missing tables, not crash
    assert len(result["warnings"]) > 0


def test_migrate_db_profile_invalid_json(temp_dir):
    from bazarr_migrator import migrate_bazarr_db

    db_path = os.path.join(temp_dir, "bazarr_bad.db")
    conn = sqlite3.connect(db_path)
    conn.execute(
        "CREATE TABLE table_languages_profiles (profileId INTEGER, name TEXT, items TEXT, cutoff INTEGER)"
    )
    conn.execute("INSERT INTO table_languages_profiles VALUES (1, 'Test', 'not-valid-json', NULL)")
    conn.commit()
    conn.close()

    result = migrate_bazarr_db(db_path)
    # Profile should exist with empty languages (invalid JSON handled)
    assert len(result["profiles"]) == 1
    assert result["profiles"][0]["languages"] == []
    # Warning should mention the parse failure
    assert any("parse" in w.lower() or "language" in w.lower() for w in result["warnings"])


# ---------------------------------------------------------------------------
# preview_migration — transformation preview
# ---------------------------------------------------------------------------


def test_preview_migration_empty_inputs():
    from bazarr_migrator import preview_migration

    config_data = {"sonarr": {}, "radarr": {}, "general": {}, "raw": {}, "warnings": []}
    db_data = {
        "profiles": [],
        "blacklist": [],
        "history": [],
        "shows": [],
        "movies": [],
        "warnings": [],
        "sonarr_config": {},
        "radarr_config": {},
    }
    result = preview_migration(config_data, db_data)
    assert result["config_entries"] == []
    assert result["profiles"] == []
    assert result["blacklist_count"] == 0


def test_preview_migration_with_sonarr_config():
    from bazarr_migrator import preview_migration

    config_data = {
        "sonarr": {"url": "10.0.0.1", "port": "8989", "api_key": "mykey"},
        "radarr": {},
        "general": {},
        "raw": {},
        "warnings": [],
    }
    db_data = {
        "profiles": [],
        "blacklist": [],
        "history": [],
        "shows": [],
        "movies": [],
        "warnings": [],
        "sonarr_config": {},
        "radarr_config": {},
    }
    result = preview_migration(config_data, db_data)
    # sonarr_url and sonarr_api_key should appear in config_entries
    keys = [e["key"] for e in result["config_entries"]]
    assert "sonarr_url" in keys
    assert "sonarr_api_key" in keys


def test_preview_migration_masks_api_key():
    from bazarr_migrator import _mask_preview, preview_migration

    # _mask_preview should show first 4 chars + ***
    assert _mask_preview("abc123abc") == "abc1***"
    assert _mask_preview("ab") == "***"
    assert _mask_preview("") == "***"


def test_preview_migration_blacklist_count():
    from bazarr_migrator import preview_migration

    config_data = {"sonarr": {}, "radarr": {}, "general": {}, "raw": {}, "warnings": []}
    db_data = {
        "profiles": [],
        "blacklist": [
            {"provider": "p1", "subtitle_id": "1", "language": "de", "timestamp": "2024-01-01"}
        ]
        * 5,
        "history": [],
        "shows": [],
        "movies": [],
        "warnings": [],
        "sonarr_config": {},
        "radarr_config": {},
    }
    result = preview_migration(config_data, db_data)
    assert result["blacklist_count"] == 5


# ---------------------------------------------------------------------------
# map_bazarr_provider_settings — provider mapping
# ---------------------------------------------------------------------------


def test_map_provider_settings_opensubtitles():
    from bazarr_migrator import map_bazarr_provider_settings

    config_data = {
        "general": {
            "opensubtitles_api_key": "oskey123",
            "opensubtitles_username": "testuser",
            "opensubtitles_password": "testpass",
        }
    }
    result = map_bazarr_provider_settings(config_data)
    assert "OpenSubtitles" in result["provider_mappings"]
    assert result["provider_mappings"]["OpenSubtitles"]["api_key"] == "oskey123"
    assert result["settings_imported"] == 1


def test_map_provider_settings_empty():
    from bazarr_migrator import map_bazarr_provider_settings

    config_data = {"general": {}}
    result = map_bazarr_provider_settings(config_data)
    assert result["provider_mappings"] == {}
    assert result["settings_imported"] == 0


# ---------------------------------------------------------------------------
# generate_mapping_report — schema inventory
# ---------------------------------------------------------------------------


def test_generate_mapping_report_nonexistent():
    from bazarr_migrator import generate_mapping_report

    report = generate_mapping_report("/nonexistent/path.db")
    assert len(report["warnings"]) > 0
    assert report["tables_found"] == []


def test_generate_mapping_report_real_db(temp_dir):
    from bazarr_migrator import generate_mapping_report

    db_path = os.path.join(temp_dir, "bazarr.db")
    _create_bazarr_db(db_path)

    report = generate_mapping_report(db_path)
    assert "table_languages_profiles" in report["tables_found"]
    assert "table_blacklist" in report["tables_found"]
    assert report["migration_summary"]["profiles_count"] == 1
    assert report["migration_summary"]["blacklist_count"] == 1