Ctrl K
ComunidadGuías…

tests/test_v150.py

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

Archivo original ↗Preguntar a KiBot
"""v1.5.0 : fallback LLM conditionnel + canal web."""

import sys
from pathlib import Path

import httpx
import pytest

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))

from app import graph  # noqa: E402
from app.config import get_settings  # noqa: E402


class _TagsResponse:
    status_code = 200


async def test_fallback_choisi_si_joignable(monkeypatch):
    monkeypatch.setattr(get_settings(), "ollama_fallback_url", "http://fallback-host:11434")

    async def ok_get(self, url, **kwargs):
        assert url.endswith("/api/tags")
        return _TagsResponse()

    monkeypatch.setattr(httpx.AsyncClient, "get", ok_get)

    async def fake_pick(base, model):
        return f"llm@{base}"

    monkeypatch.setattr(graph, "_pick_llm", fake_pick)

    llm, source = await graph.select_llm()
    assert source == "fallback" and "fallback-host" in str(llm)


async def test_primaire_si_fallback_vide(monkeypatch):
    monkeypatch.setattr(get_settings(), "ollama_fallback_url", "")
    llm, source = await graph.select_llm()
    assert source == "primaire"


async def test_primaire_si_fallback_injoignable(monkeypatch):
    """Machine éteinte = ConnectError → primaire, jamais d'erreur visible."""
    monkeypatch.setattr(get_settings(), "ollama_fallback_url", "http://fallback-host:11434")

    async def refused(self, url, **kwargs):
        raise httpx.ConnectError("refused", request=None)

    monkeypatch.setattr(httpx.AsyncClient, "get", refused)
    llm, source = await graph.select_llm()
    assert source == "primaire"


async def test_canal_web_expose_page_et_chat(monkeypatch):
    from fastapi.testclient import TestClient

    from app import main as app_main

    class FakeMemory:
        async def ensure_conversation(self, *a, **k):
            return 42

        async def add_message(self, *a, **k):
            pass

        async def history(self, *a, **k):
            return []

        async def close(self):
            pass

    async def fake_agent(memory, text, history, user_key="default"):
        return f"réponse à : {text}"

    monkeypatch.setattr(app_main, "memory", FakeMemory())
    monkeypatch.setattr(app_main, "run_agent", fake_agent)

    # Sans contexte `with` : la lifespan (memory réelle + watcher) ne tourne pas
    client = TestClient(app_main.app)
    page = client.get("/web")
    assert page.status_code == 200 and "Vektor" in page.text and "api/web/chat" in page.text

    ok = client.post(
        "/api/web/chat",
        json={"text": "état de jellyfin ?"},
        headers={"X-Vektor-Token": "test-token"},
    )
    assert ok.status_code == 200 and "réponse à" in ok.json()["response"]

    refus = client.post(
        "/api/web/chat", json={"text": "salut"}, headers={"X-Vektor-Token": "mauvais"}
    )
    assert refus.status_code == 401


async def test_canal_web_commandes_directes(monkeypatch):
    """« /dns » et les autres raccourcis renvoient le rapport direct (pas de
    LLM, pas de mémoire) — même contrat que les commandes Telegram."""
    from fastapi.testclient import TestClient

    from app import main as app_main

    called = []

    async def fake_dns():
        called.append("dns")
        return "🟢 rapport dns de test"

    async def fail_agent(*a, **k):
        raise AssertionError("une commande ne doit pas passer par le LLM")

    monkeypatch.setitem(app_main._WEB_COMMANDS, "/dns", fake_dns)
    monkeypatch.setattr(app_main, "run_agent", fail_agent)

    client = TestClient(app_main.app)
    r = client.post(
        "/api/web/chat",
        json={"text": "/dns"},
        headers={"X-Vektor-Token": "test-token"},
    )
    assert r.status_code == 200
    assert "rapport dns de test" in r.json()["response"]
    assert called == ["dns"]

    # commande inconnue en slash : bascule en chat LLM normal (pas de crash)
    class FakeMemory2:
        async def ensure_conversation(self, *a, **k):
            return 7

        async def add_message(self, *a, **k):
            pass

        async def history(self, *a, **k):
            return []

    async def fake_agent2(memory, text, history, user_key="default"):
        return "chat normal"

    monkeypatch.setattr(app_main, "memory", FakeMemory2())
    monkeypatch.setattr(app_main, "run_agent", fake_agent2)
    r2 = client.post(
        "/api/web/chat",
        json={"text": "/cmd-inexistante"},
        headers={"X-Vektor-Token": "test-token"},
    )
    assert r2.status_code == 200 and r2.json()["response"] == "chat normal"