From f50e47ae916672c822e68d8f489b95b67d9c3a37 Mon Sep 17 00:00:00 2001 From: Sunandhita B Date: Fri, 29 Nov 2024 13:37:42 +0530 Subject: [PATCH] Updated api tests to improve test coverage --- api/app/handlers/v2/bot.py | 1 + api/tests/test_bot.py | 57 +++++ api/tests/test_crud.py | 450 +++++++++++++++++++++++++++++++++++++ 3 files changed, 508 insertions(+) create mode 100644 api/tests/test_bot.py create mode 100644 api/tests/test_crud.py diff --git a/api/app/handlers/v2/bot.py b/api/app/handlers/v2/bot.py index da729124..284929d3 100644 --- a/api/app/handlers/v2/bot.py +++ b/api/app/handlers/v2/bot.py @@ -32,6 +32,7 @@ async def install(install_content: JBBotCode) -> Flow: fsm_code=install_content.code, requirements_txt=install_content.requirements, index_urls=install_content.index_urls, + version = bot.version ), ), ) diff --git a/api/tests/test_bot.py b/api/tests/test_bot.py new file mode 100644 index 00000000..24ec46c9 --- /dev/null +++ b/api/tests/test_bot.py @@ -0,0 +1,57 @@ +from unittest.mock import patch +import pytest +from lib.data_models import Flow, BotConfig, BotIntent, FlowIntent, Bot +from lib.models import JBBot +from app.handlers.v2.bot import list_bots, install +from app.jb_schema import JBBotCode + +@pytest.mark.asyncio +async def test_list_bots(): + mock_bot1 = JBBot(id="test_bot_1", name="Bot1", status="active") + mock_bot2 = JBBot(id="test_bot_2", name="Bot2", status="active") + + bot_list = [mock_bot1,mock_bot2] + with patch("app.handlers.v2.bot.get_bot_list", return_value = bot_list) as mock_get_bot_list: + result = await list_bots() + + assert result == bot_list + mock_get_bot_list.assert_awaited_once() + +@pytest.mark.asyncio +async def test_install(): + + mock_jbbot_code = JBBotCode( + name = "Bot1", + status = "active", + dsl = "test_dsl", + code = "test_code", + requirements = "codaio", + index_urls = ["index_url_1","index_url_2"], + version = "1.0.0", + ) + + mock_bot1 = JBBot(id="test_bot_1", + name=mock_jbbot_code.name, + status=mock_jbbot_code.status, + dsl = mock_jbbot_code.dsl, + code = mock_jbbot_code.code, + requirements = mock_jbbot_code.requirements, + index_urls = mock_jbbot_code.index_urls, + version = mock_jbbot_code.version) + + with patch("app.handlers.v2.bot.create_bot", return_value = mock_bot1) as mock_create_bot: + result = await install(mock_jbbot_code) + + assert isinstance(result,Flow) + assert result.source == "api" + assert result.intent == FlowIntent.BOT + assert isinstance(result.bot_config,BotConfig) + assert result.bot_config.bot_id == mock_bot1.id + assert isinstance(result.bot_config.bot,Bot) + assert result.bot_config.bot.name == mock_jbbot_code.name + assert result.bot_config.bot.fsm_code == mock_jbbot_code.code + assert result.bot_config.bot.requirements_txt == mock_jbbot_code.requirements + assert result.bot_config.bot.index_urls == mock_jbbot_code.index_urls + assert result.bot_config.bot.version == mock_bot1.version + + mock_create_bot.assert_awaited_once_with(mock_jbbot_code.model_dump()) diff --git a/api/tests/test_crud.py b/api/tests/test_crud.py new file mode 100644 index 00000000..613bb5a5 --- /dev/null +++ b/api/tests/test_crud.py @@ -0,0 +1,450 @@ +import pytest +from unittest import mock +from uuid import uuid4 +from lib.db_session_handler import DBSessionHandler +from lib.models import JBUser, JBTurn, JBChannel, JBBot +from app.crud import ( + create_user, + create_turn, + get_user_by_number, + get_channel_by_id, + get_bot_list, + get_channels_by_identifier, + update_bot, + update_channel, + update_channel_by_bot_id, +) + +class AsyncContextManagerMock: + def __init__(self, session_mock): + self.session_mock = session_mock + + async def __aenter__(self): + return self.session_mock + + async def __aexit__(self, exc_type, exc_val, exc_tb): + pass + +class AsyncBeginMock: + async def __aenter__(self): + pass + + async def __aexit__(self, exc_type, exc_val, exc_tb): + pass + +@pytest.mark.asyncio +async def test_create_user_success(): + channel_id = "channel123" + phone_number = "1234567890" + first_name = "John" + last_name = "Doe" + + mock_session = mock.Mock() + mock_session.commit = mock.AsyncMock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + user = await create_user(channel_id, phone_number, first_name, last_name) + + assert user is not None + assert user.first_name == first_name + assert user.last_name == last_name + assert user.identifier == phone_number + assert user.channel_id == channel_id + assert isinstance(user.id, str) + assert len(user.id) == 36 + + mock_session.commit.assert_awaited_once() + +@pytest.mark.asyncio +async def test_create_user_db_failure(): + channel_id = "channel123" + phone_number = "1234567890" + first_name = "John" + last_name = "Doe" + + with mock.patch.object(DBSessionHandler, 'get_async_session', side_effect=Exception("Database error")): + with pytest.raises(Exception): + await create_user(channel_id, phone_number, first_name, last_name) + +@pytest.mark.asyncio +async def test_create_turn_success(): + bot_id = "test_bot_id" + channel_id = "channel123" + user_id = "test_user_id" + + mock_session = mock.Mock() + mock_session.commit = mock.AsyncMock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + turn_id = await create_turn(bot_id, channel_id, user_id) + + assert turn_id is not None + assert isinstance(turn_id, str) + assert len(turn_id) == 36 + + mock_session.commit.assert_awaited_once() + +@pytest.mark.asyncio +async def test_create_turn_db_failure(): + + bot_id = "test_bot_id" + channel_id = "channel123" + user_id = "test_user_id" + + with mock.patch.object(DBSessionHandler, 'get_async_session', side_effect=Exception("Database error")): + with pytest.raises(Exception): + await create_turn(bot_id, channel_id, user_id) + +@pytest.mark.asyncio +async def test_get_user_by_number_success(): + phone_number = "1234567890" + channel_id = "channel123" + + mock_user = JBUser( + id=str(uuid4()), + channel_id=channel_id, + first_name="John", + last_name="Doe", + identifier=phone_number + ) + + mock_session = mock.Mock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + + mock_execute_result = mock.Mock() + mock_execute_result.scalars.return_value.first.return_value = mock_user + + mock_session.execute = mock.AsyncMock(return_value=mock_execute_result) + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + + result = await get_user_by_number(phone_number, channel_id) + + assert result.id == mock_user.id + assert result.channel_id == mock_user.channel_id + assert result.first_name == mock_user.first_name + assert result.last_name == mock_user.last_name + assert result.identifier == mock_user.identifier + mock_session.execute.assert_awaited_once() + +@pytest.mark.asyncio +async def test_get_user_by_number_failure(): + phone_number = "1234567890" + channel_id = "channel123" + + with mock.patch.object(DBSessionHandler, 'get_async_session', side_effect=Exception("Database error")): + with pytest.raises(Exception): + await get_user_by_number(phone_number, channel_id) + +@pytest.mark.asyncio +async def test_get_channel_by_id_success(): + channel_id = "channel123" + + mock_channel = JBChannel( + id = channel_id, + bot_id = "test_bot_id", + status = "active", + name = "telegram", + type = "telegram", + key = "mfvghsikzhfcdfhjsrghehssliakzjfhsk" + ) + + mock_session = mock.Mock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + + mock_execute_result = mock.Mock() + mock_execute_result.scalars.return_value.first.return_value = mock_channel + + mock_session.execute = mock.AsyncMock(return_value=mock_execute_result) + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + + result = await get_channel_by_id(channel_id) + + assert result.id == mock_channel.id + assert result.bot_id == mock_channel.bot_id + assert result.status == mock_channel.status + assert result.name == mock_channel.name + assert result.type == mock_channel.type + assert result.key == mock_channel.key + + mock_session.execute.assert_awaited_once() + +@pytest.mark.asyncio +async def test_get_channel_by_id_failure(): + channel_id = "channel123" + + with mock.patch.object(DBSessionHandler, 'get_async_session', side_effect=Exception("Database error")): + with pytest.raises(Exception): + await get_channel_by_id(channel_id) + +@pytest.mark.asyncio +async def test_get_bot_list_success(): + mock_bot1 = JBBot(id=1, name="Bot1", status="active") + mock_bot2 = JBBot(id=2, name="Bot2", status="active") + + mock_session = mock.Mock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + mock_execute_result = mock.Mock() + + mock_scalars = mock.Mock() + mock_scalars.unique.return_value = mock_scalars + mock_scalars.all.return_value = [mock_bot1, mock_bot2] + + mock_execute_result.scalars.return_value = mock_scalars + mock_session.execute = mock.AsyncMock(return_value=mock_execute_result) + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + bot_list = await get_bot_list() + assert bot_list == [mock_bot1, mock_bot2] + mock_session.execute.assert_awaited_once() + + +@pytest.mark.asyncio +async def test_get_bot_list_no_bots_found(): + + mock_session = mock.Mock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + mock_execute_result = mock.Mock() + + mock_scalars = mock.Mock() + mock_scalars.unique.return_value = mock_scalars + mock_scalars.all.return_value = [] + + mock_execute_result.scalars.return_value = mock_scalars + mock_session.execute = mock.AsyncMock(return_value=mock_execute_result) + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + bot_list = await get_bot_list() + assert bot_list == [] + mock_session.execute.assert_awaited_once() + +@pytest.mark.asyncio +async def test_get_channels_by_identifier_success(): + identifier = "1234567890" + channel_type = "telegram" + + mock_channel1 = JBChannel(app_id="1234567890", type="telegram", status="inactive") + mock_channel2 = JBChannel(app_id="1234567890", type="telegram", status="active") + + mock_session = mock.Mock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + mock_execute_result = mock.Mock() + + mock_scalars = mock.Mock() + mock_scalars.unique.return_value = mock_scalars + mock_scalars.all.return_value = [mock_channel1, mock_channel2] + + mock_execute_result.scalars.return_value = mock_scalars + mock_session.execute = mock.AsyncMock(return_value=mock_execute_result) + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + channels_list = await get_channels_by_identifier(identifier, channel_type) + assert channels_list == [mock_channel1, mock_channel2] + mock_session.execute.assert_awaited_once() + +@pytest.mark.asyncio +async def test_get_channels_by_identifier_no_channels_found(): + + identifier = "1234567890" + channel_type = "telegram" + + mock_session = mock.Mock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + mock_execute_result = mock.Mock() + + mock_scalars = mock.Mock() + mock_scalars.unique.return_value = mock_scalars + mock_scalars.all.return_value = [] + + mock_execute_result.scalars.return_value = mock_scalars + mock_session.execute = mock.AsyncMock(return_value=mock_execute_result) + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + channels_list = await get_channels_by_identifier(identifier, channel_type) + assert channels_list == [] + mock_session.execute.assert_awaited_once() + +@pytest.mark.asyncio +async def test_update_bot_success(): + bot_id = "test_bot_id" + data = {"status":"active"} + + mock_session = mock.Mock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + mock_session.commit = mock.AsyncMock() + + mock_execute = mock.AsyncMock() + mock_execute.return_value.rowcount = 1 + mock_session.execute = mock_execute + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + + result = await update_bot(bot_id, data) + assert result is not None + assert result == bot_id + + mock_session.execute.assert_awaited_once() + mock_session.commit.assert_awaited_once() + +@pytest.mark.asyncio +async def test_update_bot_no_bot_found(): + bot_id = None + data = {"status":"active"} + + mock_session = mock.Mock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + mock_session.commit = mock.AsyncMock() + + mock_execute = mock.AsyncMock() + mock_execute.return_value.rowcount = 0 + mock_session.execute = mock_execute + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + result = await update_bot(bot_id, data) + assert result is None + + mock_session.execute.assert_awaited_once() + mock_session.commit.assert_awaited_once() + +@pytest.mark.asyncio +async def test_update_bot_error(): + + bot_id = "test_bot_id" + data = {"status":"deleted"} + + mock_session = mock.Mock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + mock_session.commit = mock.AsyncMock() + + mock_execute = mock.AsyncMock(side_effect=Exception("Database error")) + mock_session.execute = mock_execute + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + with pytest.raises(Exception, match="Database error"): + await update_bot(bot_id, data) + + mock_session.execute.assert_awaited_once() + mock_session.commit.assert_not_awaited() + +@pytest.mark.asyncio +async def test_update_channel_success(): + channel_id = "test_channel_id" + data = {"status": "active", "key": "ahjbdbhsbdrhiiuciuhrqtnjuifh"} + + mock_session = mock.Mock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + mock_session.commit = mock.AsyncMock() + + mock_execute = mock.AsyncMock() + mock_execute.return_value.rowcount = 1 + mock_session.execute = mock_execute + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + result = await update_channel(channel_id, data) + assert result is not None + assert result == channel_id + + mock_session.execute.assert_awaited_once() + mock_session.commit.assert_awaited_once() + +@pytest.mark.asyncio +async def test_update_channel_no_channel_found(): + channel_id = None + data = {"status": "active", "key": "ahjbdbhsbdrhiiuciuhrqtnjuifh"} + + mock_session = mock.Mock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + mock_session.commit = mock.AsyncMock() + + mock_execute = mock.AsyncMock() + mock_execute.return_value.rowcount = 0 + mock_session.execute = mock_execute + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + result = await update_channel(channel_id, data) + assert result is None + + mock_session.execute.assert_awaited_once() + mock_session.commit.assert_awaited_once() + +@pytest.mark.asyncio +async def test_update_channel_error(): + channel_id = "test_channel_id" + data = {"status": "active", "key": "ahjbdbhsbdrhiiuciuhrqtnjuifh"} + + mock_session = mock.Mock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + mock_session.commit = mock.AsyncMock() + + mock_execute = mock.AsyncMock(side_effect=Exception("Database error")) + mock_session.execute = mock_execute + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + with pytest.raises(Exception, match="Database error"): + await update_channel(channel_id, data) + + mock_session.execute.assert_awaited_once() + mock_session.commit.assert_not_awaited() + +@pytest.mark.asyncio +async def test_update_channel_by_bot_id_success(): + bot_id = "test_bot_id" + data = {"status": "deleted"} + + mock_session = mock.Mock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + mock_session.commit = mock.AsyncMock() + + mock_execute = mock.AsyncMock() + mock_execute.return_value.rowcount = 1 + mock_session.execute = mock_execute + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + result = await update_channel_by_bot_id(bot_id, data) + assert result is not None + assert result == bot_id + + mock_session.execute.assert_awaited_once() + mock_session.commit.assert_awaited_once() + +@pytest.mark.asyncio +async def test_update_channel_by_bot_id_no_channel_found(): + bot_id = None + data = {"status": "deleted"} + + mock_session = mock.Mock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + mock_session.commit = mock.AsyncMock() + + mock_execute = mock.AsyncMock() + mock_execute.return_value.rowcount = 0 + mock_session.execute = mock_execute + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + result = await update_channel_by_bot_id(bot_id, data) + assert result is None + + mock_session.execute.assert_awaited_once() + mock_session.commit.assert_awaited_once() + +@pytest.mark.asyncio +async def test_update_channel_by_bot_id_error(): + bot_id = "test_bot_id" + data = {"status": "deleted"} + + mock_session = mock.Mock() + mock_session.begin = mock.Mock(return_value=AsyncBeginMock()) + mock_session.commit = mock.AsyncMock() + + mock_execute = mock.AsyncMock(side_effect=Exception("Database error")) + mock_session.execute = mock_execute + + with mock.patch.object(DBSessionHandler, 'get_async_session', return_value=AsyncContextManagerMock(mock_session)): + with pytest.raises(Exception, match="Database error"): + await update_channel_by_bot_id(bot_id, data) + + mock_session.execute.assert_awaited_once() + mock_session.commit.assert_not_awaited() \ No newline at end of file