Compare commits

...

10 Commits
master ... #139

33 changed files with 975 additions and 7 deletions

3
.gitignore vendored
View File

@ -143,4 +143,5 @@ PythonImportHelper-v2-Completion.json
deploy/
# idea
.idea/
.idea/
selenium-data/

View File

@ -1,2 +1,14 @@
# kd_discord_bot
## Test Bot
To test the bot run unittests or call ```cpl test```.
Configure test instance by creating the file ./test/ui_tests/.env and set following environment variables:
```sh
KDB_TEST_DB_PASSWORD=
KDB_TEST_NAME=
KDB_TEST_TOKEN=
KDB_TEST_DISCORD_MAIL=
KDB_TEST_DISCORD_PASSWORD=
```

View File

@ -14,29 +14,27 @@
"permission": "src/modules/permission/permission.json",
"stats": "src/modules/stats/stats.json",
"technician": "src/modules/technician/technician.json",
"ui-tests": "test/ui_tests/ui-tests.json",
"ui-tests-shared": "test/ui_tests_shared/ui-tests-shared.json",
"ui-tests-tests": "test/ui_tests_tests/ui-tests-tests.json",
"get-version": "tools/get_version/get-version.json",
"post-build": "tools/post_build/post-build.json",
"set-version": "tools/set_version/set-version.json"
},
"Scripts": {
"test": "export $(cat test/ui_tests/.env); export PYTHONPATH=$PWD/src:$PYTHONPATH; cpl run ui-tests",
"sv": "cpl set-version $ARGS",
"set-version": "cpl run set-version $ARGS; echo '';",
"gv": "cpl get-version",
"get-version": "export VERSION=$(cpl run get-version); echo $VERSION;",
"pre-build": "cpl set-version $ARGS",
"post-build": "cpl run post-build",
"pre-prod": "cpl build",
"prod": "export KDB_ENVIRONMENT=production; export KDB_NAME=KDB-Prod; cpl start;",
"pre-stage": "cpl build",
"stage": "export KDB_ENVIRONMENT=staging; export KDB_NAME=KDB-Stage; cpl start;",
"pre-dev": "cpl build",
"dev": "export KDB_ENVIRONMENT=development; export KDB_NAME=KDB-Dev; cpl start;",
"docker-build": "cpl build $ARGS; docker build -t kdb-bot/kdb-bot:$(cpl gv) .;",
"dc-up": "docker-compose up -d",
"dc-down": "docker-compose down",

View File

@ -0,0 +1 @@
# imports:

View File

@ -0,0 +1,16 @@
{
"DatabaseSettings": {
"Host": "localhost",
"User": "kd_kdb",
"Password": "",
"Database": "keksdose_bot_dev",
"Charset": "utf8mb4",
"UseUnicode": "true",
"Buffered": "true",
"AuthPlugin": "mysql_native_password"
},
"DiscordBot": {
"Token": "",
"Prefix": "!kab-e "
}
}

View File

@ -0,0 +1,54 @@
{
"TimeFormatSettings": {
"DateFormat": "%Y-%m-%d",
"TimeFormat": "%H:%M:%S",
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
},
"LoggingSettings": {
"Path": "logs/$date_now/",
"Filename": "bot.log",
"ConsoleLogLevel": "ERROR",
"FileLogLevel": "WARN"
},
"BotLoggingSettings": {
"Api": {
"Path": "logs/$date_now/",
"Filename": "api.log",
"ConsoleLogLevel": "INFO",
"FileLogLevel": "DEBUG"
},
"Command": {
"Path": "logs/$date_now/",
"Filename": "commands.log",
"ConsoleLogLevel": "INFO",
"FileLogLevel": "DEBUG"
},
"Database": {
"Path": "logs/$date_now/",
"Filename": "database.log",
"ConsoleLogLevel": "INFO",
"FileLogLevel": "DEBUG"
},
"Message": {
"Path": "logs/$date_now/",
"Filename": "message.log",
"ConsoleLogLevel": "INFO",
"FileLogLevel": "DEBUG"
}
},
"Translation": {
"DefaultLanguage": "de",
"Languages": [
"de"
]
},
"TestSettings": {
"LoginUrl": "https://discord.com/login",
"MePageUrl": "https://discord.com/channels/@me",
"CmdURL": "https://discord.com/channels/910199451145076828/911578636899987526",
"GuildId": 910199451145076828,
"BotId": 998159802393964594,
"TestUserId": 401941112010571777
}
}

View File

@ -0,0 +1,34 @@
import asyncio
from cpl_core.application import ApplicationABC
from cpl_core.application import ApplicationBuilder
from startup_test_extension import StartupTestExtension
from ui_tests.startup import Startup
from ui_tests_shared.declarations import Declarations
try:
from test_application import TestApplication
except ImportError:
from .test_application import TestApplication
def get_app() -> ApplicationABC:
app_builder = ApplicationBuilder(TestApplication) \
.use_extension(StartupTestExtension) \
.use_startup(Startup)
app = app_builder.build()
Declarations.app = app
return app
async def main():
app = get_app()
await app.run_async()
if __name__ == '__main__':
import nest_asyncio
nest_asyncio.apply()
asyncio.run(main())

View File

@ -0,0 +1,118 @@
import os
from typing import Optional
from cpl_core.application import StartupABC
from cpl_core.configuration import ConfigurationABC
from cpl_core.database import DatabaseSettings
from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.environment import ApplicationEnvironment
from cpl_discord import get_discord_collection
from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum
from dotenv import load_dotenv
from bot.startup_settings_extension import StartupSettingsExtension
from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC
from bot_core.abc.custom_file_logger_abc import CustomFileLoggerABC
from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.configuration.bot_logging_settings import BotLoggingSettings
from bot_core.logging.command_logger import CommandLogger
from bot_core.logging.database_logger import DatabaseLogger
from bot_core.logging.message_logger import MessageLogger
from bot_core.pipes.date_time_offset_pipe import DateTimeOffsetPipe
from bot_core.service.client_utils_service import ClientUtilsService
from bot_core.service.message_service import MessageService
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC
from bot_data.abc.client_repository_abc import ClientRepositoryABC
from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC
from bot_data.abc.level_repository_abc import LevelRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.statistic_repository_abc import StatisticRepositoryABC
from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC
from bot_data.abc.user_joined_voice_channel_abc import UserJoinedVoiceChannelRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.db_context import DBContext
from bot_data.service.auth_user_repository_service import AuthUserRepositoryService
from bot_data.service.auto_role_repository_service import AutoRoleRepositoryService
from bot_data.service.client_repository_service import ClientRepositoryService
from bot_data.service.known_user_repository_service import KnownUserRepositoryService
from bot_data.service.level_repository_service import LevelRepositoryService
from bot_data.service.seeder_service import SeederService
from bot_data.service.server_repository_service import ServerRepositoryService
from bot_data.service.statistic_repository_service import StatisticRepositoryService
from bot_data.service.user_joined_server_repository_service import UserJoinedServerRepositoryService
from bot_data.service.user_joined_voice_channel_service import UserJoinedVoiceChannelRepositoryService
from bot_data.service.user_repository_service import UserRepositoryService
from ui_tests.test_on_ready_event import TestOnReadyEvent
class Startup(StartupABC):
def __init__(self):
StartupABC.__init__(self)
self._config: Optional[ConfigurationABC] = None
def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironment) -> ConfigurationABC:
load_dotenv()
configuration.add_environment_variables('KDB_TEST_')
configuration.add_environment_variables('DISCORD_')
cwd = os.path.dirname(os.path.realpath(__file__))
configuration.add_json_file(f'{cwd}/config/appsettings.json', optional=False)
configuration.add_json_file(f'{cwd}/config/appsettings.{environment.host_name}.json', optional=True)
StartupSettingsExtension._configure_settings_with_sub_settings(configuration, BotLoggingSettings, lambda x: x.files, lambda x: x.key)
self._config = configuration
return configuration
def configure_services(self, services: ServiceCollectionABC, environment: ApplicationEnvironment) -> ServiceProviderABC:
services.add_logging()
services.add_singleton(CustomFileLoggerABC, CommandLogger)
services.add_singleton(CustomFileLoggerABC, DatabaseLogger)
services.add_singleton(CustomFileLoggerABC, MessageLogger)
services.add_translation()
db_settings: DatabaseSettings = self._config.get_configuration(DatabaseSettings)
db_settings_with_pw = DatabaseSettings()
pw = self._config.get_configuration('DB_PASSWORD')
db_settings_with_pw.from_dict({
"Host": db_settings.host,
"User": db_settings.user,
"Password": '' if pw is None else pw,
"Database": db_settings.database,
"Charset": db_settings.charset,
"UseUnicode": db_settings.use_unicode,
"Buffered": db_settings.buffered,
"AuthPlugin": db_settings.auth_plugin
})
services.add_db_context(DBContext, db_settings_with_pw)
services.add_discord()
dc = get_discord_collection(services)
dc.add_event(DiscordEventTypesEnum.on_ready.value, TestOnReadyEvent)
# bot_core stuff
services.add_transient(MessageServiceABC, MessageService)
services.add_transient(ClientUtilsServiceABC, ClientUtilsService)
# pipes
services.add_transient(DateTimeOffsetPipe)
# data stuff
services.add_transient(AuthUserRepositoryABC, AuthUserRepositoryService)
services.add_transient(ServerRepositoryABC, ServerRepositoryService)
services.add_transient(UserRepositoryABC, UserRepositoryService)
services.add_transient(ClientRepositoryABC, ClientRepositoryService)
services.add_transient(KnownUserRepositoryABC, KnownUserRepositoryService)
services.add_transient(UserJoinedServerRepositoryABC, UserJoinedServerRepositoryService)
services.add_transient(UserJoinedVoiceChannelRepositoryABC, UserJoinedVoiceChannelRepositoryService)
services.add_transient(AutoRoleRepositoryABC, AutoRoleRepositoryService)
services.add_transient(LevelRepositoryABC, LevelRepositoryService)
services.add_transient(StatisticRepositoryABC, StatisticRepositoryService)
services.add_transient(SeederService)
return services.build_service_provider()

View File

@ -0,0 +1,20 @@
import os
from cpl_core.application import StartupExtensionABC
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC
class StartupTestExtension(StartupExtensionABC):
def __init__(self):
StartupExtensionABC.__init__(self)
def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironmentABC):
# this shit has to be done here because we need settings in subsequent startup extensions
environment.set_working_directory(os.path.dirname(os.path.realpath(__file__)))
environment.set_working_directory('../../src/bot/')
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
pass

View File

@ -0,0 +1,64 @@
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_discord.application import DiscordBotApplicationABC
from cpl_discord.configuration import DiscordBotSettings
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslationSettings, TranslationServiceABC
from bot_core.configuration.bot_settings import BotSettings
class TestApplication(DiscordBotApplicationABC):
def __init__(self, config: ConfigurationABC, services: ServiceProviderABC):
DiscordBotApplicationABC.__init__(self, config, services)
self._config = config
self._services = services
self._bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC)
self._translation: TranslationServiceABC = services.get_service(TranslationServiceABC)
discord_bot_settings: DiscordBotSettings = config.get_configuration(DiscordBotSettings)
self._discord_settings = self._get_settings(discord_bot_settings)
def _get_settings(self, settings_from_config: DiscordBotSettings) -> DiscordBotSettings:
new_settings = DiscordBotSettings()
token = None if settings_from_config is None else settings_from_config.token
prefix = None if settings_from_config is None else settings_from_config.prefix
env_token = self._config.get_configuration('TOKEN')
env_prefix = self._config.get_configuration('PREFIX')
new_settings.from_dict({
'Token': env_token if token is None or token == '' else token,
'Prefix': ('! ' if self._is_string_invalid(env_prefix) else env_prefix) if self._is_string_invalid(prefix) else prefix
})
if new_settings.token is None or new_settings.token == '':
raise Exception('You have to configure discord token by appsettings or environment variables')
return new_settings
@staticmethod
def _is_string_invalid(x):
return x is None or x == ''
@property
def config(self) -> ConfigurationABC:
return self._config
@property
def services(self) -> ServiceProviderABC:
return self._services
async def configure(self):
self._translation.load_by_settings(self._configuration.get_configuration(TranslationSettings))
async def main(self):
if self._config.get_configuration('IS_UNITTEST'):
await self._bot.login(self._discord_settings.token)
self._bot.loop.create_task(self._bot.connect())
return
await self._bot.start_async()
async def stop_async(self):
await self._bot.close()

View File

@ -0,0 +1,47 @@
import os
import unittest
from cpl_core.configuration import ConfigurationABC
from cpl_core.console import Console, ForegroundColorEnum
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.logging import LoggerABC
from cpl_discord.events import OnReadyABC
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe
from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC
class TestOnReadyEvent(OnReadyABC):
def __init__(
self,
config: ConfigurationABC,
bot: DiscordBotServiceABC,
services: ServiceProviderABC,
client_utils: ClientUtilsServiceABC,
t: TranslatePipe
):
OnReadyABC.__init__(self)
self._config = config
self._bot = bot
self._services = services
self._client_utils = client_utils
self._t = t
async def on_ready(self):
if self._config.get_configuration('IS_UNITTEST'):
return
Console.write_line('\nStarting tests:\n')
loader = unittest.TestLoader()
path = f'{os.path.dirname(os.path.realpath(__file__))}/../'
tests = loader.discover(path, pattern='*_test_case.py')
runner = unittest.TextTestRunner()
runner.run(tests)
# for cls in CommandTestABC.__subclasses__():
# service: CommandTestABC = self._services.get_service(cls)
# await service.run(self._tests)
await self._bot.close()

View File

@ -0,0 +1,49 @@
{
"ProjectSettings": {
"Name": "ui-tests",
"Version": {
"Major": "0",
"Minor": "0",
"Micro": "0"
},
"Author": "",
"AuthorEmail": "",
"Description": "",
"LongDescription": "",
"URL": "",
"CopyrightDate": "",
"CopyrightName": "",
"LicenseName": "",
"LicenseDescription": "",
"Dependencies": [
"cpl-core>=2022.10.0.post9",
"PyNaCl==1.5.0",
"cffi==1.15.1",
"pycparser==2.21",
"selenium==4.6.1",
"webdriver-manager==3.8.5"
],
"DevDependencies": [
"cpl-cli>=2022.10.0"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "console",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "ui_tests_shared.main",
"EntryPoint": "ui-tests-shared",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}

View File

@ -0,0 +1 @@
# imports:

View File

@ -0,0 +1,15 @@
from enum import Enum
class CommandSelectorsEnum(Enum):
cmd_chat = '/html/body/div[1]/div[2]/div/div[1]/div/div[2]/div/div[1]/div/div/div[3]/div[2]/main/form/div/div[1]/div/div[3]/div/div[2]/div'
cmd_chat_input = '/html/body/div[1]/div[2]/div/div[1]/div/div[2]/div/div[1]/div/div/div[3]/div/main/form/div/div[2]/div/div[2]/div/div'
msg_input = '/html/body/div[1]/div[2]/div/div[1]/div/div[2]/div/div[1]/div/div/div[3]/div[2]/main/form/div/div[1]/div/div[3]/div/div/div'
kdb_test = "//*[contains (text(), 'Krümmelmonster-test')]"
ping = kdb_test
info = kdb_test
help = kdb_test
afk = kdb_test
purge = kdb_test

View File

@ -0,0 +1,62 @@
import time
from typing import Optional
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe
from selenium.common import NoSuchElementException, StaleElementReferenceException
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from ui_tests_shared.command_selectors_enum import CommandSelectorsEnum
from ui_tests_shared.test_case_with_app import TestCaseWithApp
from ui_tests_shared.ui import UI
class CommandTestCaseWithApp(TestCaseWithApp):
_cmd: WebElement
_bot: Optional[DiscordBotServiceABC] = None
_t: Optional[TranslatePipe] = None
@classmethod
def setUpClass(cls):
TestCaseWithApp.setUpClass()
cls._bot = cls._services.get_service(DiscordBotServiceABC)
cls._t = cls._services.get_service(TranslatePipe)
@classmethod
def send_message(cls, msg: str):
if UI.driver.current_url != cls._test_settings.cmd_url:
UI.driver.get(cls._test_settings.cmd_url)
time.sleep(2)
cmd_element_ident = (By.XPATH, CommandSelectorsEnum.cmd_chat.value)
cmd_element = UI.driver.find_element(*cmd_element_ident)
cmd_element.send_keys(msg)
time.sleep(2)
UI.driver.find_element(By.XPATH, CommandSelectorsEnum.msg_input.value).send_keys(Keys.ENTER)
@classmethod
def send_command(cls, cmd: str, selector: CommandSelectorsEnum, reload=True):
if reload or UI.driver.current_url != cls._test_settings.cmd_url:
UI.driver.get(cls._test_settings.cmd_url)
time.sleep(2)
cmd_element_ident = (By.XPATH, CommandSelectorsEnum.cmd_chat.value)
cmd_element = UI.driver.find_element(*cmd_element_ident)
cmd_element.send_keys(f'/{cmd}')
time.sleep(2)
ignored_exceptions = (NoSuchElementException, StaleElementReferenceException,)
WebDriverWait(UI.driver, 20, ignored_exceptions=ignored_exceptions).until(
expected_conditions.presence_of_element_located((
By.XPATH,
selector.value
))
).click()
time.sleep(2)
UI.driver.find_element(By.XPATH, CommandSelectorsEnum.cmd_chat_input.value).send_keys(Keys.ENTER)

View File

@ -0,0 +1 @@
# imports

View File

@ -0,0 +1,53 @@
import traceback
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
class TestSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._login_url = ''
self._me_page_url = ''
self._cmd_url = ''
self._guild_id = 0
self._bot_id = 0
self._test_user_id = 0
@property
def login_url(self) -> str:
return self._login_url
@property
def me_page_url(self) -> str:
return self._me_page_url
@property
def cmd_url(self) -> str:
return self._cmd_url
@property
def guild_id(self) -> int:
return self._guild_id
@property
def bot_id(self) -> int:
return self._bot_id
@property
def test_user_id(self) -> int:
return self._test_user_id
def from_dict(self, settings: dict):
try:
self._login_url = settings['LoginUrl']
self._me_page_url = settings['MePageUrl']
self._cmd_url = settings['CmdURL']
self._guild_id = settings['GuildId']
self._bot_id = settings['BotId']
self._test_user_id = settings['TestUserId']
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@ -0,0 +1,12 @@
from typing import Optional
from cpl_core.environment import ApplicationEnvironmentABC
from ui_tests.test_application import TestApplication
from ui_tests_shared.configuration.test_settings import TestSettings
class Declarations:
app: Optional[TestApplication] = None
env: Optional[ApplicationEnvironmentABC] = None
test_settings: Optional[TestSettings] = None

View File

@ -0,0 +1,18 @@
import asyncio
class Async:
_loop = None
@classmethod
def async_func(cls, coro):
def wrapper(*args, **kwargs):
try:
if cls._loop is None:
cls._loop = asyncio.get_event_loop()
return cls._loop.run_until_complete(coro(*args, **kwargs))
except Exception as e:
return
return wrapper

View File

@ -0,0 +1,44 @@
import asyncio
import time
import unittest
from typing import Optional
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC
from ui_tests.main import get_app
from ui_tests_shared.configuration.test_settings import TestSettings
from ui_tests_shared.declarations import Declarations
from ui_tests_shared.ui import UI
class TestCaseWithApp(unittest.TestCase):
_config: Optional[ConfigurationABC] = None
_services: Optional[ServiceProviderABC] = None
_test_settings: Optional[TestSettings] = None
_loop = None
@classmethod
def setUpClass(cls):
if Declarations.app is None:
app = get_app()
import nest_asyncio
nest_asyncio.apply()
if cls._loop is None:
cls._loop = asyncio.get_event_loop()
cls._loop.run_until_complete(app.run_async())
# asyncio.run(app.run_async())
cls._config = Declarations.app.config
cls._services = Declarations.app.services
cls._test_settings: TestSettings = cls._config.get_configuration(TestSettings)
UI.set_settings(cls._test_settings)
UI.login(cls._config.get_configuration('DISCORD_MAIL'), cls._config.get_configuration('DISCORD_PASSWORD'))
@classmethod
def tearDownClass(cls):
if cls._config.get_configuration('IS_UNITTEST'):
loop = asyncio.get_event_loop()
loop.run_until_complete(Declarations.app.stop_async())

View File

@ -0,0 +1,44 @@
{
"ProjectSettings": {
"Name": "ui-tests-shared",
"Version": {
"Major": "0",
"Minor": "0",
"Micro": "0"
},
"Author": "",
"AuthorEmail": "",
"Description": "",
"LongDescription": "",
"URL": "",
"CopyrightDate": "",
"CopyrightName": "",
"LicenseName": "",
"LicenseDescription": "",
"Dependencies": [
"cpl-core>=2022.10.0.post9"
],
"DevDependencies": [
"cpl-cli>=2022.10.0"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "console",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "ui_tests_shared.main",
"EntryPoint": "ui-tests-shared",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}

View File

@ -0,0 +1,69 @@
import time
import unittest
from typing import Optional
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC
from selenium import webdriver
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from ui_tests.main import get_app
from ui_tests_shared.configuration.test_settings import TestSettings
from ui_tests_shared.declarations import Declarations
class UI:
_test_settings: Optional[TestSettings] = None
options = webdriver.ChromeOptions()
options.add_argument("user-data-dir=selenium-data")
options.add_experimental_option('useAutomationExtension', False)
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("prefs", {
"profile.default_content_setting_values.media_stream_mic": 1,
"profile.default_content_setting_values.media_stream_camera": 1,
"profile.default_content_setting_values.geolocation": 1,
"profile.default_content_setting_values.notifications": 1
})
driver = webdriver.Chrome(options=options)
_is_logged_in = False
@classmethod
def set_settings(cls, test_settings: TestSettings):
cls._test_settings = test_settings
@classmethod
def login(cls, mail: str, password: str):
if cls._is_logged_in:
return
# use full xpath: https://stackoverflow.com/questions/71179006/how-can-selenium-python-chrome-find-web-elements-visible-in-dev-tools-but-no
cls.driver.get(cls._test_settings.login_url)
try:
WebDriverWait(cls.driver, 10).until(expected_conditions.url_matches(cls._test_settings.me_page_url))
cls._is_logged_in = True
return
except Exception as e:
WebDriverWait(cls.driver, 20).until(expected_conditions.presence_of_element_located((By.NAME, 'email')))
mail_element = cls.driver.find_element(By.NAME, 'email')
mail_element.clear()
mail_element.send_keys(mail)
mail_element.send_keys(Keys.RETURN)
pw_element = cls.driver.find_element(By.NAME, 'password')
pw_element.clear()
pw_element.send_keys(password)
pw_element.send_keys(Keys.RETURN)
time.sleep(1)
pw_element.send_keys(Keys.RETURN)
WebDriverWait(cls.driver, 20).until(expected_conditions.url_matches(cls._test_settings.me_page_url))
time.sleep(4)
cls._is_logged_in = True

View File

@ -0,0 +1 @@
# imports:

View File

@ -0,0 +1,15 @@
{
"TimeFormatSettings": {
"DateFormat": "%Y-%m-%d",
"TimeFormat": "%H:%M:%S",
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
},
"LoggingSettings": {
"Path": "logs/",
"Filename": "log_$start_time.log",
"ConsoleLogLevel": "ERROR",
"FileLogLevel": "WARN"
}
}

View File

@ -0,0 +1,48 @@
import discord
from selenium.webdriver.common.by import By
from ui_tests_shared.command_selectors_enum import CommandSelectorsEnum
from ui_tests_shared.command_test_case_with_app import CommandTestCaseWithApp
from ui_tests_shared.decorators import Async
from ui_tests_shared.ui import UI
class AFKCommandTestCase(CommandTestCaseWithApp):
@classmethod
def tearDownClass(cls):
btn = UI.driver.find_elements(By.XPATH, '/html/body/div[1]/div[2]/div/div[1]/div/div[2]/div/div[1]/div/div/div[1]/section/div[1]/div/div[1]/div[2]/button')
if len(btn) == 0:
return
btn[0].click()
@Async.async_func
async def test_error_message(self):
correct_response = self._t.transform('modules.base.afk_command_channel_missing_message')
self.assertIsNotNone(correct_response)
self.send_command('afk', CommandSelectorsEnum.afk, reload=False)
def check(m: discord.Message):
return m.author.id == self._test_settings.bot_id
response = await self._bot.wait_for('message', check=check, timeout=10)
self.assertEqual(response.content, correct_response)
@Async.async_func
async def test_move(self):
# correct_response = self._t.transform('modules.base.pong')
# self.assertIsNotNone(correct_response)
UI.driver.find_element(By.XPATH, '//*[@id="channels"]/ul/li[20]/div/div/div/a').click()
def check1(member: discord.Member, before: discord.VoiceState, after: discord.VoiceState):
return member.id == self._test_settings.test_user_id and after is not None and after.channel.id == 911578760476762153
res = await self._bot.wait_for('voice_state_update', check=check1, timeout=10)
self.send_command('afk', CommandSelectorsEnum.afk, reload=False)
def check2(member: discord.Member, before: discord.VoiceState, after: discord.VoiceState):
return member.id == self._test_settings.test_user_id and after is not None and after.channel.id == 910199452915093594
member, before, after = await self._bot.wait_for('voice_state_update', check=check2, timeout=10)
self.assertIsNotNone(after.channel)
self.assertEqual(after.channel.id, 910199452915093594)
# self.assertEqual(response.content, correct_response)

View File

@ -0,0 +1,20 @@
import discord
from ui_tests_shared.command_selectors_enum import CommandSelectorsEnum
from ui_tests_shared.command_test_case_with_app import CommandTestCaseWithApp
from ui_tests_shared.decorators import Async
class HelpCommandTestCase(CommandTestCaseWithApp):
@Async.async_func
async def test_help(self):
correct_response = 'https://git.sh-edraft.de/sh-edraft.de/kd_discord_bot/wiki/Befehle'
self.send_command('help', CommandSelectorsEnum.help)
def check(m: discord.Message):
return m.author.id == self._test_settings.bot_id
response = await self._bot.wait_for('message', check=check, timeout=10)
self.assertEqual(response.content, correct_response)
await self._bot.close()

View File

@ -0,0 +1,53 @@
from datetime import datetime
from unittest import skip
import discord
import bot
from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC
from ui_tests_shared.command_selectors_enum import CommandSelectorsEnum
from ui_tests_shared.command_test_case_with_app import CommandTestCaseWithApp
from ui_tests_shared.decorators import Async
class InfoCommandTestCase(CommandTestCaseWithApp):
def get_embed(self) -> discord.Embed:
client_utils: ClientUtilsServiceABC = self._services.get_service(ClientUtilsServiceABC)
client = client_utils.get_client(self._test_settings.bot_id, self._test_settings.guild_id)
embed = discord.Embed(
title=self._t.transform('modules.base.info.title'),
description=self._t.transform('modules.base.info.description'),
color=int('ef9d0d', 16)
)
embed.add_field(name=self._t.transform('modules.base.info.fields.version'), value=bot.__version__)
# start_time = self._config.get_configuration('Bot_StartTime')
start_time = str(datetime.now())
ontime = round((datetime.now() - datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S.%f')).total_seconds() / 3600, 2)
embed.add_field(name=self._t.transform('modules.base.info.fields.ontime'), value=f'{ontime}h')
embed.add_field(name=self._t.transform('modules.base.info.fields.sent_message_count'), value=client.sent_message_count, inline=False)
embed.add_field(name=self._t.transform('modules.base.info.fields.received_message_count'), value=client.received_message_count)
embed.add_field(name=self._t.transform('modules.base.info.fields.deleted_message_count'), value=client.deleted_message_count, inline=False)
embed.add_field(name=self._t.transform('modules.base.info.fields.received_command_count'), value=client.received_command_count)
embed.add_field(name=self._t.transform('modules.base.info.fields.moved_users_count'), value=client.moved_users_count)
from bot.module_list import ModuleList
modules = ModuleList.get_modules()
modules = modules.select(lambda x: x.__name__.replace('Module', ''))
embed.add_field(name=self._t.transform('modules.base.info.fields.modules'), value='\n'.join(modules), inline=False)
return embed
@skip('stage db not ready yet')
@Async.async_func
async def test_info(self):
correct_response = self.get_embed()
self.send_command('info', CommandSelectorsEnum.info)
def check(m: discord.Message):
return m.embeds[0] == correct_response and m.author.id == self._test_settings.bot_id
response: discord.Message = await self._bot.wait_for('message', check=check, timeout=10)
self.assertEqual(len(response.embeds), 1)
self.assertEqual(response.embeds[0], correct_response)
await self._bot.close()

View File

@ -0,0 +1,20 @@
import discord
from ui_tests_shared.command_selectors_enum import CommandSelectorsEnum
from ui_tests_shared.command_test_case_with_app import CommandTestCaseWithApp
from ui_tests_shared.decorators import Async
class PingCommandTestCase(CommandTestCaseWithApp):
@Async.async_func
async def test_ping(self):
correct_response = self._t.transform('modules.base.pong')
self.assertIsNotNone(correct_response)
self.send_command('ping', CommandSelectorsEnum.ping)
def check(m: discord.Message):
return m.author.id == self._test_settings.bot_id
response = await self._bot.wait_for('message', check=check, timeout=10)
self.assertEqual(response.content, correct_response)

View File

@ -0,0 +1,34 @@
import time
import discord
from ui_tests_shared.command_selectors_enum import CommandSelectorsEnum
from ui_tests_shared.command_test_case_with_app import CommandTestCaseWithApp
from ui_tests_shared.decorators import Async
class PurgeCommandTestCase(CommandTestCaseWithApp):
def setUp(self):
self.send_message('test nachricht 1')
self.send_message('test nachricht 2')
self.send_message('test nachricht 3')
self.send_message('test nachricht 4')
@Async.async_func
async def test_purge(self):
correct_response = self._t.transform('modules.moderator.purge_message')
self.assertIsNotNone(correct_response)
self.send_command('purge', CommandSelectorsEnum.purge)
def check(m: discord.Message):
return m.author.id == self._test_settings.bot_id
response = await self._bot.wait_for('message', check=check, timeout=10)
self.assertEqual(response.content, correct_response)
time.sleep(20)
channel = self._bot.get_channel(911578680998895687)
message_count = 0
async for _ in channel.history(limit=None):
message_count += 1
self.assertEqual(message_count, 0)

View File

@ -0,0 +1,44 @@
{
"ProjectSettings": {
"Name": "ui-tests-tests",
"Version": {
"Major": "0",
"Minor": "0",
"Micro": "0"
},
"Author": "",
"AuthorEmail": "",
"Description": "",
"LongDescription": "",
"URL": "",
"CopyrightDate": "",
"CopyrightName": "",
"LicenseName": "",
"LicenseDescription": "",
"Dependencies": [
"cpl-core>=2022.10.0.post9"
],
"DevDependencies": [
"cpl-cli>=2022.10.0"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "console",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "ui_tests__tests.main",
"EntryPoint": "ui-tests_tests",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}