From e4fe6de0f717db72be729c7b7c64a7b47910c4ed Mon Sep 17 00:00:00 2001 From: TheKrol Date: Thu, 18 Apr 2024 02:19:49 -0400 Subject: [PATCH 01/10] Update htd with what it is trying to conver to --- techsupport_bot/commands/htd.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/techsupport_bot/commands/htd.py b/techsupport_bot/commands/htd.py index 1f2849959..2d706caeb 100644 --- a/techsupport_bot/commands/htd.py +++ b/techsupport_bot/commands/htd.py @@ -305,10 +305,17 @@ async def htd_command(self, ctx: commands.Context, val_to_convert: str) -> None: try: int_list = self.convert_list_to_ints(parsed_list.copy()) except ValueError: - await auxiliary.send_deny_embed( + parsed_list_str = str(parsed_list) # Convert the list to a string to only print so many. + embed = auxiliary.prepare_deny_embed( message="Unable to convert value, are you sure it's valid?", - channel=ctx.channel, ) + if parsed_list[0].startswith("0x"): + embed.set_footer(text=f"Tried to convert {parsed_list_str[1:31]} to hex") + elif parsed_list[0].startswith("0b"): + embed.set_footer(text=f"Tried to convert {parsed_list_str[1:31]} to binary") + else: + embed.set_footer(text=f"Tried to convert {parsed_list_str[1:31]} to int") + await ctx.send(embed=embed) return # Attempt to parse the given equation and return a single integer answer From df09374ef9a93267d16bca90476815090d5d3289 Mon Sep 17 00:00:00 2001 From: TheKrol Date: Thu, 18 Apr 2024 02:21:21 -0400 Subject: [PATCH 02/10] format update --- techsupport_bot/commands/htd.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/techsupport_bot/commands/htd.py b/techsupport_bot/commands/htd.py index 2d706caeb..4ecfefa73 100644 --- a/techsupport_bot/commands/htd.py +++ b/techsupport_bot/commands/htd.py @@ -305,16 +305,24 @@ async def htd_command(self, ctx: commands.Context, val_to_convert: str) -> None: try: int_list = self.convert_list_to_ints(parsed_list.copy()) except ValueError: - parsed_list_str = str(parsed_list) # Convert the list to a string to only print so many. + parsed_list_str = str( + parsed_list + ) # Convert the list to a string to only print so many. embed = auxiliary.prepare_deny_embed( message="Unable to convert value, are you sure it's valid?", ) if parsed_list[0].startswith("0x"): - embed.set_footer(text=f"Tried to convert {parsed_list_str[1:31]} to hex") + embed.set_footer( + text=f"Tried to convert {parsed_list_str[1:31]} to hex" + ) elif parsed_list[0].startswith("0b"): - embed.set_footer(text=f"Tried to convert {parsed_list_str[1:31]} to binary") + embed.set_footer( + text=f"Tried to convert {parsed_list_str[1:31]} to binary" + ) else: - embed.set_footer(text=f"Tried to convert {parsed_list_str[1:31]} to int") + embed.set_footer( + text=f"Tried to convert {parsed_list_str[1:31]} to int" + ) await ctx.send(embed=embed) return From 935de3edf90f03083de14da3aad0401aa9456144 Mon Sep 17 00:00:00 2001 From: TheKrol Date: Thu, 18 Apr 2024 03:01:04 -0400 Subject: [PATCH 03/10] Fix testing (hopefully I did it right) --- .../commands_tests/test_extensions_htd.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/techsupport_bot/tests/commands_tests/test_extensions_htd.py b/techsupport_bot/tests/commands_tests/test_extensions_htd.py index 2f9dda0c6..974d193ab 100644 --- a/techsupport_bot/tests/commands_tests/test_extensions_htd.py +++ b/techsupport_bot/tests/commands_tests/test_extensions_htd.py @@ -5,6 +5,7 @@ import importlib from unittest.mock import AsyncMock, MagicMock, call, patch +from discord.ext import commands import discord import pytest @@ -648,16 +649,24 @@ async def test_convertints_error(self): hextodec.convert_list_to_ints = MagicMock(side_effect=ValueError) hextodec.perform_op_on_list = MagicMock() hextodec.custom_embed_generation = MagicMock() - auxiliary.send_deny_embed = AsyncMock() - discord_env.context.send = AsyncMock() + auxiliary.prepare_deny_embed = MagicMock() + + # Create a MockContext with the required attributes + mock_context = MagicMock(spec=commands.Context) + mock_context.send = AsyncMock() # Mock the send method + + # Set the mock context for the test + discord_env.context = mock_context # Step 2 - Call the function await hextodec.htd_command(discord_env.context, "test") # Step 3 - Assert that everything works - auxiliary.send_deny_embed.assert_called_once_with( - message="Unable to convert value, are you sure it's valid?", - channel=discord_env.context.channel, + auxiliary.prepare_deny_embed.assert_called_once_with( + message="Unable to convert value, are you sure it's valid?" + ) + discord_env.context.send.assert_called_once_with( + embed=auxiliary.prepare_deny_embed.return_value ) # Step 4 - Cleanup From 4c97cadab3019d8da719808992543fbc16211242 Mon Sep 17 00:00:00 2001 From: TheKrol Date: Thu, 18 Apr 2024 03:02:07 -0400 Subject: [PATCH 04/10] Format update --- techsupport_bot/tests/commands_tests/test_extensions_htd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/techsupport_bot/tests/commands_tests/test_extensions_htd.py b/techsupport_bot/tests/commands_tests/test_extensions_htd.py index 974d193ab..09ce66a54 100644 --- a/techsupport_bot/tests/commands_tests/test_extensions_htd.py +++ b/techsupport_bot/tests/commands_tests/test_extensions_htd.py @@ -5,12 +5,12 @@ import importlib from unittest.mock import AsyncMock, MagicMock, call, patch -from discord.ext import commands import discord import pytest from commands import htd from core import auxiliary +from discord.ext import commands from tests import config_for_tests, helpers From d432447c38aab16ad07aed347af340d35690fa3e Mon Sep 17 00:00:00 2001 From: TheKrol Date: Sun, 4 May 2025 07:32:42 -0400 Subject: [PATCH 05/10] Started over and cleaned it up a lot --- techsupport_bot/commands/htd.py | 514 ++++++++++++++++---------------- 1 file changed, 263 insertions(+), 251 deletions(-) diff --git a/techsupport_bot/commands/htd.py b/techsupport_bot/commands/htd.py index 4ecfefa73..5f1a56979 100644 --- a/techsupport_bot/commands/htd.py +++ b/techsupport_bot/commands/htd.py @@ -2,333 +2,345 @@ Convert a value or evaluate a mathematical expression to decimal, hex, binary, and ascii encoding """ -from typing import Self +from __future__ import annotations + +from typing import TYPE_CHECKING, Self import discord from core import auxiliary, cogs from discord.ext import commands +if TYPE_CHECKING: + import bot -async def setup(bot): - """ - boilerplate to load htd class + +async def setup(bot: bot.TechSupportBot) -> None: + """Loading the HTD plugin into the bot + + Args: + bot (bot.TechSupportBot): The bot object to register the cogs to """ await bot.add_cog(Htd(bot=bot)) -class Htd(cogs.BaseCog): - """ - perform calculations on cross-base numbers and convert between them +def convert_value_to_integer(value_to_convert: str) -> int: + """Converts a given value as hex, binary, or decimal into an integer type + + Args: + value_to_convert (str): The given value to convert + + Returns: + int: The value represented as an integer """ - OPERATORS = ["+", "-", "*", "/"] + if value_to_convert.replace("-", "").startswith("0x"): + # input detected as hex + num_base = 16 + elif value_to_convert.replace("-", "").startswith("0b"): + # input detected as binary + num_base = 2 + else: + # assume the input is detected as an int + num_base = 10 + # special handling is needed for floats + if "." in value_to_convert: + return int(float(value_to_convert)) - def split_nicely(self, str_to_split: str) -> list: - """Takes an input string of an equation, and - returns a list with numbers and operators in separate parts + return int(value_to_convert, num_base) - Args: - str_to_split (str): The equation to parse - Returns: - list: A list containing strings of the operators and numbers - """ +def perform_op_on_list(equation_list: list) -> int: + """This will compute an equation if passed as a list + This does not use eval() + This expected a list of integers and OPERATORS only + + Args: + equation_list (list): The equation in a list form - parsed_list: list = [] - val_buffer = "" - - for character in str_to_split: - if character == "-" and not val_buffer: - # If the buffer is empty, we have just found either a number or operator - # In this case, if the next character is a '-', it must be a negative - # in a properly formed equation - val_buffer += character - elif character in self.OPERATORS: - # If the character is an operator, we add the finished character to the list - # And then we add the operator to the list - parsed_list.append(val_buffer) - parsed_list.append(character) - val_buffer = "" + Raises: + ValueError: If the operator is not valid, this is raised + + Returns: + int: The integer value of the computed equation + """ + + running_value = equation_list[0] + current_operator = "" + for index, value in enumerate(equation_list): + if index == 0: + continue + if index % 2 == 1: + # Odd position must be an operator + current_operator = value + else: + # Even position, must be a number + if current_operator == "+": + running_value = running_value + value + elif current_operator == "-": + running_value = running_value - value + elif current_operator == "*": + running_value = running_value * value + elif current_operator == "/": + running_value = int(running_value / value) + elif current_operator == "%": + running_value = running_value % value else: - # Otherwise, we add the character to the buffer, as it must be part of a number - val_buffer += character + raise ValueError("Invalid Equation") + return running_value - # At the end of the string, whatever we have left must be the last number in the equation - # So, we must append it - parsed_list.append(val_buffer) - return parsed_list +def clean_input(user_input: str) -> str: + """A method to clean up input to be better processed by later functions + This replaces "#" with "0x" to recognized "#" as hex + It also removes quotes and spaces - def convert_value_to_integer(self, value_to_convert: str) -> int: - """Converts a given value as hex, binary, or decimal into an integer type + Args: + user_input (str): The raw input from the user - Args: - value_to_convert (str): The given value to convert + Returns: + str: The cleaned up string + """ + user_input = user_input.replace("#", "0x") + user_input = user_input.replace("'", "") + user_input = user_input.replace('"', "") + user_input = user_input.replace(" ", "") + return user_input - Returns: - int: The value represented as an integer - """ - if value_to_convert.replace("-", "").startswith("0x"): - # input detected as hex - num_base = 16 - elif value_to_convert.replace("-", "").startswith("0b"): - # input detected as binary - num_base = 2 - else: - # assume the input is detected as an int - num_base = 10 - # special handling is needed for floats - if "." in value_to_convert: - return int(float(value_to_convert)) +def convert_list_to_ints(raw_list: list) -> list: + """This converts the values in an equation list into ints - return int(value_to_convert, num_base) + Args: + raw_list (list): An equation formatted as a list - def perform_op_on_list(self, equation_list: list) -> int: - """This will compute an equation if passed as a list - This does not use eval() - This expected a list of integers and OPERATORS only + Returns: + list: The same list you passed in, but with only ints + """ + for index, value in enumerate(raw_list): + if index % 2 == 1: + continue + try: + # Attempt to convert each value + raw_list[index] = convert_value_to_integer(value) + except ValueError: + # If conversion fails, get the base from the value + if value.startswith("0x"): + base = "hexadecimal" + elif value.startswith("0b"): + base = "binary" + else: + base = "decimal" - Args: - equation_list (list): The equation in a list form + raise ValueError(f"Failed to convert `{value}` from {base} base.") - Raises: - ValueError: If the operator is not valid, this is raised + return raw_list - Returns: - int: The integer value of the computed equation - """ - running_value = equation_list[0] - current_operator = "" - for index, value in enumerate(equation_list): - if index == 0: - continue - if index % 2 == 1: - # Odd position must be an operator - current_operator = value - else: - # Even position, must be a number - if current_operator == "+": - running_value = running_value + value - elif current_operator == "-": - running_value = running_value - value - elif current_operator == "*": - running_value = running_value * value - elif current_operator == "/": - running_value = int(running_value / value) - else: - raise ValueError("Invalid Equation") - return running_value +def integer_to_hexadecimal(integer: int) -> str: + """Takes an integer in and returns a string representation in hex + This will return in the format of "0x05" - @commands.command( - name="htd", - brief="Convert values to different bases", - description=( - "Takes a value and returns the value in different bases and" - " encodings (binary, hex, base 10, and ascii)" - ), - usage=( - "`[value]`\nAccepts numbers in the following formats:\n0x" - " (hex)\n0b (binary) \nNo prefix (assumed decimal)" - ), - ) - async def htd(self, ctx: commands.Context, *, val_to_convert: str): - """This discord command for .htd + Args: + integer (int): The integer to convert to hex - Args: - ctx (commands.Context): The context in which the command was called at - val_to_convert (str): The raw conversion request - """ - await self.htd_command(ctx, val_to_convert) + Returns: + str: The hexadecimal representation of the input + """ + raw_hex = hex(integer) + compare_value = 1 + if raw_hex.startswith("-"): + compare_value = 0 - def clean_input(self: Self, user_input: str) -> str: - """A method to clean up input to be better processed by later functions - This replaces "#" with "0x" to recognized "#" as hex - It also removes quotes and spaces + if len(raw_hex) % 2 == compare_value: + raw_hex = raw_hex.replace("0x", "0x0") - Args: - user_input (str): The raw input from the user + return raw_hex - Returns: - str: The cleaned up string - """ - user_input = user_input.replace("#", "0x") - user_input = user_input.replace("'", "") - user_input = user_input.replace('"', "") - user_input = user_input.replace(" ", "") - return user_input - def convert_list_to_ints(self, raw_list: list) -> list: - """This converts the values in an equation list into ints +def integer_to_binary(integer: int) -> str: + """Takes an integer in and returns a string representation in binary - Args: - raw_list (list): An equation formatted as a list + Args: + integer (int): The integer to convert to binary - Returns: - list: The same list you passed in, but with only ints - """ - for index, value in enumerate(raw_list): - if index % 2 == 1: - continue - raw_list[index] = self.convert_value_to_integer(value) - return raw_list + Returns: + str: The binary representation of the input + """ + return bin(integer) - def integer_to_hexadecimal(self, integer: int) -> str: - """Takes an integer in and returns a string representation in hex - This will return in the format of "0x05" - Args: - integer (int): The integer to convert to hex +def integer_to_ascii(integer: int) -> str: + """Takes an integer in and returns a string representation in ascii - Returns: - str: The hexadecimal representation of the input - """ - raw_hex = hex(integer) - compare_value = 1 - if raw_hex.startswith("-"): - compare_value = 0 + Args: + integer (int): The integer to convert to ascii - if len(raw_hex) % 2 == compare_value: - raw_hex = raw_hex.replace("0x", "0x0") + Returns: + str: The ascii representation of the input + """ + raw_hex = hex(integer) + raw_hex = raw_hex.replace("0x", "") + raw_hex = raw_hex.replace("-", "") + hex_bytes = str(bytes.fromhex(raw_hex).decode("unicode_escape")) + return hex_bytes - return raw_hex - def integer_to_binary(self, integer: int) -> str: - """Takes an integer in and returns a string representation in binary +def format_embed_field(data: str) -> str: + """Turns an input string into a formatted string ready to be added to the embed + The length of the field cannot be more than 1024, so if the length is greater than + 1024, we replace the last 3 characters with full stops - Args: - integer (int): The integer to convert to binary + Args: + data (str): The raw input to format - Returns: - str: The binary representation of the input - """ - return bin(integer) + Returns: + str: The string output, either left alone or cropped + """ + if len(data) <= 1024: + return data + return data[:1021] + "..." - def integer_to_ascii(self, integer: int) -> str: - """Takes an integer in and returns a string representation in ascii - Args: - integer (int): The integer to convert to ascii +def custom_embed_generation(raw_input: str, val_to_convert: int) -> discord.Embed: + """Generates, but does not send, a formatted embed - Returns: - str: The ascii representation of the input - """ - raw_hex = hex(integer) - raw_hex = raw_hex.replace("0x", "") - raw_hex = raw_hex.replace("-", "") - hex_bytes = str(bytes.fromhex(raw_hex).decode("unicode_escape")) - return hex_bytes + Args: + raw_input (str): The raw input from the user, to display in the embed + val_to_convert (int): The value to convert from - def format_embed_field(self, data: str) -> str: - """Turns an input string into a formatted string ready to be added to the embed - The length of the field cannot be more than 1024, so if the length is greater than - 1024, we replace the last 3 characters with full stops + Returns: + discord.Embed: The formatted embed + """ + embed = auxiliary.generate_basic_embed( + title="Your conversion results", + description=f"Converting `{raw_input}`", + color=discord.Color.green(), + ) + # Start by adding decimal + embed.add_field( + name="Decimal:", + value=format_embed_field(str(val_to_convert)), + inline=False, + ) - Args: - data (str): The raw input to format + # Next, add hex + embed.add_field( + name="Hexadecimal:", + value=format_embed_field(integer_to_hexadecimal(val_to_convert)), + inline=False, + ) - Returns: - str: The string output, either left alone or cropped - """ - if len(data) <= 1024: - return data - return data[:1021] + "..." + # Next, add binary + embed.add_field( + name="Binary:", + value=format_embed_field(integer_to_binary(val_to_convert)), + inline=False, + ) - def custom_embed_generation( - self, raw_input: str, val_to_convert: int - ) -> discord.Embed: - """Generates, but does not send, a formatted embed + try: + ascii_value = format_embed_field(integer_to_ascii(val_to_convert)) + except ValueError: + ascii_value = "No ascii representation could be made" - Args: - raw_input (str): The raw input from the user, to display in the embed - val_to_convert (int): The value to convert from + # Finally, add ascii encoding - Returns: - discord.Embed: The formatted embed - """ - embed = auxiliary.generate_basic_embed( - title="Your conversion results", - description=f"Converting `{raw_input}`", - color=discord.Color.green(), - ) - # Start by adding decimal - embed.add_field( - name="Decimal:", - value=self.format_embed_field(str(val_to_convert)), - inline=False, - ) - - # Next, add hex - embed.add_field( - name="Hexadecimal:", - value=self.format_embed_field(self.integer_to_hexadecimal(val_to_convert)), - inline=False, - ) - - # Next, add binary - embed.add_field( - name="Binary:", - value=self.format_embed_field(self.integer_to_binary(val_to_convert)), - inline=False, - ) + embed.add_field( + name="Ascii encoding:", + value=ascii_value, + inline=False, + ) + + return embed - try: - ascii_value = self.format_embed_field(self.integer_to_ascii(val_to_convert)) - except ValueError: - ascii_value = "No ascii representation could be made" - # Finally, add ascii encoding +def split_nicely(str_to_split: str) -> list: + """Takes an input string of an equation, and + returns a list with numbers and operators in separate parts - embed.add_field( - name="Ascii encoding:", - value=ascii_value, - inline=False, - ) + Args: + str_to_split (str): The equation to parse - return embed + Returns: + list: A list containing strings of the operators and numbers + """ + + OPERATORS = ["+", "-", "*", "/", "%"] + + parsed_list: list = [] + val_buffer = "" + + for character in str_to_split: + if character == "-" and not val_buffer: + # If the buffer is empty, we have just found either a number or operator + # In this case, if the next character is a '-', it must be a negative + # in a properly formed equation + val_buffer += character + elif character in OPERATORS: + # If the character is an operator, we add the finished character to the list + # And then we add the operator to the list + parsed_list.append(val_buffer) + parsed_list.append(character) + val_buffer = "" + else: + # Otherwise, we add the character to the buffer, as it must be part of a number + val_buffer += character - async def htd_command(self, ctx: commands.Context, val_to_convert: str) -> None: + # At the end of the string, whatever we have left must be the last number in the equation + # So, we must append it + parsed_list.append(val_buffer) + + return parsed_list + + +class Htd(cogs.BaseCog): + """ + perform calculations on cross-base numbers and convert between them + """ + + @commands.command( + name="htd", + brief="Convert values to different bases", + description=( + "Takes a value and returns the value in different bases and" + " encodings (binary, hex, base 10, and ascii)" + ), + usage=( + "`[value]`\nAccepts numbers in the following formats:\n0x" + " (hex)\n0b (binary) \nNo prefix (assumed decimal)" + ), + ) + async def htd(self: Self, ctx: commands.Context, *, val_to_convert: str) -> None: """The main logic for the htd command Args: ctx (commands.Context): The context in which the command was run it val_to_convert (str): The raw user input """ - val_to_convert = self.clean_input(val_to_convert) + val_to_convert = clean_input(val_to_convert) # Convert the input into a list, splitting on operators and numbers # A non-equation input will have a list size of one - parsed_list = self.split_nicely(val_to_convert) + parsed_list = split_nicely(val_to_convert) # Convert the list to all ints try: - int_list = self.convert_list_to_ints(parsed_list.copy()) - except ValueError: - parsed_list_str = str( - parsed_list - ) # Convert the list to a string to only print so many. + int_list = convert_list_to_ints(parsed_list.copy()) + except ValueError as e: + # Handle the exception properly and send the embed here embed = auxiliary.prepare_deny_embed( - message="Unable to convert value, are you sure it's valid?", + message="Unable to convert value, are you sure it's valid?" + ) + embed.add_field( + name="Error Details", + value=str(e), + inline=False, ) - if parsed_list[0].startswith("0x"): - embed.set_footer( - text=f"Tried to convert {parsed_list_str[1:31]} to hex" - ) - elif parsed_list[0].startswith("0b"): - embed.set_footer( - text=f"Tried to convert {parsed_list_str[1:31]} to binary" - ) - else: - embed.set_footer( - text=f"Tried to convert {parsed_list_str[1:31]} to int" - ) await ctx.send(embed=embed) return # Attempt to parse the given equation and return a single integer answer try: - calced_val = self.perform_op_on_list(int_list) + calced_val = perform_op_on_list(int_list) except ValueError: await auxiliary.send_deny_embed( message=( @@ -339,5 +351,5 @@ async def htd_command(self, ctx: commands.Context, val_to_convert: str) -> None: ) return - embed = self.custom_embed_generation(val_to_convert, calced_val) - await ctx.send(embed=embed) + embed = custom_embed_generation(val_to_convert, calced_val) + await ctx.send(embed=embed) \ No newline at end of file From ea6e954e84f41ead71fb0de7102e1ddb87020e16 Mon Sep 17 00:00:00 2001 From: TheKrol Date: Sun, 4 May 2025 07:35:51 -0400 Subject: [PATCH 06/10] Code factor clean up --- techsupport_bot/commands/htd.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/techsupport_bot/commands/htd.py b/techsupport_bot/commands/htd.py index 5f1a56979..8fd41a386 100644 --- a/techsupport_bot/commands/htd.py +++ b/techsupport_bot/commands/htd.py @@ -122,7 +122,7 @@ def convert_list_to_ints(raw_list: list) -> list: try: # Attempt to convert each value raw_list[index] = convert_value_to_integer(value) - except ValueError: + except ValueError as exc: # If conversion fails, get the base from the value if value.startswith("0x"): base = "hexadecimal" @@ -131,7 +131,7 @@ def convert_list_to_ints(raw_list: list) -> list: else: base = "decimal" - raise ValueError(f"Failed to convert `{value}` from {base} base.") + raise ValueError(f"Failed to convert `{value}` from {base} base.") from exc return raw_list @@ -352,4 +352,4 @@ async def htd(self: Self, ctx: commands.Context, *, val_to_convert: str) -> None return embed = custom_embed_generation(val_to_convert, calced_val) - await ctx.send(embed=embed) \ No newline at end of file + await ctx.send(embed=embed) From 0c6f7ecd7c3bcf037e4dffcb0524576595ff382a Mon Sep 17 00:00:00 2001 From: TheKrol Date: Sun, 4 May 2025 07:49:38 -0400 Subject: [PATCH 07/10] Attempting to fix test --- techsupport_bot/tests/commands_tests/test_extensions_htd.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/techsupport_bot/tests/commands_tests/test_extensions_htd.py b/techsupport_bot/tests/commands_tests/test_extensions_htd.py index d9d293980..ff66c2d63 100644 --- a/techsupport_bot/tests/commands_tests/test_extensions_htd.py +++ b/techsupport_bot/tests/commands_tests/test_extensions_htd.py @@ -8,10 +8,12 @@ from typing import Self import pytest +import importlib from commands import htd from core import auxiliary from discord.ext import commands from tests import config_for_tests, helpers +from unittest.mock import AsyncMock, MagicMock, call def setup_local_extension(bot: helpers.MockBot = None): @@ -383,6 +385,7 @@ def test_long_string(self: Self) -> None: # Step 3 - Assert that everything works assert output == "A" * 1021 + "..." + class Test_CustomEmbed: """A set of tests for custom_embed_generation""" From 967e174fe592af315466b10b0760e225fbc5fb69 Mon Sep 17 00:00:00 2001 From: TheKrol Date: Sun, 4 May 2025 07:50:47 -0400 Subject: [PATCH 08/10] Fix them all? --- techsupport_bot/tests/commands_tests/test_extensions_htd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/techsupport_bot/tests/commands_tests/test_extensions_htd.py b/techsupport_bot/tests/commands_tests/test_extensions_htd.py index ff66c2d63..56c39dc15 100644 --- a/techsupport_bot/tests/commands_tests/test_extensions_htd.py +++ b/techsupport_bot/tests/commands_tests/test_extensions_htd.py @@ -7,13 +7,14 @@ from typing import Self +import discord import pytest import importlib from commands import htd from core import auxiliary from discord.ext import commands from tests import config_for_tests, helpers -from unittest.mock import AsyncMock, MagicMock, call +from unittest.mock import AsyncMock, MagicMock, call, patch def setup_local_extension(bot: helpers.MockBot = None): From f65056d1ce2a908484c15faf0caee2d21dd20db1 Mon Sep 17 00:00:00 2001 From: TheKrol Date: Sun, 4 May 2025 08:04:26 -0400 Subject: [PATCH 09/10] Revert test back to old ones --- techsupport_bot/commands/htd.py | 1 + .../commands_tests/test_extensions_htd.py | 340 +----------------- 2 files changed, 2 insertions(+), 339 deletions(-) diff --git a/techsupport_bot/commands/htd.py b/techsupport_bot/commands/htd.py index 600930dca..dcba22627 100644 --- a/techsupport_bot/commands/htd.py +++ b/techsupport_bot/commands/htd.py @@ -290,6 +290,7 @@ def split_nicely(str_to_split: str) -> list: return parsed_list + class Htd(cogs.BaseCog): """ perform calculations on cross-base numbers and convert between them diff --git a/techsupport_bot/tests/commands_tests/test_extensions_htd.py b/techsupport_bot/tests/commands_tests/test_extensions_htd.py index 56c39dc15..b483a65f6 100644 --- a/techsupport_bot/tests/commands_tests/test_extensions_htd.py +++ b/techsupport_bot/tests/commands_tests/test_extensions_htd.py @@ -7,28 +7,8 @@ from typing import Self -import discord import pytest -import importlib from commands import htd -from core import auxiliary -from discord.ext import commands -from tests import config_for_tests, helpers -from unittest.mock import AsyncMock, MagicMock, call, patch - - -def setup_local_extension(bot: helpers.MockBot = None): - """A simple function to setup an instance of the htd extension - - Args: - bot (helpers.MockBot, optional): A fake bot object. Should be used if using a - fake_discord_env in the test. Defaults to None. - - Returns: - HTD: The instance of the htd class - """ - with patch("asyncio.create_task", return_value=None): - return htd.Htd(bot) class Test_SplitNicely: @@ -384,322 +364,4 @@ def test_long_string(self: Self) -> None: output = htd.format_embed_field("A" * 2024) # Step 3 - Assert that everything works - assert output == "A" * 1021 + "..." - - -class Test_CustomEmbed: - """A set of tests for custom_embed_generation""" - - def test_basic_embed_called(self): - """A test to ensure that the basic embed is generated correctly""" - # Step 1 - Setup env - hextodec = setup_local_extension() - auxiliary.generate_basic_embed = MagicMock() - hextodec.format_embed_field = MagicMock() - hextodec.integer_to_hexadecimal = MagicMock() - hextodec.integer_to_binary = MagicMock() - hextodec.integer_to_ascii = MagicMock() - - # Step 2 - Call the function - hextodec.custom_embed_generation("raw", 5) - - # Step 3 - Assert that everything works - auxiliary.generate_basic_embed.assert_called_once_with( - title="Your conversion results", - description="Converting `raw`", - color=discord.Color.green(), - ) - - # Step 4 - Cleanup - importlib.reload(auxiliary) - - def test_fields_correct(self): - """A test to ensure that the basic embed is generated correctly""" - # Step 1 - Setup env - hextodec = setup_local_extension() - fakeembed = MagicMock() - fakeembed.add_field = MagicMock() - auxiliary.generate_basic_embed = MagicMock(return_value=fakeembed) - hextodec.format_embed_field = MagicMock(return_value="value") - hextodec.integer_to_hexadecimal = MagicMock() - hextodec.integer_to_binary = MagicMock() - hextodec.integer_to_ascii = MagicMock() - - # Step 2 - Call the function - hextodec.custom_embed_generation("raw", 5) - - # Step 3 - Assert that everything works - expected_calls = [ - call( - name="Decimal:", - value="value", - inline=False, - ), - call( - name="Hexadecimal:", - value="value", - inline=False, - ), - call( - name="Binary:", - value="value", - inline=False, - ), - call( - name="Ascii encoding:", - value="value", - inline=False, - ), - ] - fakeembed.add_field.assert_has_calls(expected_calls) - - # Step 4 - Cleanup - importlib.reload(auxiliary) - - def test_ascii_error(self): - """A test to ensure that the basic embed is generated correctly, - even if int to ascii has a ValueError""" - # Step 1 - Setup env - hextodec = setup_local_extension() - fakeembed = MagicMock() - fakeembed.add_field = MagicMock() - auxiliary.generate_basic_embed = MagicMock(return_value=fakeembed) - hextodec.format_embed_field = MagicMock(return_value="value") - hextodec.integer_to_hexadecimal = MagicMock() - hextodec.integer_to_binary = MagicMock() - hextodec.integer_to_ascii = MagicMock(side_effect=ValueError) - - # Step 2 - Call the function - hextodec.custom_embed_generation("raw", 5) - - # Step 3 - Assert that everything works - expected_calls = [ - call( - name="Decimal:", - value="value", - inline=False, - ), - call( - name="Hexadecimal:", - value="value", - inline=False, - ), - call( - name="Binary:", - value="value", - inline=False, - ), - call( - name="Ascii encoding:", - value="No ascii representation could be made", - inline=False, - ), - ] - fakeembed.add_field.assert_has_calls(expected_calls) - - # Step 4 - Cleanup - importlib.reload(auxiliary) - - -class Test_HTDCommand: - """A set of tests to test htd_command""" - - @pytest.mark.asyncio - async def test_cleaninput_call(self): - """A test to ensure that clean_input is called correctly""" - # Step 1 - Setup env - discord_env = config_for_tests.FakeDiscordEnv() - hextodec = setup_local_extension(discord_env.bot) - hextodec.clean_input = MagicMock() - hextodec.split_nicely = MagicMock() - hextodec.convert_list_to_ints = MagicMock() - hextodec.perform_op_on_list = MagicMock() - hextodec.custom_embed_generation = MagicMock() - auxiliary.send_deny_embed = AsyncMock() - discord_env.context.send = AsyncMock() - - # Step 2 - Call the function - await hextodec.htd_command(discord_env.context, " test ") - - # Step 3 - Assert that everything works - hextodec.clean_input.assert_called_once_with(" test ") - - # Step 4 - Cleanup - importlib.reload(auxiliary) - - @pytest.mark.asyncio - async def test_splitnicely_call(self): - """A test to ensure that split_nicely is called correctly""" - # Step 1 - Setup env - discord_env = config_for_tests.FakeDiscordEnv() - hextodec = setup_local_extension(discord_env.bot) - hextodec.clean_input = MagicMock(return_value="clean") - hextodec.split_nicely = MagicMock() - hextodec.convert_list_to_ints = MagicMock() - hextodec.perform_op_on_list = MagicMock() - hextodec.custom_embed_generation = MagicMock() - auxiliary.send_deny_embed = AsyncMock() - discord_env.context.send = AsyncMock() - - # Step 2 - Call the function - await hextodec.htd_command(discord_env.context, "test") - - # Step 3 - Assert that everything works - hextodec.split_nicely.assert_called_once_with("clean") - - # Step 4 - Cleanup - importlib.reload(auxiliary) - - @pytest.mark.asyncio - async def test_convertints_call(self): - """A test to ensure that convert_list_to_ints is called correctly""" - # Step 1 - Setup env - discord_env = config_for_tests.FakeDiscordEnv() - hextodec = setup_local_extension(discord_env.bot) - hextodec.clean_input = MagicMock() - hextodec.split_nicely = MagicMock(return_value=["1", "+", "1"]) - hextodec.convert_list_to_ints = MagicMock() - hextodec.perform_op_on_list = MagicMock() - hextodec.custom_embed_generation = MagicMock() - auxiliary.send_deny_embed = AsyncMock() - discord_env.context.send = AsyncMock() - - # Step 2 - Call the function - await hextodec.htd_command(discord_env.context, "test") - - # Step 3 - Assert that everything works - hextodec.convert_list_to_ints.assert_called_once_with(["1", "+", "1"]) - - # Step 4 - Cleanup - importlib.reload(auxiliary) - - @pytest.mark.asyncio - async def test_convertints_error(self): - """A test to ensure that convert_list_to_ints error is handled correctly""" - # Step 1 - Setup env - discord_env = config_for_tests.FakeDiscordEnv() - hextodec = setup_local_extension(discord_env.bot) - hextodec.clean_input = MagicMock() - hextodec.split_nicely = MagicMock(return_value=["1", "+", "1"]) - hextodec.convert_list_to_ints = MagicMock(side_effect=ValueError) - hextodec.perform_op_on_list = MagicMock() - hextodec.custom_embed_generation = MagicMock() - auxiliary.prepare_deny_embed = MagicMock() - - # Create a MockContext with the required attributes - mock_context = MagicMock(spec=commands.Context) - mock_context.send = AsyncMock() # Mock the send method - - # Set the mock context for the test - discord_env.context = mock_context - - # Step 2 - Call the function - await hextodec.htd_command(discord_env.context, "test") - - # Step 3 - Assert that everything works - auxiliary.prepare_deny_embed.assert_called_once_with( - message="Unable to convert value, are you sure it's valid?" - ) - discord_env.context.send.assert_called_once_with( - embed=auxiliary.prepare_deny_embed.return_value - ) - - # Step 4 - Cleanup - importlib.reload(auxiliary) - - @pytest.mark.asyncio - async def test_performop_call(self): - """A test to ensure that perform_op_on_list is called correctly""" - # Step 1 - Setup env - discord_env = config_for_tests.FakeDiscordEnv() - hextodec = setup_local_extension(discord_env.bot) - hextodec.clean_input = MagicMock() - hextodec.split_nicely = MagicMock() - hextodec.convert_list_to_ints = MagicMock(return_value=[1, "+", 1]) - hextodec.perform_op_on_list = MagicMock() - hextodec.custom_embed_generation = MagicMock() - auxiliary.send_deny_embed = AsyncMock() - discord_env.context.send = AsyncMock() - - # Step 2 - Call the function - await hextodec.htd_command(discord_env.context, "test") - - # Step 3 - Assert that everything works - hextodec.perform_op_on_list.assert_called_once_with([1, "+", 1]) - - # Step 4 - Cleanup - importlib.reload(auxiliary) - - @pytest.mark.asyncio - async def test_perform_op_error(self): - """A test to ensure that perform_op_on_list error is handled correctly""" - # Step 1 - Setup env - discord_env = config_for_tests.FakeDiscordEnv() - hextodec = setup_local_extension(discord_env.bot) - hextodec.clean_input = MagicMock() - hextodec.split_nicely = MagicMock() - hextodec.convert_list_to_ints = MagicMock() - hextodec.perform_op_on_list = MagicMock(side_effect=ValueError) - hextodec.custom_embed_generation = MagicMock() - auxiliary.send_deny_embed = AsyncMock() - discord_env.context.send = AsyncMock() - - # Step 2 - Call the function - await hextodec.htd_command(discord_env.context, "test") - - # Step 3 - Assert that everything works - auxiliary.send_deny_embed.assert_called_once_with( - message=( - "Unable to perform calculation, are you sure that equation is valid?" - ), - channel=discord_env.context.channel, - ) - - # Step 4 - Cleanup - importlib.reload(auxiliary) - - @pytest.mark.asyncio - async def test_customembed_call(self): - """A test to ensure that custom_embed_generation is called correctly""" - # Step 1 - Setup env - discord_env = config_for_tests.FakeDiscordEnv() - hextodec = setup_local_extension(discord_env.bot) - hextodec.clean_input = MagicMock(return_value="1") - hextodec.split_nicely = MagicMock() - hextodec.convert_list_to_ints = MagicMock() - hextodec.perform_op_on_list = MagicMock(return_value=1) - hextodec.custom_embed_generation = MagicMock() - auxiliary.send_deny_embed = AsyncMock() - discord_env.context.send = AsyncMock() - - # Step 2 - Call the function - await hextodec.htd_command(discord_env.context, "test") - - # Step 3 - Assert that everything works - hextodec.custom_embed_generation.assert_called_once_with("1", 1) - - # Step 4 - Cleanup - importlib.reload(auxiliary) - - @pytest.mark.asyncio - async def test_send_call(self): - """A test to ensure that perform_op_on_list is called correctly""" - # Step 1 - Setup env - discord_env = config_for_tests.FakeDiscordEnv() - hextodec = setup_local_extension(discord_env.bot) - hextodec.clean_input = MagicMock() - hextodec.split_nicely = MagicMock() - hextodec.convert_list_to_ints = MagicMock() - hextodec.perform_op_on_list = MagicMock() - hextodec.custom_embed_generation = MagicMock(return_value="Fake Embed") - auxiliary.send_deny_embed = AsyncMock() - discord_env.context.send = AsyncMock() - - # Step 2 - Call the function - await hextodec.htd_command(discord_env.context, "test") - - # Step 3 - Assert that everything works - discord_env.context.send.assert_called_once_with(embed="Fake Embed") - - # Step 4 - Cleanup - importlib.reload(auxiliary) + assert output == "A" * 1021 + "..." \ No newline at end of file From ee7df5c5a3c989343b71dfb8c5cfe5739b815a94 Mon Sep 17 00:00:00 2001 From: TheKrol Date: Sun, 4 May 2025 08:05:22 -0400 Subject: [PATCH 10/10] Add final new line --- techsupport_bot/tests/commands_tests/test_extensions_htd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/techsupport_bot/tests/commands_tests/test_extensions_htd.py b/techsupport_bot/tests/commands_tests/test_extensions_htd.py index b483a65f6..7c907cc68 100644 --- a/techsupport_bot/tests/commands_tests/test_extensions_htd.py +++ b/techsupport_bot/tests/commands_tests/test_extensions_htd.py @@ -364,4 +364,4 @@ def test_long_string(self: Self) -> None: output = htd.format_embed_field("A" * 2024) # Step 3 - Assert that everything works - assert output == "A" * 1021 + "..." \ No newline at end of file + assert output == "A" * 1021 + "..."