Ctrl K
ComunidadGuías…

tests/test_bilingual_queries.py

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

Archivo original ↗Preguntar a KiBot
"""
Tests for bilingual search queries.

Checks:
- _coerce_search_terms accepts a dict and a flat list
- _detect_term_language recognises German umlauts and stop words
- ResearchQuestion stores search_terms_by_lang correctly
- the terms_by_lang builder in the orchestrator produces separate lists
  per language (no mirror image)
"""

import sys
from pathlib import Path
from unittest.mock import MagicMock

if "httpx" not in sys.modules:
    sys.modules["httpx"] = MagicMock()
if "src.connectors.rate_limiter" not in sys.modules:
    _rl = MagicMock()
    _rl.get_rate_limiter = MagicMock(return_value=MagicMock())
    sys.modules["src.connectors.rate_limiter"] = _rl

sys.path.insert(0, str(Path(__file__).parent.parent))

from src.pipeline.models import ResearchQuestion  # noqa: E402
from src.pipeline.query_utils import (  # noqa: E402
    coerce_search_terms as _coerce_search_terms,
)


def test_coerce_accepts_dict():
    """Dict input becomes a dict + flat list."""
    print("Test: _coerce accepts a dict ...", end=" ")
    raw = {
        "de": ["KI Regulierung Deutschland", "Künstliche Intelligenz EU"],
        "en": ["AI regulation Europe", "artificial intelligence policy"],
    }
    flat, by_lang = _coerce_search_terms(raw)
    assert set(by_lang.keys()) == {"de", "en"}
    assert len(by_lang["de"]) == 2
    assert len(by_lang["en"]) == 2
    assert len(flat) == 4  # union of all terms
    assert "KI Regulierung Deutschland" in flat
    assert "AI regulation Europe" in flat
    print("✓")


def test_coerce_handles_empty():
    """Empty input returns empty structures."""
    print("Test: _coerce empty input ...", end=" ")
    flat, by_lang = _coerce_search_terms(None)
    assert flat == []
    assert by_lang == {}
    flat, by_lang = _coerce_search_terms({})
    assert flat == []
    assert by_lang == {}
    flat, by_lang = _coerce_search_terms([])
    assert flat == []
    assert by_lang == {}
    print("✓")


def test_coerce_skips_empty_strings():
    """Empty strings and non-strings are filtered."""
    print("Test: _coerce filters empty strings ...", end=" ")
    raw = {"de": ["gut", "", "   ", None, 42, "auch gut"]}
    flat, by_lang = _coerce_search_terms(raw)
    assert by_lang["de"] == ["gut", "auch gut"]
    assert flat == ["gut", "auch gut"]
    print("✓")


def test_research_question_stores_by_lang():
    """ResearchQuestion stores search_terms_by_lang directly."""
    print("Test: ResearchQuestion has the by_lang field ...", end=" ")
    q = ResearchQuestion(
        id="F1",
        question="Test?",
        search_terms=["term1", "term2"],
        search_terms_by_lang={"de": ["term1"], "en": ["term2"]},
        search_langs=["de", "en"],
    )
    assert q.search_terms_by_lang == {"de": ["term1"], "en": ["term2"]}
    assert q.search_terms == ["term1", "term2"]
    print("✓")


def test_research_question_default_by_lang_empty():
    """The default for search_terms_by_lang is an empty dict."""
    print("Test: ResearchQuestion default by_lang is empty ...", end=" ")
    q = ResearchQuestion(
        id="F1",
        question="Test?",
        search_terms=["term1"],
    )
    assert q.search_terms_by_lang == {}
    print("✓")


def test_orchestrator_terms_by_lang_from_dict():
    """The orchestrator builder produces separate lists when
    search_terms_by_lang is set.

    This test simulates the code block in the orchestrator directly.
    """
    print("Test: terms_by_lang from a dict ...", end=" ")

    # simulate the orchestrator logic
    questions = [
        ResearchQuestion(
            id="F1",
            question="Test?",
            search_terms_by_lang={
                "de": ["KI Regulierung Deutschland"],
                "en": ["AI regulation Germany"],
            },
            search_langs=["de", "en"],
        ),
        ResearchQuestion(
            id="F2",
            question="Test 2?",
            search_terms_by_lang={
                "de": ["Datenschutz Europa"],
                "en": ["data protection Europe"],
            },
            search_langs=["de", "en"],
        ),
    ]

    terms_by_lang: dict = {}
    all_langs: set = set()
    for q in questions:
        for lang in q.search_langs:
            all_langs.add(lang)
            if lang not in terms_by_lang:
                terms_by_lang[lang] = []
        if q.search_terms_by_lang:
            for lang, terms in q.search_terms_by_lang.items():
                if lang not in terms_by_lang:
                    terms_by_lang[lang] = []
                for term in terms:
                    if term and term not in terms_by_lang[lang]:
                        terms_by_lang[lang].append(term)

    # de and en must have DIFFERENT lists
    assert terms_by_lang["de"] != terms_by_lang["en"]
    assert "KI Regulierung Deutschland" in terms_by_lang["de"]
    assert "KI Regulierung Deutschland" not in terms_by_lang["en"]
    assert "AI regulation Germany" in terms_by_lang["en"]
    assert "AI regulation Germany" not in terms_by_lang["de"]
    print("✓")


def main():
    print("=" * 60)
    print("Tests: bilingual query generation")
    print("=" * 60)

    tests = [
        test_coerce_accepts_dict,
        test_coerce_handles_empty,
        test_coerce_skips_empty_strings,
        test_research_question_stores_by_lang,
        test_research_question_default_by_lang_empty,
        test_orchestrator_terms_by_lang_from_dict,
    ]

    failed = 0
    for t in tests:
        try:
            t()
        except AssertionError as e:
            print(f"❌ {t.__name__}: {e}")
            failed += 1
        except Exception as e:
            print(f"💥 {t.__name__}: {type(e).__name__}: {e}")
            import traceback
            traceback.print_exc()
            failed += 1

    print("=" * 60)
    if failed:
        print(f"❌ {failed}/{len(tests)} tests failed")
        sys.exit(1)
    else:
        print(f"✅ All {len(tests)} tests passed")


if __name__ == "__main__":
    main()