Babble / tests /test_app.py
root
polish chat menus and memory
30d0e8e
import importlib
import json
from pathlib import Path
from typing import Any
from fastapi.testclient import TestClient
import app as babble_app
class FakeTelegramClient:
requests: list[dict[str, Any]] = []
status_code = 200
text = "{\"ok\":true}"
def __init__(self, *args: Any, **kwargs: Any) -> None:
del args, kwargs
async def __aenter__(self) -> "FakeTelegramClient":
return self
async def __aexit__(self, *args: Any) -> None:
del args
async def post(self, url: str, json: dict[str, Any]) -> "FakeTelegramClient":
self.requests.append({"url": url, "json": json})
return self
class TimeoutTelegramClient(FakeTelegramClient):
async def post(self, url: str, json: dict[str, Any]) -> "FakeTelegramClient":
del url, json
raise babble_app.httpx.ConnectTimeout("timeout", request=None)
def load_app(monkeypatch, **env):
monkeypatch.setenv("REQUIRE_MODEL", "false")
monkeypatch.setenv("MEMORY_ENABLED", "false")
for key, value in env.items():
monkeypatch.setenv(key, value)
return importlib.reload(babble_app)
def configure_webhook(monkeypatch, **env):
reloaded_app = load_app(
monkeypatch,
WEBHOOK_SECRET="expected-secret",
TELEGRAM_BOT_TOKEN="fake-token",
LLM_PROVIDER="stub",
**env,
)
FakeTelegramClient.requests = []
monkeypatch.setattr(reloaded_app.httpx, "AsyncClient", FakeTelegramClient)
return reloaded_app
def test_health_endpoint(monkeypatch) -> None:
reloaded_app = load_app(monkeypatch, LLM_PROVIDER="stub")
client = TestClient(reloaded_app.create_app())
response = client.get("/health")
assert response.status_code == 200
body = response.json()
assert body["ok"] is True
assert body["service"] == "babble"
assert body["model"] == "Qwen/Qwen3-0.6B"
assert body["loaded"] is False
assert body["persona"] == "default"
def test_persona_endpoint_lists_available_personas(monkeypatch) -> None:
reloaded_app = load_app(monkeypatch, LLM_PROVIDER="stub")
client = TestClient(reloaded_app.create_app())
response = client.get("/persona")
assert response.status_code == 200
body = response.json()
assert body["active_persona"]["persona_id"] == "default"
assert body["active_persona"]["name"] == "Babble"
assert "playful" in body["available_personas"]
def test_menu_endpoint_shows_commands(monkeypatch) -> None:
reloaded_app = load_app(monkeypatch, LLM_PROVIDER="stub")
client = TestClient(reloaded_app.create_app())
response = client.get("/menu")
assert response.status_code == 200
text = response.json()["text"]
assert "/persona [id]" in text
assert "/character key=value ..." in text
assert "Available personas" in text
def test_webhook_rejects_wrong_secret(monkeypatch) -> None:
reloaded_app = configure_webhook(monkeypatch)
client = TestClient(reloaded_app.create_app())
response = client.post(
"/telegram/webhook/wrong-secret",
json={"message": {"chat": {"id": 123}, "text": "hello"}},
)
assert response.status_code == 403
assert FakeTelegramClient.requests == []
def test_start_sends_telegram_message_payload(monkeypatch) -> None:
reloaded_app = configure_webhook(monkeypatch)
client = TestClient(reloaded_app.create_app())
response = client.post(
"/telegram/webhook/expected-secret",
json={"message": {"chat": {"id": 123}, "text": " /start "}},
)
assert response.status_code == 200
assert response.json() == {"ok": True, "sent": True}
request = FakeTelegramClient.requests[-1]
assert request["url"] == "https://api.telegram.org/botfake-token/sendMessage"
assert request["json"]["chat_id"] == 123
assert request["json"]["text"] == "Hey, I’m here. What kind of mood are we in tonight?"
assert "reply_markup" in request["json"]
assert request["json"]["reply_markup"]["keyboard"][0] == [
"Menu",
"Characters",
"Build Character",
]
def test_telegram_timeout_falls_back_to_webhook_payload(monkeypatch) -> None:
reloaded_app = load_app(
monkeypatch,
WEBHOOK_SECRET="expected-secret",
TELEGRAM_BOT_TOKEN="fake-token",
LLM_PROVIDER="stub",
)
monkeypatch.setattr(reloaded_app.httpx, "AsyncClient", TimeoutTelegramClient)
client = TestClient(reloaded_app.create_app())
response = client.post(
"/telegram/webhook/expected-secret",
json={"message": {"chat": {"id": 123}, "text": "hello"}},
)
assert response.status_code == 200
assert response.json() == {
"method": "sendMessage",
"chat_id": 123,
"text": "Babble stub response: backend online and ready.",
"reply_markup": {
"keyboard": [
["Menu", "Characters", "Build Character"],
["Warm", "Romantic", "Playful", "Deep"],
["Babble", "Charlotte", "Jaylaa"],
["Memory On", "Reset", "Help"],
],
"resize_keyboard": True,
"is_persistent": True,
"input_field_placeholder": "Tap a button or send a message",
},
}
def test_persona_command_switches_active_persona(monkeypatch) -> None:
reloaded_app = configure_webhook(monkeypatch)
client = TestClient(reloaded_app.create_app())
response = client.post(
"/telegram/webhook/expected-secret",
json={"message": {"chat": {"id": 123}, "text": "/persona playful"}},
)
assert response.status_code == 200
assert FakeTelegramClient.requests[-1]["json"]["text"] == (
"Persona switched to playful (Charlotte)."
)
client.post(
"/telegram/webhook/expected-secret",
json={"message": {"chat": {"id": 123}, "text": "/menu"}},
)
assert "playful" in FakeTelegramClient.requests[-1]["json"]["text"]
def test_character_command_updates_custom_profile(monkeypatch) -> None:
reloaded_app = configure_webhook(monkeypatch)
client = TestClient(reloaded_app.create_app())
response = client.post(
"/telegram/webhook/expected-secret",
json={
"message": {
"chat": {"id": 123},
"text": (
'/character name=Nova age=21 gender=woman '
'style="teasing and warm" voice=soft mood=curious '
'boundaries="adult romance only" opening="Come closer."'
),
}
},
)
assert response.status_code == 200
text = FakeTelegramClient.requests[-1]["json"]["text"]
assert "Character updated." in text
assert "Nova" in text
assert "Age: 21" in text
def test_missing_message_does_not_crash(monkeypatch) -> None:
reloaded_app = configure_webhook(monkeypatch)
client = TestClient(reloaded_app.create_app())
response = client.post("/telegram/webhook/expected-secret", json={"ok": True})
assert response.status_code == 200
assert response.json() == {"ok": True, "ignored": "missing_chat_id"}
assert FakeTelegramClient.requests == []
def test_memory_store_persists_turns(tmp_path: Path, monkeypatch) -> None:
memory_path = tmp_path / "chat_memory.json"
reloaded_app = load_app(
monkeypatch,
MEMORY_ENABLED="true",
MEMORY_PATH=str(memory_path),
LLM_PROVIDER="stub",
)
record = reloaded_app._default_session_record()
record = reloaded_app._record_turn(record, "user", "hello")
record = reloaded_app._record_turn(record, "assistant", "hi there")
reloaded_app._set_session_record("123", record)
assert memory_path.exists()
payload = json.loads(memory_path.read_text(encoding="utf-8"))
assert payload["123"]["memory"]["turns"][-2:] == [
{"role": "user", "text": "hello"},
{"role": "assistant", "text": "hi there"},
]
def test_debug_config_reports_persona_and_memory(monkeypatch) -> None:
reloaded_app = load_app(
monkeypatch,
DEBUG="true",
LLM_PROVIDER="stub",
PERSONA_ID="playful",
)
client = TestClient(reloaded_app.create_app())
response = client.get("/debug/config")
assert response.status_code == 200
body = response.json()
assert body["persona_id"] == "playful"
assert body["persona_name"] == "Charlotte"
assert body["memory_enabled"] is False
assert body["persona_count"] >= 3
def test_debug_model_surfaces_local_model_failure(monkeypatch) -> None:
reloaded_app = load_app(
monkeypatch,
DEBUG="true",
LOCAL_MODEL_ENABLED="false",
)
client = TestClient(reloaded_app.create_app())
response = client.get("/debug/model")
assert response.status_code == 200
body = response.json()
assert body["ok"] is False
assert body["error"] == "RuntimeError"
assert body["detail"] == "Local model is disabled"
def test_model_endpoint_respects_provider_knob(monkeypatch) -> None:
reloaded_app = load_app(monkeypatch, LLM_PROVIDER="stub")
client = TestClient(reloaded_app.create_app())
response = client.get("/model")
assert response.status_code == 200
body = response.json()
assert body["model_id"] == "Qwen/Qwen3-0.6B"
assert body["enabled"] is False
assert body["loaded"] is False
assert body["persona_id"] == "default"
assert body["memory_enabled"] is False