Skip to content

Commit

Permalink
Chatbot base class tests (#186)
Browse files Browse the repository at this point in the history
* Add test coverage for Chatbot init

* Remove Python 3.10-3.11 tests needing klat-connector support

---------

Co-authored-by: Daniel McKnight <daniel@neon.ai>
  • Loading branch information
NeonDaniel and NeonDaniel authored Dec 14, 2023
1 parent b0eaf60 commit d0e79bc
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 11 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ jobs:
unit_tests:
strategy:
matrix:
python-version: [ 3.7, 3.8, 3.9, '3.10', '3.11']
python-version: [ 3.7, 3.8, 3.9]
# TODO: 3.10 and 3.11 need support in klat-connector
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
Expand Down
21 changes: 16 additions & 5 deletions chatbot_core/v2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,18 @@ def _on_mentioned_user_message(self, body: dict):
"""
MQ handler for requesting message for current bot
"""
if body.get('cid', None) in list(self.current_conversations) and not body.get('omit_reply', False):
self.handle_incoming_shout(body)
else:
self.log.warning(f'Skipping processing of mentioned user message with data: {body} '
f'as it is not in current conversations')
if body.get('omit_reply'):
self.log.debug(f"Explicitly requested no response: messageID="
f"{body.get('messageID')}")
return
if body.get('cid') not in list(self.current_conversations):
self.log.info(f"Ignoring message "
f"(messageID={body.get('messageID')}) outside of "
f"current conversations "
f"({self.current_conversations}")
self.log.debug(f"{body}")
return
self.handle_incoming_shout(body)

@create_mq_callback()
def _on_user_message(self, body: dict):
Expand Down Expand Up @@ -432,3 +439,7 @@ def _pause_responses(self, duration: int = 5):

def pre_run(self, **kwargs):
self._setup_listeners()

def shutdown(self):
self.shout_thread.cancel()
self.shout_thread.join()
18 changes: 18 additions & 0 deletions tests/units/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System
#
# Copyright 2008-2021 Neongecko.com Inc. | All Rights Reserved
#
# Notice of License - Duplicating this Notice of License near the start of any file containing
# a derivative of this software is a condition of license for this software.
# Friendly Licensing:
# No charge, open source royalty free use of the Neon AI software source and object is offered for
# educational users, noncommercial enthusiasts, Public Benefit Corporations (and LLCs) and
# Social Purpose Corporations (and LLCs). Developers can contact developers@neon.ai
# For commercial licensing, distribution of derivative works or redistribution please contact licenses@neon.ai
# Distributed on an "AS IS” basis without warranties or conditions of any kind, either express or implied.
# Trademarks of Neongecko: Neon AI(TM), Neon Assist (TM), Neon Communicator(TM), Klat(TM)
# Authors: Guy Daniels, Daniel McKnight, Regina Bloomstine, Elon Gasper, Richard Leeds
#
# Specialized conversational reconveyance options from Conversation Processing Intelligence Corp.
# US Patents 2008-2021: US7424516, US20140161250, US20140177813, US8638908, US8068604, US8553852, US10530923, US10530924
# China Patent: CN102017585 - Europe Patent: EU2156652 - Patents Pending
87 changes: 87 additions & 0 deletions tests/units/mocks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System
#
# Copyright 2008-2021 Neongecko.com Inc. | All Rights Reserved
#
# Notice of License - Duplicating this Notice of License near the start of any file containing
# a derivative of this software is a condition of license for this software.
# Friendly Licensing:
# No charge, open source royalty free use of the Neon AI software source and object is offered for
# educational users, noncommercial enthusiasts, Public Benefit Corporations (and LLCs) and
# Social Purpose Corporations (and LLCs). Developers can contact developers@neon.ai
# For commercial licensing, distribution of derivative works or redistribution please contact licenses@neon.ai
# Distributed on an "AS IS” basis without warranties or conditions of any kind, either express or implied.
# Trademarks of Neongecko: Neon AI(TM), Neon Assist (TM), Neon Communicator(TM), Klat(TM)
# Authors: Guy Daniels, Daniel McKnight, Regina Bloomstine, Elon Gasper, Richard Leeds
#
# Specialized conversational reconveyance options from Conversation Processing Intelligence Corp.
# US Patents 2008-2021: US7424516, US20140161250, US20140177813, US8638908, US8068604, US8553852, US10530923, US10530924
# China Patent: CN102017585 - Europe Patent: EU2156652 - Patents Pending
from typing import Optional
from unittest.mock import MagicMock
from chatbot_core.chatbot_abc import ChatBotABC


class MockMQ(MagicMock):
def __init__(self, config, service_name, vhost):
self.service_name = service_name
self.config = config
self.vhost = vhost
self.current_conversations = set()
self.is_running = True
self._service_id = "test_id"

self.vhost_prefix = None
self.testing_envs = set()


class TestBot(ChatBotABC):
def on_vote(self, prompt_id: str, selected: str, voter: str):
pass

def on_discussion(self, user: str, shout: str):
pass

def on_proposed_response(self):
pass

def on_selection(self, prompt: str, user: str, response: str):
pass

def on_ready_for_next(self, user: str):
pass

def at_chatbot(self, user: str, shout: str, timestamp: str) -> str:
pass

def ask_proctor(self, prompt: str, user: str, cid: str, dom: str):
pass

def ask_chatbot(self, user: str, shout: str, timestamp: str) -> str:
pass

def ask_history(self, user: str, shout: str, dom: str, cid: str) -> str:
pass

def ask_appraiser(self, options: dict) -> str:
pass

def ask_discusser(self, options: dict) -> str:
pass

def _send_first_prompt(self):
pass

def handle_shout(self, *args, **kwargs):
pass

def vote_response(self, response_user: str, cid: Optional[str] = None):
pass

def _handle_next_shout(self):
pass

def _pause_responses(self, duration: int = 5):
pass

def parse_init(self, *args, **kwargs) -> tuple:
pass
100 changes: 95 additions & 5 deletions tests/units/test_base_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,115 @@

import unittest

from unittest.mock import patch
from ovos_utils.log import LOG

from .mocks import MockMQ


class ChatBotV1Tests(unittest.TestCase):
from chatbot_core.v1 import ChatBot
from klat_connector.mach_server import MachKlatServer
from klat_connector import start_socket
server = MachKlatServer()
socket = start_socket(addr="0.0.0.0")

@patch("chatbot_core.utils.bot_utils.clean_up_bot")
def test_init(self, clean_up):
from chatbot_core.chatbot_abc import ChatBotABC
from chatbot_core.v1 import ChatBot

domain = "test_domain"
bot_kwargs = ChatBot(socket=self.socket, domain=domain,
username="test", password="pass", on_server=False,
is_prompter=False)
self.assertIsInstance(bot_kwargs, ChatBotABC)
self.assertEqual(bot_kwargs.socket, self.socket)
self.assertEqual(bot_kwargs.start_domain, domain)
self.assertEqual(bot_kwargs.username, "test")
self.assertEqual(bot_kwargs._bot_id, "test")
self.assertEqual(bot_kwargs.password, "pass")
self.assertFalse(bot_kwargs.on_server)
self.assertFalse(bot_kwargs.is_prompter)
self.assertTrue(bot_kwargs.enable_responses)
from chatbot_core.utils.enum import BotTypes
self.assertEqual(bot_kwargs.bot_type, BotTypes.SUBMIND)
self.assertTrue(bot_kwargs.shout_thread.is_alive())

bot_kwargs.exit()
self.assertEqual(bot_kwargs.shout_queue.qsize(), 0)
clean_up.assert_called_once_with(bot_kwargs)

bot_args = ChatBot(self.socket, domain, "test", "")
self.assertIsInstance(bot_args, ChatBotABC)
self.assertEqual(bot_args.socket, self.socket)
self.assertEqual(bot_args.start_domain, domain)
self.assertEqual(bot_args.username, "test")
self.assertEqual(bot_args._bot_id, "test")
self.assertIsNone(bot_args.password)
self.assertTrue(bot_args.on_server)
self.assertFalse(bot_args.is_prompter)
self.assertTrue(bot_args.enable_responses)
from chatbot_core.utils.enum import BotTypes
self.assertEqual(bot_args.bot_type, BotTypes.SUBMIND)
self.assertTrue(bot_args.shout_thread.is_alive())

bot_args.exit()
self.assertEqual(bot_args.shout_queue.qsize(), 0)
clean_up.assert_called_with(bot_args)

# TODO


class ChatBotV2Tests(unittest.TestCase):
from chatbot_core.v2 import ChatBot
@patch("chatbot_core.v2.KlatAPIMQ", new=MockMQ)
def test_init(self):
from chatbot_core.v2 import ChatBot
config = {"test": True,
"MQ": {"mq_key": "val"},
"chatbots": {"test_bot": {"bot_config": True}}}
from chatbot_core.utils.enum import BotTypes
bot_kwargs = ChatBot(config=config, service_name="test_bot",
vhost="/test", bot_type=BotTypes.OBSERVER)
self.assertEqual(bot_kwargs.bot_type, BotTypes.OBSERVER)
self.assertEqual(bot_kwargs._bot_id, "test_bot")
self.assertEqual(bot_kwargs.config, config["MQ"])
self.assertEqual(bot_kwargs.bot_config, config["chatbots"]["test_bot"])
self.assertEqual(bot_kwargs.vhost, "/test")
self.assertTrue(bot_kwargs.shout_thread.is_alive())
bot_kwargs.shutdown()
self.assertFalse(bot_kwargs.shout_thread.is_alive())

bot_args = ChatBot(config, "args_bot", "/chat")
self.assertEqual(bot_args.bot_type, BotTypes.SUBMIND)
self.assertEqual(bot_args._bot_id, "args_bot")
self.assertEqual(bot_args.config, config["MQ"])
self.assertEqual(bot_args.bot_config, dict())
self.assertEqual(bot_args.vhost, "/chat")
self.assertTrue(bot_args.shout_thread.is_alive())
bot_args.shutdown()
self.assertFalse(bot_args.shout_thread.is_alive())
# TODO


class ChatBotABCTests(unittest.TestCase):
from chatbot_core.chatbot_abc import ChatBotABC
# TODO
def test_base_class(self):
from queue import Queue
from chatbot_core.chatbot_abc import ChatBotABC
from .mocks import TestBot
bot_id = "test"
test_config = {"config": True}
bot = TestBot(bot_id, test_config)
self.assertIsInstance(bot, ChatBotABC)
self.assertEqual(bot._bot_id, bot_id)
self.assertEqual(bot.bot_config, test_config)
self.assertIsInstance(bot.shout_queue, Queue)
self.assertEqual(bot.log, LOG)
self.assertEqual(bot.log.name, bot_id)


class NeonTests(unittest.TestCase):
from chatbot_core.neon import NeonBot
# TODO
# TODO Deprecate class


class ParlaiTests(unittest.TestCase):
Expand Down

0 comments on commit d0e79bc

Please sign in to comment.