add gpt model health check (#21)

This commit is contained in:
Dmitry Afanasyev
2023-09-29 20:30:58 +03:00
committed by GitHub
parent 52df4d338f
commit 42f5191042
9 changed files with 228 additions and 69 deletions

View File

@@ -2,18 +2,18 @@ import asyncio
from asyncio import AbstractEventLoop
from unittest import mock
import httpx
import pytest
import telegram
from assertpy import assert_that
from faker import Faker
from httpx import AsyncClient
from httpx import AsyncClient, Response
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from constants import BotStagesEnum
from core.bot import BotApplication, BotQueue
from main import Application
from settings.config import AppSettings, settings
from tests.integration.bot.conftest import mocked_ask_question_api
from tests.integration.bot.networking import MockedRequest
from tests.integration.factories.bot import (
BotCallBackQueryFactory,
@@ -21,6 +21,7 @@ from tests.integration.factories.bot import (
BotUpdateFactory,
CallBackFactory,
)
from tests.integration.utils import mocked_ask_question_api
pytestmark = [
pytest.mark.asyncio,
@@ -31,11 +32,6 @@ pytestmark = [
faker = Faker()
async def test_bot_updates(rest_client: AsyncClient) -> None:
response = await rest_client.get("/api/healthcheck")
assert response.status_code == 200
async def test_bot_webhook_endpoint(
rest_client: AsyncClient,
main_application: Application,
@@ -169,8 +165,8 @@ async def test_about_bot_callback_action(
assert mocked_reply_text.call_args.args == (
f"Бот использует бесплатную модель {settings.GPT_MODEL} для ответов на вопросы. "
f"Принимает запросы на разных языках.\n\nБот так же умеет переводить русские голосовые сообщения в текст. "
f"Просто пришлите голосовуху и получите поток сознания в виде текста, но без знаков препинания",
f"\nПринимает запросы на разных языках.\n\nБот так же умеет переводить русские голосовые сообщения "
f"в текст. Просто пришлите голосовуху и получите поток сознания в виде текста, но без знаков препинания",
)
assert mocked_reply_text.call_args.kwargs == {"parse_mode": "Markdown"}
@@ -198,7 +194,10 @@ async def test_ask_question_action(
) -> None:
with mock.patch.object(
telegram._bot.Bot, "send_message", return_value=lambda *args, **kwargs: (args, kwargs)
) as mocked_send_message, mocked_ask_question_api(host=test_settings.GPT_BASE_HOST):
) as mocked_send_message, mocked_ask_question_api(
host=test_settings.GPT_BASE_HOST,
return_value=Response(status_code=httpx.codes.OK, text="Привет! Как я могу помочь вам сегодня?"),
):
bot_update = BotUpdateFactory(message=BotMessageFactory.create_instance(text="Привет!"))
bot_update["message"].pop("entities")
@@ -214,6 +213,55 @@ async def test_ask_question_action(
)
async def test_ask_question_action_not_success(
main_application: Application,
test_settings: AppSettings,
) -> None:
with mock.patch.object(
telegram._bot.Bot, "send_message", return_value=lambda *args, **kwargs: (args, kwargs)
) as mocked_send_message, mocked_ask_question_api(
host=test_settings.GPT_BASE_HOST, return_value=Response(status_code=httpx.codes.INTERNAL_SERVER_ERROR)
):
bot_update = BotUpdateFactory(message=BotMessageFactory.create_instance(text="Привет!"))
bot_update["message"].pop("entities")
await main_application.bot_app.application.process_update(
update=Update.de_json(data=bot_update, bot=main_application.bot_app.bot)
)
assert_that(mocked_send_message.call_args.kwargs).is_equal_to(
{
"text": "Что-то пошло не так, попробуйте еще раз или обратитесь к администратору",
"chat_id": bot_update["message"]["chat"]["id"],
},
include=["text", "chat_id"],
)
async def test_ask_question_action_critical_error(
main_application: Application,
test_settings: AppSettings,
) -> None:
with mock.patch.object(
telegram._bot.Bot, "send_message", return_value=lambda *args, **kwargs: (args, kwargs)
) as mocked_send_message, mocked_ask_question_api(
host=test_settings.GPT_BASE_HOST,
side_effect=Exception(),
):
bot_update = BotUpdateFactory(message=BotMessageFactory.create_instance(text="Привет!"))
bot_update["message"].pop("entities")
await main_application.bot_app.application.process_update(
update=Update.de_json(data=bot_update, bot=main_application.bot_app.bot)
)
assert_that(mocked_send_message.call_args.kwargs).is_equal_to(
{
"text": "Вообще всё сломалось :(",
"chat_id": bot_update["message"]["chat"]["id"],
},
include=["text", "chat_id"],
)
async def test_no_update_message(
main_application: Application,
test_settings: AppSettings,

View File

@@ -4,19 +4,16 @@ pytest framework. A common change is to allow monkeypatching of the class member
enforcing slots in the subclasses."""
import asyncio
from asyncio import AbstractEventLoop
from contextlib import contextmanager
from datetime import tzinfo
from typing import Any, AsyncGenerator, Iterator
from typing import Any, AsyncGenerator
import pytest
import pytest_asyncio
import respx
from httpx import AsyncClient, Response
from httpx import AsyncClient
from pytest_asyncio.plugin import SubRequest
from telegram import Bot, User
from telegram.ext import Application, ApplicationBuilder, Defaults, ExtBot
from constants import CHAT_GPT_BASE_URI
from core.bot import BotApplication
from core.handlers import bot_event_handlers
from main import Application as AppApplication
@@ -255,15 +252,3 @@ async def rest_client(
headers={"Content-Type": "application/json"},
) as client:
yield client
@contextmanager
def mocked_ask_question_api(host: str) -> Iterator[respx.MockRouter]:
with respx.mock(
assert_all_mocked=True,
assert_all_called=True,
base_url=host,
) as respx_mock:
ask_question_route = respx_mock.post(url=CHAT_GPT_BASE_URI, name="ask_question")
ask_question_route.return_value = Response(status_code=200, text="Привет! Как я могу помочь вам сегодня?")
yield respx_mock

View File

@@ -0,0 +1,56 @@
import httpx
import pytest
from faker import Faker
from httpx import AsyncClient, Response
from settings.config import AppSettings
from tests.integration.utils import mocked_ask_question_api
pytestmark = [
pytest.mark.asyncio,
pytest.mark.enable_socket,
]
faker = Faker()
async def test_bot_updates(rest_client: AsyncClient) -> None:
response = await rest_client.get("/api/healthcheck")
assert response.status_code == 200
async def test_bot_healthcheck_is_ok(
rest_client: AsyncClient,
test_settings: AppSettings,
) -> None:
with mocked_ask_question_api(
host=test_settings.GPT_BASE_HOST,
return_value=Response(status_code=httpx.codes.OK, text="Привет! Как я могу помочь вам сегодня?"),
):
response = await rest_client.get("/api/bot-healthcheck")
assert response.status_code == httpx.codes.OK
async def test_bot_healthcheck_invalid_request_model(
rest_client: AsyncClient,
test_settings: AppSettings,
) -> None:
with mocked_ask_question_api(
host=test_settings.GPT_BASE_HOST,
return_value=Response(status_code=httpx.codes.OK, text="Invalid request model"),
):
response = await rest_client.get("/api/bot-healthcheck")
assert response.status_code == httpx.codes.INTERNAL_SERVER_ERROR
async def test_bot_healthcheck_not_ok(
rest_client: AsyncClient,
test_settings: AppSettings,
) -> None:
with mocked_ask_question_api(
host=test_settings.GPT_BASE_HOST,
side_effect=Exception(),
):
response = await rest_client.get("/api/bot-healthcheck")
assert response.status_code == httpx.codes.INTERNAL_SERVER_ERROR

View File

@@ -0,0 +1,22 @@
from contextlib import contextmanager
from typing import Any, Iterator
import respx
from httpx import Response
from constants import CHAT_GPT_BASE_URI
@contextmanager
def mocked_ask_question_api(
host: str, return_value: Response | None = None, side_effect: Any | None = None
) -> Iterator[respx.MockRouter]:
with respx.mock(
assert_all_mocked=True,
assert_all_called=True,
base_url=host,
) as respx_mock:
ask_question_route = respx_mock.post(url=CHAT_GPT_BASE_URI, name="ask_question")
ask_question_route.return_value = return_value
ask_question_route.side_effect = side_effect
yield respx_mock