Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chatbot base class tests #186

Merged
merged 2 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading