From 47169d8c46eaed8f1563241932bc25c475c4b104 Mon Sep 17 00:00:00 2001 From: ajax146 <31014239+ajax146@users.noreply.github.com> Date: Fri, 5 Jul 2024 11:30:35 -0700 Subject: [PATCH] Moves functions around in htd.py, makes unit tests better (#1058) * Moves functions around in htd.py, makes unit tests better * Formatting fixes --- techsupport_bot/commands/htd.py | 460 +++++++++-------- .../commands_tests/test_extensions_htd.py | 481 ++---------------- 2 files changed, 269 insertions(+), 672 deletions(-) diff --git a/techsupport_bot/commands/htd.py b/techsupport_bot/commands/htd.py index 14d974a8..453dba3c 100644 --- a/techsupport_bot/commands/htd.py +++ b/techsupport_bot/commands/htd.py @@ -23,300 +23,294 @@ async def setup(bot: bot.TechSupportBot) -> None: 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 - Attrs: - OPERATORS (list[str]): The list of operations to process + 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: 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 + + Raises: + ValueError: If the operator is not valid, this is raised + + Returns: + int: The integer value of the computed equation + """ - 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 = "" + 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: - # 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: 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: 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 + raw_list[index] = convert_value_to_integer(value) + return raw_list - Args: - equation_list (list): The equation in a list form - Raises: - ValueError: If the operator is not valid, this is raised +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" - Returns: - int: The integer value of the computed equation - """ + Args: + integer (int): The integer to convert to hex - 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 + Returns: + str: The hexadecimal representation of the input + """ + raw_hex = hex(integer) + compare_value = 1 + if raw_hex.startswith("-"): + compare_value = 0 - @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: - """This discord command for .htd + if len(raw_hex) % 2 == compare_value: + raw_hex = raw_hex.replace("0x", "0x0") - 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) + return raw_hex - 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 - Args: - user_input (str): The raw input from the user +def integer_to_binary(integer: int) -> str: + """Takes an integer in and returns a string representation in binary - 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 + Args: + integer (int): The integer to convert to binary - def convert_list_to_ints(self: Self, raw_list: list) -> list: - """This converts the values in an equation list into ints + Returns: + str: The binary representation of the input + """ + return bin(integer) - Args: - raw_list (list): An equation formatted as a list - 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 +def integer_to_ascii(integer: int) -> str: + """Takes an integer in and returns a string representation in ascii - def integer_to_hexadecimal(self: 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 ascii - Args: - integer (int): The integer to convert to hex + 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 - Returns: - str: The hexadecimal representation of the input - """ - raw_hex = hex(integer) - compare_value = 1 - if raw_hex.startswith("-"): - compare_value = 0 - if len(raw_hex) % 2 == compare_value: - raw_hex = raw_hex.replace("0x", "0x0") +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 - return raw_hex + Args: + data (str): The raw input to format - def integer_to_binary(self: Self, integer: int) -> str: - """Takes an integer in and returns a string representation in binary + Returns: + str: The string output, either left alone or cropped + """ + if len(data) <= 1024: + return data + return data[:1021] + "..." - Args: - integer (int): The integer to convert to binary - Returns: - str: The binary representation of the input - """ - return bin(integer) +def custom_embed_generation(raw_input: str, val_to_convert: int) -> discord.Embed: + """Generates, but does not send, a formatted embed - def integer_to_ascii(self: Self, integer: int) -> str: - """Takes an integer in and returns a string representation in ascii + Args: + raw_input (str): The raw input from the user, to display in the embed + val_to_convert (int): The value to convert from - Args: - integer (int): The integer to convert to ascii + 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, + ) - 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 + # Next, add hex + embed.add_field( + name="Hexadecimal:", + value=format_embed_field(integer_to_hexadecimal(val_to_convert)), + inline=False, + ) - def format_embed_field(self: 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 + # Next, add binary + embed.add_field( + name="Binary:", + value=format_embed_field(integer_to_binary(val_to_convert)), + inline=False, + ) - Args: - data (str): The raw input to format + try: + ascii_value = format_embed_field(integer_to_ascii(val_to_convert)) + except ValueError: + ascii_value = "No ascii representation could be made" - Returns: - str: The string output, either left alone or cropped - """ - if len(data) <= 1024: - return data - return data[:1021] + "..." + # Finally, add ascii encoding - def custom_embed_generation( - self: Self, raw_input: str, val_to_convert: int - ) -> discord.Embed: - """Generates, but does not send, a formatted embed + embed.add_field( + name="Ascii encoding:", + value=ascii_value, + inline=False, + ) - Args: - raw_input (str): The raw input from the user, to display in the embed - val_to_convert (int): The value to convert from + print(embed.fields[0].name) + return embed - 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, - ) - try: - ascii_value = self.format_embed_field(self.integer_to_ascii(val_to_convert)) - except ValueError: - ascii_value = "No ascii representation could be made" +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 - # Finally, add ascii encoding + Args: + str_to_split (str): The equation to parse - embed.add_field( - name="Ascii encoding:", - value=ascii_value, - inline=False, - ) + Returns: + list: A list containing strings of the operators and numbers + """ - return embed + OPERATORS = ["+", "-", "*", "/"] - async def htd_command( - self: Self, ctx: commands.Context, val_to_convert: str - ) -> None: + 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 + + # 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()) + int_list = convert_list_to_ints(parsed_list.copy()) except ValueError: await auxiliary.send_deny_embed( message="Unable to convert value, are you sure it's valid?", @@ -326,7 +320,7 @@ async def htd_command( # 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=( @@ -337,5 +331,5 @@ async def htd_command( ) return - embed = self.custom_embed_generation(val_to_convert, calced_val) + embed = custom_embed_generation(val_to_convert, calced_val) await ctx.send(embed=embed) diff --git a/techsupport_bot/tests/commands_tests/test_extensions_htd.py b/techsupport_bot/tests/commands_tests/test_extensions_htd.py index 6266f575..7c907cc6 100644 --- a/techsupport_bot/tests/commands_tests/test_extensions_htd.py +++ b/techsupport_bot/tests/commands_tests/test_extensions_htd.py @@ -5,29 +5,10 @@ from __future__ import annotations -import importlib from typing import Self -from unittest.mock import AsyncMock, MagicMock, call, patch -import discord import pytest from commands import htd -from core import auxiliary -from tests import config_for_tests, helpers - - -def setup_local_extension(bot: helpers.MockBot = None) -> htd.Htd: - """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.Htd: The instance of the htd class - """ - with patch("asyncio.create_task", return_value=None): - return htd.Htd(bot) class Test_SplitNicely: @@ -36,66 +17,54 @@ class Test_SplitNicely: def test_single_number(self: Self) -> None: """A test to ensure that when just a single number is passed, only a single entry is returned""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.split_nicely("5") + output = htd.split_nicely("5") # Step 3 - Assert that everything works assert output == ["5"] def test_simple_equation(self: Self) -> None: """A test to ensure that equations are split properly""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.split_nicely("5+5") + output = htd.split_nicely("5+5") # Step 3 - Assert that everything works assert output == ["5", "+", "5"] def test_negative(self: Self) -> None: """A test to ensure that negatives are handled properly""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.split_nicely("-2") + output = htd.split_nicely("-2") # Step 3 - Assert that everything works assert output == ["-2"] def test_double_minus(self: Self) -> None: """A test to ensure that 2 minus signs in a row are handled properly""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.split_nicely("5--8") + output = htd.split_nicely("5--8") # Step 3 - Assert that everything works assert output == ["5", "-", "-8"] def test_every_operator(self: Self) -> None: """A test to ensure that every operator is recognized""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.split_nicely("1+2-3*4/5") + output = htd.split_nicely("1+2-3*4/5") # Step 3 - Assert that everything works assert output == ["1", "+", "2", "-", "3", "*", "4", "/", "5"] def test_long_number(self: Self) -> None: """A test to ensure that long numbers are added correctly""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.split_nicely("3276856238658273658724658247658245") + output = htd.split_nicely("3276856238658273658724658247658245") # Step 3 - Assert that everything works assert output == ["3276856238658273658724658247658245"] @@ -106,77 +75,63 @@ class Test_ConvertToInt: def test_simple_hexadecimal(self: Self) -> None: """Test that a simple hex value is properly converted""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.convert_value_to_integer("0x5") + output = htd.convert_value_to_integer("0x5") # Step 3 - Assert that everything works assert output == 5 def test_complex_hexadecimal(self: Self) -> None: """Test that a complex hex value is properly converted""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.convert_value_to_integer("0xFBA34") + output = htd.convert_value_to_integer("0xFBA34") # Step 3 - Assert that everything works assert output == 1030708 def test_simple_binary(self: Self) -> None: """Test that a simple binary value is properly converted""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.convert_value_to_integer("0b1") + output = htd.convert_value_to_integer("0b1") # Step 3 - Assert that everything works assert output == 1 def test_complex_binary(self: Self) -> None: """Test that a complex binary value is properly converted""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.convert_value_to_integer("0b10101010100110") + output = htd.convert_value_to_integer("0b10101010100110") # Step 3 - Assert that everything works assert output == 10918 def test_simple_decimal(self: Self) -> None: """Test that a simple deciaml value is properly converted""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.convert_value_to_integer("8") + output = htd.convert_value_to_integer("8") # Step 3 - Assert that everything works assert output == 8 def test_complex_decimal(self: Self) -> None: """Test that a complex deciaml value is properly converted""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.convert_value_to_integer("58934275971834685") + output = htd.convert_value_to_integer("58934275971834685") # Step 3 - Assert that everything works assert output == 58934275971834685 def test_float_handling(self: Self) -> None: """Test that a float is turned into an integer""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.convert_value_to_integer("1.2") + output = htd.convert_value_to_integer("1.2") # Step 3 - Assert that everything works assert output == 1 @@ -187,55 +142,45 @@ class Test_PerformOperator: def test_single_integer(self: Self) -> None: """A test to ensure that a single integer input is not modified""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.perform_op_on_list([1]) + output = htd.perform_op_on_list([1]) # Step 3 - Assert that everything works assert output == 1 def test_single_operator(self: Self) -> None: """A test to ensure that operations work as expected""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.perform_op_on_list([3, "+", 4]) + output = htd.perform_op_on_list([3, "+", 4]) # Step 3 - Assert that everything works assert output == 7 def test_multiple_operator(self: Self) -> None: """A test to ensure that multiple operators work""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.perform_op_on_list([3, "+", 4, "-", 5]) + output = htd.perform_op_on_list([3, "+", 4, "-", 5]) # Step 3 - Assert that everything works assert output == 2 def test_all_operator(self: Self) -> None: """A test to ensure that all operators work""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.perform_op_on_list([3, "+", 4, "-", 5, "*", 6, "/", 2]) + output = htd.perform_op_on_list([3, "+", 4, "-", 5, "*", 6, "/", 2]) # Step 3 - Assert that everything works assert output == 6 def test_negative_number(self: Self) -> None: """A test to ensure that negative numbers work""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.perform_op_on_list([-3, "+", 4]) + output = htd.perform_op_on_list([-3, "+", 4]) # Step 3 - Assert that everything works assert output == 1 @@ -246,33 +191,27 @@ class Test_CleanInput: def test_replacing_hex(self: Self) -> None: """A test to ensure that # is replaced with 0x""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.clean_input("#124") + output = htd.clean_input("#124") # Step 3 - Assert that everything works assert output == "0x124" def test_stripping_spaces(self: Self) -> None: """A test to ensure that spaces are removed from the string""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.clean_input("5 + 5") + output = htd.clean_input("5 + 5") # Step 3 - Assert that everything works assert output == "5+5" def test_stripping_quotes(self: Self) -> None: """A test to ensure that quotes are removed from the string""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.clean_input("\"5'") + output = htd.clean_input("\"5'") # Step 3 - Assert that everything works assert output == "5" @@ -283,24 +222,18 @@ class Test_ConvertList: def test_single_int(self: Self) -> None: """A test to ensure that just a single int is correctly converted""" - # Step 1 - Setup env - hextodec = setup_local_extension() - hextodec.convert_value_to_integer = MagicMock(return_value=5) # Step 2 - Call the function - output = hextodec.convert_list_to_ints(["5"]) + output = htd.convert_list_to_ints(["5"]) # Step 3 - Assert that everything works assert output == [5] def test_equations(self: Self) -> None: """A test to ensure that just a single int is correctly converted""" - # Step 1 - Setup env - hextodec = setup_local_extension() - hextodec.convert_value_to_integer = MagicMock(return_value=5) # Step 2 - Call the function - output = hextodec.convert_list_to_ints(["5", "+", "5"]) + output = htd.convert_list_to_ints(["5", "+", "5"]) # Step 3 - Assert that everything works assert output == [5, "+", 5] @@ -311,44 +244,36 @@ class Test_IntToHex: def test_simple_hex(self: Self) -> None: """This tests to ensure that a basic hex conversion works""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.integer_to_hexadecimal(16) + output = htd.integer_to_hexadecimal(16) # Step 3 - Assert that everything works assert output == "0x10" def test_complex_hex(self: Self) -> None: """This tests to ensure that a complex hex conversion works""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.integer_to_hexadecimal(847653289450) + output = htd.integer_to_hexadecimal(847653289450) # Step 3 - Assert that everything works assert output == "0xc55c12bdea" def test_hex_styling(self: Self) -> None: """This tests to ensure that the styling works""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.integer_to_hexadecimal(5) + output = htd.integer_to_hexadecimal(5) # Step 3 - Assert that everything works assert output == "0x05" def test_negative_hex(self: Self) -> None: """This tests to ensure that the hex maintains it's negative""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.integer_to_hexadecimal(-5) + output = htd.integer_to_hexadecimal(-5) # Step 3 - Assert that everything works assert output == "-0x05" @@ -359,33 +284,27 @@ class Test_IntToBin: def test_simple_bin(self: Self) -> None: """This tests to ensure that a basic binary conversion works""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.integer_to_binary(1) + output = htd.integer_to_binary(1) # Step 3 - Assert that everything works assert output == "0b1" def test_complex_bin(self: Self) -> None: """This tests to ensure that a complex binary conversion works""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.integer_to_binary(98235671235) + output = htd.integer_to_binary(98235671235) # Step 3 - Assert that everything works assert output == "0b1011011011111010011010110001011000011" def test_negative_hex(self: Self) -> None: """This tests to ensure that the binary maintains it's negative""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.integer_to_binary(-5) + output = htd.integer_to_binary(-5) # Step 3 - Assert that everything works assert output == "-0b101" @@ -396,369 +315,53 @@ class Test_IntToAscii: def test_simple_ascii(self: Self) -> None: """This tests to ensure that a basic ascii conversion works""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.integer_to_ascii(65) + output = htd.integer_to_ascii(65) # Step 3 - Assert that everything works assert output == "A" def test_complex_ascii(self: Self) -> None: """This tests to ensure that a complex ascii conversion works""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.integer_to_ascii(18946016917865816) + output = htd.integer_to_ascii(18946016917865816) # Step 3 - Assert that everything works assert output == "COMPLEX" + def test_invalid_ascii(self: Self) -> None: + """This tests to ensure that an invalid conversion will throw an error""" + with pytest.raises(ValueError): + htd.integer_to_ascii(1) + class Test_FormatEmbedField: """Tests to test format_embed_field""" def test_short_string(self: Self) -> None: """A test to ensure that a short string is not touched""" - # Step 1 - Setup env - hextodec = setup_local_extension() # Step 2 - Call the function - output = hextodec.format_embed_field("ABCD") + output = htd.format_embed_field("ABCD") # Step 3 - Assert that everything works assert output == "ABCD" def test_1024_string(self: Self) -> None: - """A test to ensure that a short string is not touched""" - # Step 1 - Setup env - hextodec = setup_local_extension() - + """A test to ensure that a string of lenght 1024 is not touched""" # Step 2 - Call the function - output = hextodec.format_embed_field("A" * 1024) + output = htd.format_embed_field("A" * 1024) # Step 3 - Assert that everything works assert output == "A" * 1024 def test_long_string(self: Self) -> None: - """A test to ensure that a short string is not touched""" - # Step 1 - Setup env - hextodec = setup_local_extension() + """A test to ensure that a long string is cropped""" # Step 2 - Call the function - output = hextodec.format_embed_field("A" * 2024) + 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: Self) -> None: - """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: Self) -> None: - """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: Self) -> None: - """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: Self) -> None: - """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: Self) -> None: - """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: Self) -> None: - """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: Self) -> None: - """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.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 convert value, are you sure it's valid?", - channel=discord_env.context.channel, - ) - - # Step 4 - Cleanup - importlib.reload(auxiliary) - - @pytest.mark.asyncio - async def test_performop_call(self: Self) -> None: - """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: Self) -> None: - """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: Self) -> None: - """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: Self) -> None: - """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)