Ctrl K
ComunidadGuías…

backend/tests/test_azure_translator_backend.py

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

Archivo original ↗Preguntar a KiBot
"""AzureTranslatorBackend tests."""

from decimal import Decimal
from unittest.mock import MagicMock, patch

import pytest


@pytest.fixture
def app(tmp_path, monkeypatch):
    monkeypatch.setenv("SUBLARR_DB_PATH", str(tmp_path / "app.db"))
    from config import reload_settings

    reload_settings()
    from app import create_app

    app = create_app(testing=True)
    from extensions import db as sa_db

    with app.app_context():
        sa_db.create_all()
    yield app


@pytest.fixture
def reset_conc():
    from translation.concurrency import reset_for_tests

    reset_for_tests()


def _azure(api_key="sub-test", region="westeurope"):
    from translation.azure_translator import AzureTranslatorBackend

    return AzureTranslatorBackend(api_key=api_key, region=region)


def test_construction_requires_api_key():
    from translation.azure_translator import AzureTranslatorBackend

    with pytest.raises(ValueError, match="api_key"):
        AzureTranslatorBackend(api_key="", region="westeurope")


def test_default_region_westeurope():
    from translation.azure_translator import AzureTranslatorBackend

    b = AzureTranslatorBackend(api_key="k")
    assert b.region == "westeurope"


def test_char_price_from_sheet():
    b = _azure()
    assert b.cost_per_1m_chars == Decimal("10.00")
    assert b.name == "azure_translator"


def test_call_uses_subscription_key_header(app, reset_conc):
    from translation.concurrency import get_concurrency

    get_concurrency().register("azure_translator", 2)

    mock_resp = MagicMock()
    mock_resp.raise_for_status.return_value = None
    mock_resp.json.return_value = [
        {"translations": [{"text": "hallo", "to": "de"}]},
        {"translations": [{"text": "welt", "to": "de"}]},
    ]
    b = _azure()
    with patch("translation.azure_translator.requests.post") as mock_post:
        mock_post.return_value = mock_resp
        with app.app_context():
            result = b.translate_batch(
                ["hello", "world"],
                source_lang="en",
                target_lang="de",
            )

    assert result.success is True
    assert result.translated_lines == ["hallo", "welt"]
    call_args = mock_post.call_args
    headers = call_args.kwargs.get("headers") or {}
    assert headers.get("Ocp-Apim-Subscription-Key") == "sub-test"
    assert headers.get("Ocp-Apim-Subscription-Region") == "westeurope"


def test_url_includes_target_lang(app, reset_conc):
    from translation.concurrency import get_concurrency

    get_concurrency().register("azure_translator", 2)

    mock_resp = MagicMock()
    mock_resp.raise_for_status.return_value = None
    mock_resp.json.return_value = [
        {"translations": [{"text": "hallo", "to": "de"}]},
    ]
    b = _azure()
    with patch("translation.azure_translator.requests.post") as mock_post:
        mock_post.return_value = mock_resp
        with app.app_context():
            b.translate_batch(["hello"], source_lang="en", target_lang="de")
    url = mock_post.call_args.args[0]
    assert "to=de" in url
    assert "from=en" in url


def test_cost_calculation(app, reset_conc):
    """Cost should be chars_in * $10/1M."""
    from db.models.translation import TranslationEvent
    from extensions import db
    from translation.concurrency import get_concurrency

    get_concurrency().register("azure_translator", 2)

    mock_resp = MagicMock()
    mock_resp.raise_for_status.return_value = None
    mock_resp.json.return_value = [
        {"translations": [{"text": "x" * 100, "to": "de"}]},
    ]
    b = _azure()
    with patch("translation.azure_translator.requests.post") as mock_post:
        mock_post.return_value = mock_resp
        with app.app_context():
            b.translate_batch(["helloworld"], source_lang="en", target_lang="de")

    with app.app_context():
        row = db.session.query(TranslationEvent).filter_by(backend="azure_translator").one()
        # 10 chars * 10/1M = 100 micro_usd
        assert row.cost_estimate_micro_usd == 100
        assert row.chars_in == 10
        assert row.chars_out == 100


def test_error_writes_error_event(app, reset_conc):
    from db.models.translation import TranslationEvent
    from extensions import db
    from translation.concurrency import get_concurrency

    get_concurrency().register("azure_translator", 2)

    b = _azure()
    with patch("translation.azure_translator.requests.post") as mock_post:
        mock_post.side_effect = RuntimeError("api down")
        with app.app_context(), pytest.raises(RuntimeError):
            b.translate_batch(["x"], source_lang="en", target_lang="de")

    with app.app_context():
        row = db.session.query(TranslationEvent).filter_by(backend="azure_translator").one()
        assert row.status == "error"
        assert row.error_type == "RuntimeError"


def test_config_fields_present():
    b = _azure()
    field_names = {f["key"] if isinstance(f, dict) else f.key for f in b.config_fields}
    assert "api_key" in field_names
    assert "region" in field_names


def test_supports_batch_not_glossary():
    b = _azure()
    assert b.supports_batch is True
    assert b.supports_glossary is False