Ctrl K
ComunidadGuías…

backend/tests/test_error_utils.py

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

Archivo original ↗Preguntar a KiBot
"""Tests for handle_api_error decorator."""

import pytest
from flask import Flask


def make_app():
    app = Flask(__name__)
    app.config["TESTING"] = True
    return app


def test_decorator_passes_through_on_success():
    from error_utils import handle_api_error

    app = make_app()

    @app.route("/ok")
    @handle_api_error("Should not appear")
    def ok_view():
        from flask import jsonify

        return jsonify({"result": "good"})

    with app.test_client() as c:
        resp = c.get("/ok")
        assert resp.status_code == 200
        assert resp.get_json()["result"] == "good"


def test_decorator_returns_500_json_on_exception():
    from error_utils import handle_api_error

    app = make_app()

    @app.route("/boom")
    @handle_api_error("Something went wrong")
    def boom_view():
        raise RuntimeError("kaboom")

    with app.test_client() as c:
        resp = c.get("/boom")
        assert resp.status_code == 500
        data = resp.get_json()
        assert "error" in data
        assert data["error"] == "Something went wrong"


def test_decorator_custom_status_code():
    from error_utils import handle_api_error

    app = make_app()

    @app.route("/bad")
    @handle_api_error("Custom error", status_code=503)
    def bad_view():
        raise ValueError("oops")

    with app.test_client() as c:
        resp = c.get("/bad")
        assert resp.status_code == 503
        assert resp.get_json()["error"] == "Custom error"


def test_decorator_preserves_function_name():
    from error_utils import handle_api_error

    @handle_api_error("msg")
    def my_special_view():
        pass

    assert my_special_view.__name__ == "my_special_view"


# ─── internal_error_response ─────────────────────────────────────────────────


def test_internal_error_response_never_returns_the_exception_text(caplog):
    from error_utils import internal_error_response

    app = make_app()

    @app.route("/fail")
    def fail_view():
        try:
            raise OSError("[Errno 13] Permission denied: '/config/x.db?apikey=abc123'")
        except OSError as exc:
            return internal_error_response("Restore failed", exc)

    with caplog.at_level("ERROR", logger="error_utils"), app.test_client() as c:
        resp = c.get("/fail")

    assert resp.status_code == 500
    assert resp.get_json() == {"error": "Restore failed"}
    assert "apikey" not in resp.get_data(as_text=True)
    # ...but the operator still gets the full exception in the log.
    record = next(r for r in caplog.records if r.name == "error_utils")
    assert record.exc_info and "Permission denied" in str(record.exc_info[1])


def test_internal_error_response_carries_status_and_request_id():
    from flask import g

    from error_utils import internal_error_response

    app = make_app()

    @app.route("/busy")
    def busy_view():
        g.request_id = "req-42"
        return internal_error_response("Ollama pull failed", status=502)

    with app.test_client() as c:
        resp = c.get("/busy")

    assert resp.status_code == 502
    assert resp.get_json() == {"error": "Ollama pull failed", "request_id": "req-42"}


def test_internal_error_response_without_exc_does_not_log(caplog):
    from error_utils import internal_error_response

    app = make_app()
    with caplog.at_level("DEBUG", logger="error_utils"), app.test_request_context():
        _body, status = internal_error_response("Preview failed")

    assert status == 500
    assert not [r for r in caplog.records if r.name == "error_utils"]


# ─── public_error_text ───────────────────────────────────────────────────────


def test_public_error_text_redacts_a_key_in_a_request_url():
    from error_utils import public_error_text

    exc = ConnectionError(
        "HTTPConnectionPool(host='jellyfin', port=8096): Max retries exceeded with url: "
        "/Items?api_key=0123456789abcdef (Caused by NewConnectionError('refused'))"
    )
    text = public_error_text(exc)

    assert "0123456789abcdef" not in text
    assert "Max retries exceeded" in text  # the reason survives


def test_public_error_text_is_one_bounded_line():
    from error_utils import public_error_text

    text = public_error_text(RuntimeError("line one\nline two " + "x" * 1000))

    assert "\n" not in text
    assert text.startswith("line one line two")
    assert len(text) == 300