From 1191b86be784b726135e3bbbf06f99be0886908f Mon Sep 17 00:00:00 2001 From: abluey Date: Tue, 2 May 2023 16:28:54 +0100 Subject: [PATCH 01/41] feat: core migration --- koala/cogs/voting/cog.py | 261 ++++++--------------------------- koala/cogs/voting/core.py | 293 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 337 insertions(+), 217 deletions(-) create mode 100644 koala/cogs/voting/core.py diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 95ba1db3..2fac7d1b 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -16,6 +16,7 @@ # Own modules import koalabot from koala.db import session_manager, insert_extension +from . import core from .db import VoteManager, get_results, create_embed, add_reactions from .log import logger from .models import Votes @@ -92,57 +93,21 @@ async def cog_unload(self): @tasks.loop(seconds=60.0) async def vote_end_loop(self): - try: - with session_manager() as session: - now = time.time() - votes = session.execute(select(Votes.vote_id, Votes.author_id, Votes.guild_id, Votes.title, Votes.end_time) - .where(Votes.end_time < now)).all() - for v_id, a_id, g_id, title, end_time in votes: - if v_id in self.vote_manager.sent_votes.keys(): - vote = self.vote_manager.get_vote_from_id(v_id) - results = await get_results(self.bot, vote) - embed = await make_result_embed(vote, results) - try: - if vote.chair: - try: - chair = await self.bot.fetch_user(vote.chair) - await chair.send(f"Your vote {title} has closed") - await chair.send(embed=embed) - except discord.Forbidden: - user = await self.bot.fetch_user(vote.author) - await user.send(f"Your vote {title} has closed") - await user.send(embed=embed) - else: - try: - user = await self.bot.fetch_user(vote.author) - await user.send(f"Your vote {title} has closed") - await user.send(embed=embed) - except discord.Forbidden: - guild = await self.bot.fetch_guild(vote.guild) - user = await self.bot.fetch_user(guild.owner_id) - await user.send(f"A vote in your guild titled {title} has closed and the chair is unavailable.") - await user.send(embed=embed) - session.execute(delete(Votes).filter_by(vote_id=vote.id)) - session.commit() - self.vote_manager.cancel_sent_vote(vote.id) - except Exception as e: - session.execute(update(Votes).filter_by(vote_id=vote.id).values(end_time=time.time() + 86400)) - session.commit() - logger.error(f"error in vote loop: {e}") - except Exception as e: - logger.error("Exception in outer vote loop: %s" % e, exc_info=e) + await core.vote_end_loop(self.bot, self.vote_manager) @vote_end_loop.before_loop async def before_vote_loop(self): await self.bot.wait_until_ready() + @commands.Cog.listener() async def on_raw_reaction_add(self, payload): """ Listens for when a reaction is added to a message :param payload: payload of data about the reaction """ - await self.update_vote_message(payload.message_id, payload.user_id) + await core.update_vote_message(self.bot, payload.message_id, payload.user_id) + @commands.Cog.listener() async def on_raw_reaction_remove(self, payload): @@ -150,7 +115,8 @@ async def on_raw_reaction_remove(self, payload): Listens for when a reaction is removed from a message :param payload: payload of data about the reaction """ - await self.update_vote_message(payload.message_id, payload.user_id) + await core.update_vote_message(self.bot, payload.message_id, payload.user_id) + @commands.check(koalabot.is_admin) @commands.check(vote_is_enabled) @@ -159,8 +125,8 @@ async def vote(self, ctx): """ Use k!vote create to create a vote! """ - if ctx.invoked_subcommand is None: - await ctx.send(f"Please use `{koalabot.COMMAND_PREFIX}help vote` for more information") + await ctx.send(core.vote(ctx.invoked_subcommand)) + @commands.check(koalabot.is_admin) @commands.check(vote_is_enabled) @@ -170,23 +136,8 @@ async def start_vote(self, ctx, *, title): Creates a new vote :param title: The title of the vote """ - with session_manager() as session: - if self.vote_manager.has_active_vote(ctx.author.id): - guild_name = self.bot.get_guild(self.vote_manager.get_configuring_vote(ctx.author.id).guild) - await ctx.send(f"You already have an active vote in {guild_name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one.") - return - - in_db = session.execute(select(Votes).filter_by(title=title, author_id=ctx.author.id)).all() - if in_db: - await ctx.send(f"You already have a vote with title {title} sent!") - return - - if len(title) > 200: - await ctx.send("Title too long") - return + await ctx.send(core.start_vote(self.bot, self.vote_manager, title, ctx.author, ctx.guild)) - self.vote_manager.create_vote(ctx.author.id, ctx.guild.id, title) - await ctx.send(f"Vote titled `{title}` created for guild {ctx.guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it.") @currently_configuring() @commands.check(vote_is_enabled) @@ -197,9 +148,8 @@ async def add_role(self, ctx, *, role: discord.Role): If no roles are added, the vote will go to all users in a guild (unless a target voice channel has been set) :param role: role id (e.g. 135496683009081345) or a role ping (e.g. @Student) """ - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - vote.add_role(role.id) - await ctx.send(f"Vote will be sent to those with the {role.name} role") + await ctx.send(core.set_roles(self.vote_manager, ctx.author, role, "add")) + @currently_configuring() @commands.check(vote_is_enabled) @@ -209,9 +159,8 @@ async def remove_role(self, ctx, *, role: discord.Role): Removes a role to the list of roles the vote will be sent to :param role: role id (e.g. 135496683009081345) or a role ping (e.g. @Student) """ - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - vote.remove_role(role.id) - await ctx.send(f"Vote will no longer be sent to those with the {role.name} role") + await ctx.send(core.set_roles(self.vote_manager, ctx.author, role, "remove")) + @currently_configuring() @commands.check(vote_is_enabled) @@ -222,17 +171,8 @@ async def set_chair(self, ctx, *, chair: discord.Member = None): If no chair defaults to sending the message to the channel the vote is closed in :param chair: user id (e.g. 135496683009081345) or ping (e.g. @ito#8813) """ - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - if chair: - try: - await chair.send(f"You have been selected as the chair for vote titled {vote.title}") - vote.set_chair(chair.id) - await ctx.send(f"Set chair to {chair.name}") - except discord.Forbidden: - await ctx.send("Chair not set as requested user is not accepting direct messages.") - else: - vote.set_chair(None) - await ctx.send(f"Results will be sent to the channel vote is closed in") + await ctx.send(await core.set_chair(self.vote_manager, ctx.author, chair)) + @currently_configuring() @commands.check(vote_is_enabled) @@ -243,13 +183,8 @@ async def set_channel(self, ctx, *, channel: discord.VoiceChannel = None): If there isn't one set votes will go to all users in a guild (unless target roles have been added) :param channel: channel id (e.g. 135496683009081345) or mention (e.g. #cool-channel) """ - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - if channel: - vote.set_vc(channel.id) - await ctx.send(f"Set target channel to {channel.name}") - else: - vote.set_vc() - await ctx.send("Removed channel restriction on vote") + await ctx.send(core.set_channel(self.vote_manager, ctx.author, channel)) + @currently_configuring() @commands.check(vote_is_enabled) @@ -260,20 +195,8 @@ async def add_option(self, ctx, *, option_string): separate the title and description with a "+" e.g. option title+option description :param option_string: a title and description for the option separated by a '+' """ - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - if len(vote.options) > 9: - await ctx.send("Vote has maximum number of options already (10)") - return - current_option_length = sum([len(x.head) + len(x.body) for x in vote.options]) - if current_option_length + len(option_string) > 1500: - await ctx.send(f"Option string is too long. The total length of all the vote options cannot be over 1500 characters.") - return - if "+" not in option_string: - await ctx.send("Example usage: k!vote addOption option title+option description") - return - header, body = option_string.split("+") - vote.add_option(Option(header, body, self.vote_manager.generate_unique_opt_id())) - await ctx.send(f"Option {header} with description {body} added to vote") + await ctx.send(core.add_option(self.vote_manager, ctx.author, option_string)) + @currently_configuring() @commands.check(vote_is_enabled) @@ -283,9 +206,8 @@ async def remove_option(self, ctx, index: int): Removes an option from a vote based on it's index :param index: the number of the option """ - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - vote.remove_option(index) - await ctx.send(f"Option number {index} removed") + await ctx.send(core.remove_option(self.vote_manager, ctx.author, index)) + @currently_configuring() @commands.check(vote_is_enabled) @@ -297,19 +219,8 @@ async def set_end_time(self, ctx, *, time_string): :param time_string: string representing a time e.g. "2021-03-22 12:56" or "tomorrow at 10am" or "in 5 days and 15 minutes" :return: """ - now = time.time() - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - cal = parsedatetime.Calendar() - end_time_readable = cal.parse(time_string)[0] - end_time = time.mktime(end_time_readable) - if (end_time - now) < 0: - await ctx.send("You can't set a vote to end in the past") - return - # if (end_time - now) < 599: - # await ctx.send("Please set the end time to be at least 10 minutes in the future.") - # return - vote.set_end_time(end_time) - await ctx.send(f"Vote set to end at {time.strftime('%Y-%m-%d %H:%M:%S', end_time_readable)} UTC") + await ctx.send(core.set_end_time(self.vote_manager, ctx.author, time_string)) + @currently_configuring() @commands.check(vote_is_enabled) @@ -318,9 +229,8 @@ async def preview_vote(self, ctx): """ Generates a preview of what users will see with the current configuration of the vote """ - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - msg = await ctx.send(embed=create_embed(vote)) - await add_reactions(vote, msg) + await core.preview(self.vote_manager, ctx) + @commands.check(vote_is_enabled) @has_current_votes() @@ -330,12 +240,8 @@ async def cancel_vote(self, ctx, *, title): Cancels a vote you are setting up or have sent :param title: title of the vote to cancel """ - v_id = self.vote_manager.vote_lookup[(ctx.author.id, title)] - if v_id in self.vote_manager.sent_votes.keys(): - self.vote_manager.cancel_sent_vote(v_id) - else: - self.vote_manager.cancel_configuring_vote(ctx.author.id) - await ctx.send(f"Vote {title} has been cancelled.") + await ctx.send(core.cancel_vote(self.vote_manager, ctx.author, title)) + @commands.check(vote_is_enabled) @has_current_votes() @@ -345,14 +251,8 @@ async def check_current_votes(self, ctx): Return a list of all votes you have in this guild. :return: """ - with session_manager() as session: - embed = discord.Embed(title="Your current votes") - votes = session.execute(select(Votes.title).filter_by(author_id=ctx.author.id, guild_id=ctx.guild.id)).all() - body_string = "" - for title in votes: - body_string += f"{title[0]}\n" - embed.add_field(name="Vote Title", value=body_string, inline=False) - await ctx.send(embed=embed) + await ctx.send(embed=core.current_votes(ctx.author, ctx.guild)) + @currently_configuring() @vote.command(name="send") @@ -360,34 +260,8 @@ async def send_vote(self, ctx): """ Sends a vote to all users within the restrictions set with the current options added """ - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - - if not vote.is_ready(): - await ctx.send("Please add more than 1 option to vote for") - return - - self.vote_manager.configuring_votes.pop(ctx.author.id) - self.vote_manager.sent_votes[vote.id] = vote - - users = [x for x in ctx.guild.members if not x.bot] - if vote.target_voice_channel: - vc_users = discord.utils.get(ctx.guild.voice_channels, id=vote.target_voice_channel).members - users = list(set(vc_users) & set(users)) - if vote.target_roles: - role_users = [] - for role_id in vote.target_roles: - role = discord.utils.get(ctx.guild.roles, id=role_id) - role_users += role.members - role_users = list(dict.fromkeys(role_users)) - users = list(set(role_users) & set(users)) - for user in users: - try: - msg = await user.send(f"You have been asked to participate in this vote from {ctx.guild.name}.\nPlease react to make your choice (You can change your mind until the vote is closed)", embed=create_embed(vote)) - vote.register_sent(user.id, msg.id) - await add_reactions(vote, msg) - except discord.Forbidden: - logger.error(f"tried to send vote to user {user.id} but direct messages are turned off.") - await ctx.send(f"Sent vote to {len(users)} users") + await ctx.send(await core.send_vote(self.vote_manager, ctx.author, ctx.guild)) + @commands.check(vote_is_enabled) @has_current_votes() @@ -396,28 +270,14 @@ async def close(self, ctx, *, title): """ Ends a vote, and collects the results """ - vote_id = self.vote_manager.vote_lookup[(ctx.author.id, title)] - if vote_id not in self.vote_manager.sent_votes.keys(): - if ctx.author.id in self.vote_manager.configuring_votes.keys(): - await ctx.send(f"That vote has not been sent yet. Please send it to your audience with {koalabot.COMMAND_PREFIX}vote send {title}") - else: - await ctx.send("You have no votes of that title to close") - return - - vote = self.vote_manager.get_vote_from_id(vote_id) - results = await get_results(self.bot, vote) - self.vote_manager.cancel_sent_vote(vote.id) - embed = await make_result_embed(vote, results) - if vote.chair: - try: - chair = await self.bot.fetch_user(vote.chair) - await chair.send(embed=embed) - await ctx.send(f"Sent results to {chair}") - except discord.Forbidden: - await ctx.send("Chair does not accept direct messages, sending results here.") - await ctx.send(embed=embed) + msg = await core.close(self.bot, self.vote_manager, ctx.author) + if type(msg) is list: + await ctx.send(msg[0], embed=msg[1]) + elif type(msg) is discord.Embed: + await ctx.send(embed=msg) else: - await ctx.send(embed=embed) + await ctx.send(msg) + @commands.check(vote_is_enabled) @has_current_votes() @@ -426,44 +286,11 @@ async def check_results(self, ctx, *, title): """ Checks the results of a vote without closing it """ - vote_id = self.vote_manager.vote_lookup.get((ctx.author.id, title)) - if vote_id is None: - raise ValueError(f"{title} is not a valid vote title for user {ctx.author.name}") - - if vote_id not in self.vote_manager.sent_votes.keys(): - if ctx.author.id in self.vote_manager.configuring_votes.keys(): - await ctx.send( - f"That vote has not been sent yet. Please send it to your audience with {koalabot.COMMAND_PREFIX}vote send {title}") - else: - await ctx.send("You have no votes of that title to check") - return - - vote = self.vote_manager.get_vote_from_id(vote_id) - results = await get_results(self.bot, vote) - embed = await make_result_embed(vote, results) - await ctx.send(embed=embed) - - async def update_vote_message(self, message_id, user_id): - """ - Updates the vote message with the currently selected option - :param message_id: id of the message that was reacted on - :param user_id: id of the user who reacted - """ - vote = self.vote_manager.was_sent_to(message_id) - user = self.bot.get_user(user_id) - if vote and not user.bot: - msg = await user.fetch_message(message_id) - embed = msg.embeds[0] - choice = None - for reaction in msg.reactions: - if reaction.count > 1: - choice = reaction - break - if choice: - embed.set_footer(text=f"You will be voting for {choice.emoji} - {vote.options[VoteManager.emote_reference[choice.emoji]].head}") - else: - embed.set_footer(text="There are no valid choices selected") - await msg.edit(embed=embed) + msg = await core.results(self.bot, self.vote_manager, ctx.author, title) + if type(msg) is discord.Embed: + await ctx.send(embed=msg) + else: + await ctx.send(msg) async def setup(bot: koalabot) -> None: diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py new file mode 100644 index 00000000..4c13eee9 --- /dev/null +++ b/koala/cogs/voting/core.py @@ -0,0 +1,293 @@ +# Built-in/Generic Imports +import time + +# Libs +import discord +import parsedatetime.parsedatetime +from discord.ext import commands, tasks +from sqlalchemy import select, delete, update +from sqlalchemy.orm import Session + +# Own modules +import koalabot +from koala.db import assign_session, insert_extension +from .db import VoteManager, get_results, create_embed, add_reactions +from .log import logger +from .models import Votes +from .option import Option +from .utils import make_result_embed + +async def update_vote_message(bot: koalabot.KoalaBot, message_id, user_id): + """ + Updates the vote message with the currently selected option + :param message_id: id of the message that was reacted on + :param user_id: id of the user who reacted + """ + vote = bot.vote_manager.was_sent_to(message_id) + user = bot.bot.get_user(user_id) + if vote and not user.bot: + msg = await user.fetch_message(message_id) + embed = msg.embeds[0] + choice = None + for reaction in msg.reactions: + if reaction.count > 1: + choice = reaction + break + if choice: + embed.set_footer(text=f"You will be voting for {choice.emoji} - {vote.options[VoteManager.emote_reference[choice.emoji]].head}") + else: + embed.set_footer(text="There are no valid choices selected") + await msg.edit(embed=embed) + + +@assign_session +async def vote_end_loop(bot: koalabot.KoalaBot, vm: VoteManager, session: Session): + try: + now = time.time() + votes = session.execute(select(Votes.vote_id, Votes.author_id, Votes.guild_id, Votes.title, Votes.end_time) + .where(Votes.end_time < now)).all() + for v_id, a_id, g_id, title, end_time in votes: + if v_id in vm.sent_votes.keys(): + vote = vm.get_vote_from_id(v_id) + results = await get_results(bot, vote) + embed = await make_result_embed(vote, results) + try: + if vote.chair: + try: + chair = await bot.fetch_user(vote.chair) + await chair.send(f"Your vote {title} has closed") + await chair.send(embed=embed) + except discord.Forbidden: + user = await bot.fetch_user(vote.author) + await user.send(f"Your vote {title} has closed") + await user.send(embed=embed) + else: + try: + user = await bot.fetch_user(vote.author) + await user.send(f"Your vote {title} has closed") + await user.send(embed=embed) + except discord.Forbidden: + guild = await bot.fetch_guild(vote.guild) + user = await bot.fetch_user(guild.owner_id) + await user.send(f"A vote in your guild titled {title} has closed and the chair is unavailable.") + await user.send(embed=embed) + session.execute(delete(Votes).filter_by(vote_id=vote.id)) + session.commit() + vm.cancel_sent_vote(vote.id) + except Exception as e: + session.execute(update(Votes).filter_by(vote_id=vote.id).values(end_time=time.time() + 86400)) + session.commit() + logger.error(f"error in vote loop: {e}") + except Exception as e: + logger.error("Exception in outer vote loop: %s" % e, exc_info=e) + + +def vote(cmd): + if cmd is None: + return f"Please use `{koalabot.COMMAND_PREFIX}help vote` for more information" + + +@assign_session +def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author, guild, session: Session): + + # Key Error: author id in get_configuring_vote line 144 in db.py + # instantiating get_configuring_vote(ctx.author.id) in cog also gives same error so it's not the vm's problem + # guild_name = bot.get_guild(vm.get_configuring_vote(author.id).guild) + + # is there a reason we use this instead of guild.name + guild_name = author.guild.name + + # assertion error. I don't know how or what or why. They look the same sentence. + if vm.has_active_vote(author.id): + return f"You already have an active vote in {guild_name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." + + in_db = session.execute(select(Votes).filter_by(title=title, author_id=author.id)).all() + if in_db: + return f"You already have a vote with title {title} sent!" + + if len(title) > 200: + return "Title too long" + + vm.create_vote(author.id, guild.id, title) + return f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." + + +def set_roles(vm: VoteManager, author, role, action): + + # get_configuring_vote might break + vote = vm.get_configuring_vote(author.id) + + if action == "add": + vote.add_role(role.id) + return f"Vote will be sent to those with the {role.name} role" + + if action == "remove": + vote.remove_role(role.id) + return f"Vote will no longer be sent to those with the {role.name} role" + + +async def set_chair(vm: VoteManager, author, chair): + vote = vm.get_configuring_vote(author.id) + + if chair: + try: + await chair.send(f"You have been selected as the chair for vote titled {vote.title}") + vote.set_chair(chair.id) + return f"Set chair to {chair.name}" + except discord.Forbidden: + return "Chair not set as requested user is not accepting direct messages." + else: + vote.set_chair(None) + return "Results will be sent to the channel vote is closed in" + + +async def set_channel(vm: VoteManager, author, channel): + # get_configuring_vote might break + vote = vm.get_configuring_vote(author.id) + + if channel: + vote.set_vc(channel.id) + return f"Set target channel to {channel.name}" + else: + vote.set_vc() + return "Removed channel restriction on vote" + + +def add_option(vm: VoteManager, author, option_string): + # get_configuring_vote might break + vote = vm.get_configuring_vote(author.id) + + if len(vote.options) > 9: + return "Vote has maximum number of options already (10)" + + current_option_length = sum([len(x.head) + len(x.body) for x in vote.options]) + + if current_option_length + len(option_string) > 1500: + return "Option string is too long. The total length of all the vote options cannot be over 1500 characters." + + if "+" not in option_string: + return "Example usage: k!vote addOption option title+option description" + + header, body = option_string.split("+") + vote.add_option(Option(header, body, vm.generate_unique_opt_id())) + return f"Option {header} with description {body} added to vote" + + +def remove_option(vm: VoteManager, author, index): + # you know hte drill + vote = vm.get_configuring_vote(author.id) + vote.remove_option(index) + return f"Option number {index} removed" + + +def set_end_time(vm: VoteManager, author, time_string): + now = time.time() + vote = vm.get_configuring_vote(author.id) + cal = parsedatetime.Calendar() + end_time_readable = cal.parse(time_string)[0] + end_time = time.mktime(end_time_readable) + if (end_time - now) < 0: + return "You can't set a vote to end in the past" + # if (end_time - now) < 599: + # return "Please set the end time to be at least 10 minutes in the future." + vote.set_end_time(end_time) + return f"Vote set to end at {time.strftime('%Y-%m-%d %H:%M:%S', end_time_readable)} UTC" + + +async def preview(vm: VoteManager, ctx): + # get configuring vote potential error + vote = vm.get_configuring_vote(ctx.author.id) + msg = await ctx.send(embed=create_embed(vote)) + await add_reactions(vote, msg) + + +def cancel_vote(vm: VoteManager, author, title): + # i don't even know what errors this could have but there will be + v_id = vm.vote_lookup[(author.id, title)] + if v_id in vm.sent_votes.keys(): + vm.cancel_sent_vote(v_id) + else: + vm.cancel_configuring_vote(author.id) + return f"Vote {title} has been cancelled." + + +@assign_session +def current_votes(author, guild, session: Session): + embed = discord.Embed(title="Your current votes") + votes = session.execute(select(Votes.title).filter_by(author_id=author.id, guild_id=guild.id)).all() + body_string = "" + for title in votes: + body_string += f"{title[0]}\n" + embed.add_field(name="Vote Title", value=body_string, inline=False) + return embed + + +async def send_vote(vm: VoteManager, author, guild): + # configuring votes + vote = vm.get_configuring_vote(author.id) + + if not vote.is_ready(): + return "Please add more than 1 option to vote for" + + vm.configuring_votes.pop(author.id) + vm.sent_votes[vote.id] = vote + + users = [x for x in guild.members if not x.bot] + if vote.target_voice_channel: + vc_users = discord.utils.get(guild.voice_channels, id=vote.target_voice_channel).members + users = list(set(vc_users) & set(users)) + if vote.target_roles: + role_users = [] + for role_id in vote.target_roles: + role = discord.utils.get(guild.roles, id=role_id) + role_users += role.members + role_users = list(dict.fromkeys(role_users)) + users = list(set(role_users) & set(users)) + for user in users: + try: + msg = await user.send(f"You have been asked to participate in this vote from {guild.name}.\nPlease react to make your choice (You can change your mind until the vote is closed)", embed=create_embed(vote)) + vote.register_sent(user.id, msg.id) + await add_reactions(vote, msg) + except discord.Forbidden: + logger.error(f"tried to send vote to user {user.id} but direct messages are turned off.") + return f"Sent vote to {len(users)} users" + + +async def close(bot: koalabot.KoalaBot, vm: VoteManager, author, title): + vote_id = vm.vote_lookup[(author.id, title)] + if vote_id not in vm.sent_votes.keys(): + if author.id in vm.configuring_votes.keys(): + return f"That vote has not been sent yet. Please send it to your audience with {koalabot.COMMAND_PREFIX}vote send {title}" + else: + return "You have no votes of that title to close" + + vote = vm.get_vote_from_id(vote_id) + results = await get_results(bot, vote) + vm.cancel_sent_vote(vote.id) + embed = await make_result_embed(vote, results) + if vote.chair: + try: + chair = await bot.fetch_user(vote.chair) + await chair.send(embed=embed) + return f"Sent results to {chair}" + except discord.Forbidden: + return ["Chair does not accept direct messages, sending results here.", embed] + else: + return embed + + +async def results(bot: koalabot.KoalaBot, vm: VoteManager, author, title): + vote_id = vm.vote_lookup.get((author.id, title)) + if vote_id is None: + raise ValueError(f"{title} is not a valid vote title for user {author.name}") + + if vote_id not in vm.sent_votes.keys(): + if author.id in vm.configuring_votes.keys(): + return f"That vote has not been sent yet. Please send it to your audience with {koalabot.COMMAND_PREFIX}vote send {title}" + else: + return "You have no votes of that title to check" + + vote = vm.get_vote_from_id(vote_id) + results = await get_results(bot, vote) + embed = await make_result_embed(vote, results) + return embed \ No newline at end of file From 8ceebb4489f7dfd5b11e6d3a2f09ecb969e3f959 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 2 May 2023 20:55:34 +0100 Subject: [PATCH 02/41] test: initial core tests --- koala/cogs/voting/cog.py | 11 ++--- koala/cogs/voting/core.py | 8 ++-- tests/cogs/voting/test_core.py | 88 ++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 tests/cogs/voting/test_core.py diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 2fac7d1b..0b9c2715 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -5,23 +5,17 @@ Commented using reStructuredText (reST) """ # Built-in/Generic Imports -import time # Libs import discord -import parsedatetime.parsedatetime from discord.ext import commands, tasks -from sqlalchemy import select, delete, update # Own modules import koalabot -from koala.db import session_manager, insert_extension +from koala.db import insert_extension from . import core -from .db import VoteManager, get_results, create_embed, add_reactions +from .db import VoteManager from .log import logger -from .models import Votes -from .option import Option -from .utils import make_result_embed # Constants @@ -118,6 +112,7 @@ async def on_raw_reaction_remove(self, payload): await core.update_vote_message(self.bot, payload.message_id, payload.user_id) + # how do you even procc this @commands.check(koalabot.is_admin) @commands.check(vote_is_enabled) @commands.group(name="vote") diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 4c13eee9..4cbee98b 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -4,13 +4,12 @@ # Libs import discord import parsedatetime.parsedatetime -from discord.ext import commands, tasks from sqlalchemy import select, delete, update from sqlalchemy.orm import Session # Own modules import koalabot -from koala.db import assign_session, insert_extension +from koala.db import assign_session from .db import VoteManager, get_results, create_embed, add_reactions from .log import logger from .models import Votes @@ -82,6 +81,7 @@ async def vote_end_loop(bot: koalabot.KoalaBot, vm: VoteManager, session: Sessio logger.error("Exception in outer vote loop: %s" % e, exc_info=e) +# is needed? raises a CommandNotFound error def vote(cmd): if cmd is None: return f"Please use `{koalabot.COMMAND_PREFIX}help vote` for more information" @@ -94,10 +94,10 @@ def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author, guild, se # instantiating get_configuring_vote(ctx.author.id) in cog also gives same error so it's not the vm's problem # guild_name = bot.get_guild(vm.get_configuring_vote(author.id).guild) - # is there a reason we use this instead of guild.name + # is there a reason we use the above instead of this guild_name = author.guild.name - # assertion error. I don't know how or what or why. They look the same sentence. + # assertion error in test_cog. I don't know how or what or why. They look the same sentence. if vm.has_active_vote(author.id): return f"You already have an active vote in {guild_name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py new file mode 100644 index 00000000..97a73b97 --- /dev/null +++ b/tests/cogs/voting/test_core.py @@ -0,0 +1,88 @@ +#Libs +import discord +import discord.ext.test as dpytest +import pytest +import pytest_asyncio +from discord.ext import commands +from sqlalchemy import select +from koala.cogs import Voting +from koala.cogs.voting.models import Votes + +# Own modules +import koalabot +from koala.db import assign_session, session_manager, insert_extension +from tests.log import logger +from koala.cogs.voting import core + + +@pytest_asyncio.fixture(scope="function", autouse=True) +async def cog(bot: commands.Bot): + cog = Voting(bot) + await bot.add_cog(cog) + dpytest.configure(bot) + logger.info("Tests starting") + return cog + + +@pytest.mark.asyncio +async def test_update_vote_message(bot: commands.Bot): + pass + # await core.update_vote_message(bot) + + +# CommandNotFound error +# @pytest.mark.asyncio +# async def test_vote_command(): +# await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote") +# assert dpytest.verify().message().content(f"Please use `{koalabot.COMMAND_PREFIX}help vote` for more information") + + +def test_create_vote(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + assert core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) == f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." + + +@pytest.mark.asyncio +async def test_vote_already_created(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") + + assert core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) == f"You already have an active vote in {guild.name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." + + +@assign_session +@pytest.mark.asyncio +async def test_vote_already_sent(bot: commands.Bot, cog, session): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + session.add(Votes(vote_id=111, author_id=author.id, guild_id=guild.id, title="Test Vote")) + session.commit() + + assert core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) == "You already have a vote with title Test Vote sent!" + + +@pytest.mark.asyncio +async def test_add_role(bot: commands.Bot, cog, session): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + role: discord.Role = dpytest.back.make_role("testRole", guild, id_num=555) + + await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") + assert core.set_roles(cog.vote_manager, author, role, "add") == f"Vote will be sent to those with the {role.name} role" + + +@pytest.mark.asyncio +async def test_remove_role(bot: commands.Bot, cog, session): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + role: discord.Role = dpytest.back.make_role("testRole", guild, id_num=555) + + await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") + core.set_roles(cog.vote_manager, author, role, "add") + + assert core.set_roles(cog.vote_manager, author, role, "remove") == f"Vote will no longer be sent to those with the {role.name} role" \ No newline at end of file From dca52a4a91885dbe4c582393f3816c06e8767027 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 2 May 2023 22:28:26 +0100 Subject: [PATCH 03/41] debug: peek --- tests/cogs/voting/test_cog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cogs/voting/test_cog.py b/tests/cogs/voting/test_cog.py index 80f9800f..785b9221 100644 --- a/tests/cogs/voting/test_cog.py +++ b/tests/cogs/voting/test_cog.py @@ -39,6 +39,7 @@ async def test_discord_create_vote(): config = dpytest.get_config() guild = config.guilds[0] await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") + dpytest.sent_queue.peek() assert dpytest.verify().message().content( f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote`" f" to see how to configure it.") From d705e79538ced0eb28eeb4d4f92219e38ed55c11 Mon Sep 17 00:00:00 2001 From: Jack Draper <drapj002@gmail.com> Date: Tue, 2 May 2023 22:43:21 +0100 Subject: [PATCH 04/41] fix: voting group message issue --- koala/cogs/voting/cog.py | 3 ++- koala/cogs/voting/core.py | 6 ------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 0b9c2715..8d6ced80 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -120,7 +120,8 @@ async def vote(self, ctx): """ Use k!vote create <title> to create a vote! """ - await ctx.send(core.vote(ctx.invoked_subcommand)) + if ctx.invoked_subcommand is None: + await ctx.send(f"Please use `{koalabot.COMMAND_PREFIX}help vote` for more information") @commands.check(koalabot.is_admin) diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 4cbee98b..ed1352e3 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -81,12 +81,6 @@ async def vote_end_loop(bot: koalabot.KoalaBot, vm: VoteManager, session: Sessio logger.error("Exception in outer vote loop: %s" % e, exc_info=e) -# is needed? raises a CommandNotFound error -def vote(cmd): - if cmd is None: - return f"Please use `{koalabot.COMMAND_PREFIX}help vote` for more information" - - @assign_session def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author, guild, session: Session): From 4a744191586c70105098096e81527612fd0a9b94 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 2 May 2023 22:54:42 +0100 Subject: [PATCH 05/41] test: fix cog tests --- koala/cogs/voting/core.py | 2 +- tests/cogs/voting/test_cog.py | 5 ++--- tests/cogs/voting/test_core.py | 7 ------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index ed1352e3..0f578b8d 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -103,7 +103,7 @@ def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author, guild, se return "Title too long" vm.create_vote(author.id, guild.id, title) - return f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." + return f"Vote titled `{title}` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." def set_roles(vm: VoteManager, author, role, action): diff --git a/tests/cogs/voting/test_cog.py b/tests/cogs/voting/test_cog.py index 785b9221..ca906955 100644 --- a/tests/cogs/voting/test_cog.py +++ b/tests/cogs/voting/test_cog.py @@ -9,6 +9,7 @@ # Built-in/Generic Imports # Libs +import discord import discord.ext.test as dpytest import pytest import pytest_asyncio @@ -36,10 +37,8 @@ async def cog(bot: commands.Bot): @pytest.mark.asyncio async def test_discord_create_vote(): with session_manager() as session: - config = dpytest.get_config() - guild = config.guilds[0] + guild: discord.Guild = dpytest.get_config().guilds[0] await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") - dpytest.sent_queue.peek() assert dpytest.verify().message().content( f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote`" f" to see how to configure it.") diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index 97a73b97..290fd361 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -30,13 +30,6 @@ async def test_update_vote_message(bot: commands.Bot): # await core.update_vote_message(bot) -# CommandNotFound error -# @pytest.mark.asyncio -# async def test_vote_command(): -# await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote") -# assert dpytest.verify().message().content(f"Please use `{koalabot.COMMAND_PREFIX}help vote` for more information") - - def test_create_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] From 893fa854a276757ebac46a29e82f6c77d42ba4e8 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 2 May 2023 23:44:13 +0100 Subject: [PATCH 06/41] test: core chair and channel tests --- koala/cogs/voting/core.py | 4 +-- tests/cogs/voting/test_core.py | 60 ++++++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 0f578b8d..63b965a8 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -120,7 +120,7 @@ def set_roles(vm: VoteManager, author, role, action): return f"Vote will no longer be sent to those with the {role.name} role" -async def set_chair(vm: VoteManager, author, chair): +async def set_chair(vm: VoteManager, author, chair=None): vote = vm.get_configuring_vote(author.id) if chair: @@ -135,7 +135,7 @@ async def set_chair(vm: VoteManager, author, chair): return "Results will be sent to the channel vote is closed in" -async def set_channel(vm: VoteManager, author, channel): +def set_channel(vm: VoteManager, author, channel=None): # get_configuring_vote might break vote = vm.get_configuring_vote(author.id) diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index 290fd361..0fc52797 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -1,6 +1,7 @@ #Libs import discord import discord.ext.test as dpytest +import mock import pytest import pytest_asyncio from discord.ext import commands @@ -70,7 +71,7 @@ async def test_add_role(bot: commands.Bot, cog, session): @pytest.mark.asyncio -async def test_remove_role(bot: commands.Bot, cog, session): +async def test_remove_role(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] role: discord.Role = dpytest.back.make_role("testRole", guild, id_num=555) @@ -78,4 +79,59 @@ async def test_remove_role(bot: commands.Bot, cog, session): await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") core.set_roles(cog.vote_manager, author, role, "add") - assert core.set_roles(cog.vote_manager, author, role, "remove") == f"Vote will no longer be sent to those with the {role.name} role" \ No newline at end of file + assert core.set_roles(cog.vote_manager, author, role, "remove") == f"Vote will no longer be sent to those with the {role.name} role" + + +@pytest.mark.asyncio +async def test_set_chair(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + chair: discord.Member = guild.members[1] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert await core.set_chair(cog.vote_manager, author, chair) == f"Set chair to {chair.name}" + + +# failing because idk how to mock a blocked dm channel +@pytest.mark.asyncio +async def test_set_chair_no_dms(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + chair: discord.Member = guild.members[1] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + # dpytest.back.start_private_message + with mock.patch('discord.channel.DMChannel', mock.Mock(side_effect=Exception('discord.Forbidden'))): + with pytest.raises(discord.Forbidden, match="Chair not set as requested user is not accepting direct messages."): + await core.set_chair(cog.vote_manager, author, chair) + + +@pytest.mark.asyncio +async def test_set_no_chair(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert await core.set_chair(cog.vote_manager, author) == "Results will be sent to the channel vote is closed in" + + +# make_voice_channel doesn't exist even though it's in their documentation +@pytest.mark.asyncio +async def test_set_channel(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + channel = dpytest.back.make_voice_channel("Voice Channel", guild) + + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert core.set_channel(cog.vote_manager, author, channel) == f"Set target channel to {channel.name}" + + +@pytest.mark.asyncio +async def test_set_no_channel(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert core.set_channel(cog.vote_manager, author) == "Removed channel restriction on vote" \ No newline at end of file From 6345aa477c19896d43e9a4f48a1ec974da92d0f9 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Wed, 3 May 2023 00:12:49 +0100 Subject: [PATCH 07/41] test: core options, end time, stubs --- koala/cogs/voting/core.py | 21 ++--- tests/cogs/voting/test_core.py | 141 +++++++++++++++++++++++++++++++-- 2 files changed, 140 insertions(+), 22 deletions(-) diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 63b965a8..62ca51ef 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -83,15 +83,8 @@ async def vote_end_loop(bot: koalabot.KoalaBot, vm: VoteManager, session: Sessio @assign_session def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author, guild, session: Session): - - # Key Error: author id in get_configuring_vote line 144 in db.py - # instantiating get_configuring_vote(ctx.author.id) in cog also gives same error so it's not the vm's problem - # guild_name = bot.get_guild(vm.get_configuring_vote(author.id).guild) - - # is there a reason we use the above instead of this guild_name = author.guild.name - # assertion error in test_cog. I don't know how or what or why. They look the same sentence. if vm.has_active_vote(author.id): return f"You already have an active vote in {guild_name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." @@ -107,8 +100,6 @@ def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author, guild, se def set_roles(vm: VoteManager, author, role, action): - - # get_configuring_vote might break vote = vm.get_configuring_vote(author.id) if action == "add": @@ -136,7 +127,6 @@ async def set_chair(vm: VoteManager, author, chair=None): def set_channel(vm: VoteManager, author, channel=None): - # get_configuring_vote might break vote = vm.get_configuring_vote(author.id) if channel: @@ -148,7 +138,6 @@ def set_channel(vm: VoteManager, author, channel=None): def add_option(vm: VoteManager, author, option_string): - # get_configuring_vote might break vote = vm.get_configuring_vote(author.id) if len(vote.options) > 9: @@ -168,10 +157,12 @@ def add_option(vm: VoteManager, author, option_string): def remove_option(vm: VoteManager, author, index): - # you know hte drill vote = vm.get_configuring_vote(author.id) - vote.remove_option(index) - return f"Option number {index} removed" + try: + vote.remove_option(index) + return f"Option number {index} removed" + except IndexError: + return f"Option number {index} not found" def set_end_time(vm: VoteManager, author, time_string): @@ -189,14 +180,12 @@ def set_end_time(vm: VoteManager, author, time_string): async def preview(vm: VoteManager, ctx): - # get configuring vote potential error vote = vm.get_configuring_vote(ctx.author.id) msg = await ctx.send(embed=create_embed(vote)) await add_reactions(vote, msg) def cancel_vote(vm: VoteManager, author, title): - # i don't even know what errors this could have but there will be v_id = vm.vote_lookup[(author.id, title)] if v_id in vm.sent_votes.keys(): vm.cancel_sent_vote(v_id) diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index 0fc52797..27dd2bc3 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -100,7 +100,8 @@ async def test_set_chair_no_dms(bot: commands.Bot, cog): chair: discord.Member = guild.members[1] core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - # dpytest.back.start_private_message + # dpytest.back.start_private_message? + # pytest.raises is NOT the way to go here. the Forbidden is excepted, not thrown. with mock.patch('discord.channel.DMChannel', mock.Mock(side_effect=Exception('discord.Forbidden'))): with pytest.raises(discord.Forbidden, match="Chair not set as requested user is not accepting direct messages."): await core.set_chair(cog.vote_manager, author, chair) @@ -116,8 +117,7 @@ async def test_set_no_chair(bot: commands.Bot, cog): # make_voice_channel doesn't exist even though it's in their documentation -@pytest.mark.asyncio -async def test_set_channel(bot: commands.Bot, cog): +def test_set_channel(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] channel = dpytest.back.make_voice_channel("Voice Channel", guild) @@ -127,11 +127,140 @@ async def test_set_channel(bot: commands.Bot, cog): assert core.set_channel(cog.vote_manager, author, channel) == f"Set target channel to {channel.name}" -@pytest.mark.asyncio -async def test_set_no_channel(bot: commands.Bot, cog): +def test_set_no_channel(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - assert core.set_channel(cog.vote_manager, author) == "Removed channel restriction on vote" \ No newline at end of file + assert core.set_channel(cog.vote_manager, author) == "Removed channel restriction on vote" + + +def test_add_option(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert core.add_option(cog.vote_manager, author, "Option 1+Option description") == "Option Option 1 with description Option description added to vote" + +def test_add_option_wrong_formatting(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert core.add_option(cog.vote_manager, author, "Option 1") == "Example usage: k!vote addOption option title+option description" + + +def test_add_option_too_many(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + x = 0 + while (x < 11): + core.add_option(cog.vote_manager, author, "more+options") + x += 1 + + assert core.add_option(cog.vote_manager, author, "more options+please?") == "Vote has maximum number of options already (10)" + + +def test_add_option_too_long(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + test_option = "i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup+i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably." + x = 0 + while (x < 5): + core.add_option(cog.vote_manager, author, test_option) + x += 1 + + assert core.add_option(cog.vote_manager, author, test_option) == "Option string is too long. The total length of all the vote options cannot be over 1500 characters." + + +def test_remove_option(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + core.add_option(cog.vote_manager, author, "test+option") + + assert core.remove_option(cog.vote_manager, author, 0) == "Option number 0 removed" + + +def test_remove_option_bad(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert core.remove_option(cog.vote_manager, author, 0) == "Option number 0 not found" + + +def test_set_end_time(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert core.set_end_time(cog.vote_manager, author, "2222-12-30 13:30") == "Vote set to end at 2222-12-30 13:30:00 UTC" + + +def test_set_impossible_end_time(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert core.set_end_time(cog.vote_manager, author, "2020-01-15 12:50") == "You can't set a vote to end in the past" + + +def test_preview(bot: commands.Bot, cog): + # guild: discord.Guild = dpytest.get_config().guilds[0] + # author: discord.Member = guild.members[0] + # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + pass + + +def test_cancel_vote(bot: commands.Bot, cog): + # guild: discord.Guild = dpytest.get_config().guilds[0] + # author: discord.Member = guild.members[0] + # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + pass + + +def test_current_votes(bot: commands.Bot, cog): + # guild: discord.Guild = dpytest.get_config().guilds[0] + # author: discord.Member = guild.members[0] + # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + pass + + +@pytest.mark.asyncio +async def test_close(bot: commands.Bot, cog): + # guild: discord.Guild = dpytest.get_config().guilds[0] + # author: discord.Member = guild.members[0] + # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + pass + + +@pytest.mark.asyncio +async def test_send_vote(bot: commands.Bot, cog): + # guild: discord.Guild = dpytest.get_config().guilds[0] + # author: discord.Member = guild.members[0] + # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + pass + + +@pytest.mark.asyncio +async def test_get_results(bot: commands.Bot, cog): + # guild: discord.Guild = dpytest.get_config().guilds[0] + # author: discord.Member = guild.members[0] + # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + pass \ No newline at end of file From 24ee0a6ce90cc29f874f52ce627c8c566b4ff16a Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Sun, 7 May 2023 22:45:23 +0100 Subject: [PATCH 08/41] test: add remaining core tests --- koala/cogs/voting/cog.py | 6 +- koala/cogs/voting/core.py | 8 +- tests/cogs/voting/test_core.py | 137 ++++++++++++++++++++++++++------- 3 files changed, 117 insertions(+), 34 deletions(-) diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 8d6ced80..2cc5e557 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -14,7 +14,7 @@ import koalabot from koala.db import insert_extension from . import core -from .db import VoteManager +from .db import VoteManager, add_reactions from .log import logger @@ -225,7 +225,9 @@ async def preview_vote(self, ctx): """ Generates a preview of what users will see with the current configuration of the vote """ - await core.preview(self.vote_manager, ctx) + prev = core.preview(self.vote_manager, ctx.author) + msg = await ctx.send(embed=prev[0]) + await add_reactions(prev[1], msg) @commands.check(vote_is_enabled) diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 62ca51ef..29a05453 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -179,10 +179,9 @@ def set_end_time(vm: VoteManager, author, time_string): return f"Vote set to end at {time.strftime('%Y-%m-%d %H:%M:%S', end_time_readable)} UTC" -async def preview(vm: VoteManager, ctx): - vote = vm.get_configuring_vote(ctx.author.id) - msg = await ctx.send(embed=create_embed(vote)) - await add_reactions(vote, msg) +def preview(vm: VoteManager, author): + vote = vm.get_configuring_vote(author.id) + return [create_embed(vote), vote] def cancel_vote(vm: VoteManager, author, title): @@ -206,7 +205,6 @@ def current_votes(author, guild, session: Session): async def send_vote(vm: VoteManager, author, guild): - # configuring votes vote = vm.get_configuring_vote(author.id) if not vote.is_ready(): diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index 27dd2bc3..0156c54b 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -216,51 +216,134 @@ def test_set_impossible_end_time(bot: commands.Bot, cog): def test_preview(bot: commands.Bot, cog): - # guild: discord.Guild = dpytest.get_config().guilds[0] - # author: discord.Member = guild.members[0] - # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - pass + prev = core.preview(cog.vote_manager, author) + assert prev[0].title == "Test Vote" -def test_cancel_vote(bot: commands.Bot, cog): - # guild: discord.Guild = dpytest.get_config().guilds[0] - # author: discord.Member = guild.members[0] - # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) +@pytest.mark.asyncio +async def test_cancel_sent_vote(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.add_option(cog.vote_manager, author, "Option 1+Option description") + core.add_option(cog.vote_manager, author, "Option 2+Option description2") - pass + await core.send_vote(cog.vote_manager, author, guild) + assert core.cancel_vote(cog.vote_manager, author, "Test Vote") == "Vote Test Vote has been cancelled." -def test_current_votes(bot: commands.Bot, cog): - # guild: discord.Guild = dpytest.get_config().guilds[0] - # author: discord.Member = guild.members[0] - # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - pass +def test_cancel_unsent_vote(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert core.cancel_vote(cog.vote_manager, author, "Test Vote") == "Vote Test Vote has been cancelled." @pytest.mark.asyncio -async def test_close(bot: commands.Bot, cog): - # guild: discord.Guild = dpytest.get_config().guilds[0] - # author: discord.Member = guild.members[0] - # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) +async def test_current_votes(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + embed = core.current_votes(author, guild) + assert embed.title == "Your current votes" - pass + +@pytest.mark.asyncio +async def test_close_no_chair(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.add_option(cog.vote_manager, author, "Option 1+Option description") + core.add_option(cog.vote_manager, author, "Option 2+Option description2") + + await core.send_vote(cog.vote_manager, author, guild) + + embed = await core.close(bot, cog.vote_manager, author, "Test Vote") + assert embed.title == "Test Vote Results:" + assert embed.fields[0].name == "Option 1" + assert embed.fields[1].name == "Option 2" +@pytest.mark.asyncio +async def test_close_with_chair(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + chair: discord.Member = guild.members[1] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.add_option(cog.vote_manager, author, "Option 1+Option description") + core.add_option(cog.vote_manager, author, "Option 2+Option description2") + await core.set_chair(cog.vote_manager, author, chair) + + await core.send_vote(cog.vote_manager, author, guild) + + assert await core.close(bot, cog.vote_manager, author, "Test Vote") == f"Sent results to {chair}" + + @pytest.mark.asyncio async def test_send_vote(bot: commands.Bot, cog): - # guild: discord.Guild = dpytest.get_config().guilds[0] - # author: discord.Member = guild.members[0] - # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.add_option(cog.vote_manager, author, "Option 1+Option description") + core.add_option(cog.vote_manager, author, "Option 2+Option description2") + + # not sure how to assert DM sent + + assert await core.send_vote(cog.vote_manager, author, guild) == "Sent vote to 1 users" - pass + +@pytest.mark.asyncio +async def test_send_vote_bad_options(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + # no options + assert await core.send_vote(cog.vote_manager, author, guild) == "Please add more than 1 option to vote for" + + # only 1 option + core.add_option(cog.vote_manager, author, "Option 1+Option description") + assert await core.send_vote(cog.vote_manager, author, guild) == "Please add more than 1 option to vote for" @pytest.mark.asyncio async def test_get_results(bot: commands.Bot, cog): - # guild: discord.Guild = dpytest.get_config().guilds[0] - # author: discord.Member = guild.members[0] - # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.add_option(cog.vote_manager, author, "Option 1+Option description") + core.add_option(cog.vote_manager, author, "Option 2+Option description2") + + await core.send_vote(cog.vote_manager, author, guild) + + embed = await core.results(bot, cog.vote_manager, author, "Test Vote") + assert embed.title == "Test Vote Results:" + assert embed.fields[0].name == "Option 1" + assert embed.fields[1].name == "Option 2" + + +@pytest.mark.asyncio +async def test_results_vote_not_sent(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.add_option(cog.vote_manager, author, "Option 1+Option description") + core.add_option(cog.vote_manager, author, "Option 2+Option description2") + + assert await core.results(bot, cog.vote_manager, author, "Test Vote") == "That vote has not been sent yet. Please send it to your audience with k!vote send Test Vote" + + +@pytest.mark.asyncio +async def test_results_invalid_vote(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] - pass \ No newline at end of file + with pytest.raises(ValueError, match=f"invalid is not a valid vote title for user {author.name}"): + await core.results(bot, cog.vote_manager, author, "invalid") \ No newline at end of file From 024866bf778492f0efbf940a5659f763f148b805 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Sun, 7 May 2023 22:59:19 +0100 Subject: [PATCH 09/41] feat: api skeleton --- koala/cogs/voting/api.py | 86 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 koala/cogs/voting/api.py diff --git a/koala/cogs/voting/api.py b/koala/cogs/voting/api.py new file mode 100644 index 00000000..e1d94cf3 --- /dev/null +++ b/koala/cogs/voting/api.py @@ -0,0 +1,86 @@ +# Futures +# Built-in/Generic Imports +# Libs +from http.client import CREATED + +import discord +from aiohttp import web +from discord.ext.commands import Bot + +from koala.rest.api import parse_request, build_response +# Own modules +from . import core +from .log import logger + +# Constants +VOTING_ENDPOINT = 'voting' +CONFIG_ENDPOINT = 'config' +RESULTS_ENDOPINT = 'results' + +# Variables + +class VotingEndpoint: + """ + The API endpoints for Voting + """ + def __init__(self, bot): + self._bot = bot + + def register(self, app): + """ + Register the routes for the given application + todo: review aiohttp 'views' and see if they are a better idea + :param app: The aiohttp.web.Application (likely of the sub app) + :return: app + """ + app.add_routes([web.post('/{endpoint}'.format(endpoint=CONFIG_ENDPOINT), self.post_new_vote), + web.get('/{endpoint}'.format(endpoint=CONFIG_ENDPOINT), self.get_current_votes), + web.post('/{endpoint}'.format(endpoint=RESULTS_ENDOPINT), self.post_close_results), + web.get('/{endpoint}'.format(endpoint=RESULTS_ENDOPINT), self.get_results)]) + return app + + + @parse_request + async def post_new_vote(self): + """ + Create a new vote. + :param : + :return: The list of ScheduledActivities + """ + pass + + + @parse_request + async def get_current_votes(self): + """ + Gets list of open votes. + """ + pass + + + @parse_request + async def post_close_results(self): + """ + Gets results and closes the vote. + """ + pass + + + @parse_request + async def get_results(self): + """ + Gets results, but does not close the vote. + """ + pass + + +def setup(bot: Bot): + """ + Load this cog to the KoalaBot. + :param bot: the bot client for KoalaBot + """ + sub_app = web.Application() + endpoint = VotingEndpoint(bot) + endpoint.register(sub_app) + getattr(bot, "koala_web_app").add_subapp('/{extension}'.format(extension=VOTING_ENDPOINT), sub_app) + logger.info("Voting API is ready.") From bdaaec24f064ee4d18352f34ef42154d748127a0 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Mon, 22 May 2023 13:37:29 +0100 Subject: [PATCH 10/41] refactor: use id as params --- koala/cogs/voting/cog.py | 28 +++--- koala/cogs/voting/core.py | 78 +++++++++-------- tests/cogs/voting/test_core.py | 150 ++++++++++++++++----------------- 3 files changed, 131 insertions(+), 125 deletions(-) diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 2cc5e557..2d093c44 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -132,7 +132,7 @@ async def start_vote(self, ctx, *, title): Creates a new vote :param title: The title of the vote """ - await ctx.send(core.start_vote(self.bot, self.vote_manager, title, ctx.author, ctx.guild)) + await ctx.send(core.start_vote(self.bot, self.vote_manager, title, ctx.author.id, ctx.guild.id)) @currently_configuring() @@ -144,7 +144,7 @@ async def add_role(self, ctx, *, role: discord.Role): If no roles are added, the vote will go to all users in a guild (unless a target voice channel has been set) :param role: role id (e.g. 135496683009081345) or a role ping (e.g. @Student) """ - await ctx.send(core.set_roles(self.vote_manager, ctx.author, role, "add")) + await ctx.send(core.set_roles(self.bot, self.vote_manager, ctx.author.id, ctx.guild.id, role.id, "add")) @currently_configuring() @@ -155,7 +155,7 @@ async def remove_role(self, ctx, *, role: discord.Role): Removes a role to the list of roles the vote will be sent to :param role: role id (e.g. 135496683009081345) or a role ping (e.g. @Student) """ - await ctx.send(core.set_roles(self.vote_manager, ctx.author, role, "remove")) + await ctx.send(core.set_roles(self.bot, self.vote_manager, ctx.author.id, ctx.guild.id, role.id, "remove")) @currently_configuring() @@ -167,7 +167,7 @@ async def set_chair(self, ctx, *, chair: discord.Member = None): If no chair defaults to sending the message to the channel the vote is closed in :param chair: user id (e.g. 135496683009081345) or ping (e.g. @ito#8813) """ - await ctx.send(await core.set_chair(self.vote_manager, ctx.author, chair)) + await ctx.send(await core.set_chair(self.bot, self.vote_manager, ctx.author.id, getattr(chair, 'id', None))) @currently_configuring() @@ -179,7 +179,7 @@ async def set_channel(self, ctx, *, channel: discord.VoiceChannel = None): If there isn't one set votes will go to all users in a guild (unless target roles have been added) :param channel: channel id (e.g. 135496683009081345) or mention (e.g. #cool-channel) """ - await ctx.send(core.set_channel(self.vote_manager, ctx.author, channel)) + await ctx.send(core.set_channel(self.bot, self.vote_manager, ctx.author.id, channel.id)) @currently_configuring() @@ -191,7 +191,7 @@ async def add_option(self, ctx, *, option_string): separate the title and description with a "+" e.g. option title+option description :param option_string: a title and description for the option separated by a '+' """ - await ctx.send(core.add_option(self.vote_manager, ctx.author, option_string)) + await ctx.send(core.add_option(self.vote_manager, ctx.author.id, option_string)) @currently_configuring() @@ -202,7 +202,7 @@ async def remove_option(self, ctx, index: int): Removes an option from a vote based on it's index :param index: the number of the option """ - await ctx.send(core.remove_option(self.vote_manager, ctx.author, index)) + await ctx.send(core.remove_option(self.vote_manager, ctx.author.id, index)) @currently_configuring() @@ -215,7 +215,7 @@ async def set_end_time(self, ctx, *, time_string): :param time_string: string representing a time e.g. "2021-03-22 12:56" or "tomorrow at 10am" or "in 5 days and 15 minutes" :return: """ - await ctx.send(core.set_end_time(self.vote_manager, ctx.author, time_string)) + await ctx.send(core.set_end_time(self.vote_manager, ctx.author.id, time_string)) @currently_configuring() @@ -225,7 +225,7 @@ async def preview_vote(self, ctx): """ Generates a preview of what users will see with the current configuration of the vote """ - prev = core.preview(self.vote_manager, ctx.author) + prev = core.preview(self.vote_manager, ctx.author.id) msg = await ctx.send(embed=prev[0]) await add_reactions(prev[1], msg) @@ -238,7 +238,7 @@ async def cancel_vote(self, ctx, *, title): Cancels a vote you are setting up or have sent :param title: title of the vote to cancel """ - await ctx.send(core.cancel_vote(self.vote_manager, ctx.author, title)) + await ctx.send(core.cancel_vote(self.vote_manager, ctx.author.id, title)) @commands.check(vote_is_enabled) @@ -249,7 +249,7 @@ async def check_current_votes(self, ctx): Return a list of all votes you have in this guild. :return: """ - await ctx.send(embed=core.current_votes(ctx.author, ctx.guild)) + await ctx.send(embed=core.current_votes(ctx.author.id, ctx.guild.id)) @currently_configuring() @@ -258,7 +258,7 @@ async def send_vote(self, ctx): """ Sends a vote to all users within the restrictions set with the current options added """ - await ctx.send(await core.send_vote(self.vote_manager, ctx.author, ctx.guild)) + await ctx.send(await core.send_vote(self.bot, self.vote_manager, ctx.author.id, ctx.guild)) @commands.check(vote_is_enabled) @@ -268,7 +268,7 @@ async def close(self, ctx, *, title): """ Ends a vote, and collects the results """ - msg = await core.close(self.bot, self.vote_manager, ctx.author) + msg = await core.close(self.bot, self.vote_manager, ctx.author.id, title) if type(msg) is list: await ctx.send(msg[0], embed=msg[1]) elif type(msg) is discord.Embed: @@ -284,7 +284,7 @@ async def check_results(self, ctx, *, title): """ Checks the results of a vote without closing it """ - msg = await core.results(self.bot, self.vote_manager, ctx.author, title) + msg = await core.results(self.bot, self.vote_manager, ctx.author.id, title) if type(msg) is discord.Embed: await ctx.send(embed=msg) else: diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 29a05453..5eed7d41 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -82,39 +82,41 @@ async def vote_end_loop(bot: koalabot.KoalaBot, vm: VoteManager, session: Sessio @assign_session -def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author, guild, session: Session): - guild_name = author.guild.name +def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author_id, guild_id, session: Session): + guild_name = bot.get_guild(guild_id) - if vm.has_active_vote(author.id): + if vm.has_active_vote(author_id): return f"You already have an active vote in {guild_name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." - in_db = session.execute(select(Votes).filter_by(title=title, author_id=author.id)).all() + in_db = session.execute(select(Votes).filter_by(title=title, author_id=author_id)).all() if in_db: return f"You already have a vote with title {title} sent!" if len(title) > 200: return "Title too long" - vm.create_vote(author.id, guild.id, title) - return f"Vote titled `{title}` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." + vm.create_vote(author_id, guild_id, title) + return f"Vote titled `{title}` created for guild {guild_name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." -def set_roles(vm: VoteManager, author, role, action): - vote = vm.get_configuring_vote(author.id) +def set_roles(bot: koalabot.KoalaBot, vm: VoteManager, author_id, guild_id, role_id, action): + vote = vm.get_configuring_vote(author_id) + role = bot.get_guild(guild_id).get_role(role_id) if action == "add": - vote.add_role(role.id) + vote.add_role(role_id) return f"Vote will be sent to those with the {role.name} role" if action == "remove": - vote.remove_role(role.id) + vote.remove_role(role_id) return f"Vote will no longer be sent to those with the {role.name} role" -async def set_chair(vm: VoteManager, author, chair=None): - vote = vm.get_configuring_vote(author.id) +async def set_chair(bot: koalabot.KoalaBot, vm: VoteManager, author_id, chair_id=None): + vote = vm.get_configuring_vote(author_id) - if chair: + if chair_id: + chair = bot.get_user(chair_id) try: await chair.send(f"You have been selected as the chair for vote titled {vote.title}") vote.set_chair(chair.id) @@ -126,8 +128,9 @@ async def set_chair(vm: VoteManager, author, chair=None): return "Results will be sent to the channel vote is closed in" -def set_channel(vm: VoteManager, author, channel=None): - vote = vm.get_configuring_vote(author.id) +def set_channel(bot: koalabot.KoalaBot, vm: VoteManager, author_id, channel_id=None): + vote = vm.get_configuring_vote(author_id) + channel = bot.get_channel(channel_id) if channel: vote.set_vc(channel.id) @@ -137,8 +140,8 @@ def set_channel(vm: VoteManager, author, channel=None): return "Removed channel restriction on vote" -def add_option(vm: VoteManager, author, option_string): - vote = vm.get_configuring_vote(author.id) +def add_option(vm: VoteManager, author_id, option_string): + vote = vm.get_configuring_vote(author_id) if len(vote.options) > 9: return "Vote has maximum number of options already (10)" @@ -156,8 +159,8 @@ def add_option(vm: VoteManager, author, option_string): return f"Option {header} with description {body} added to vote" -def remove_option(vm: VoteManager, author, index): - vote = vm.get_configuring_vote(author.id) +def remove_option(vm: VoteManager, author_id, index): + vote = vm.get_configuring_vote(author_id) try: vote.remove_option(index) return f"Option number {index} removed" @@ -165,9 +168,9 @@ def remove_option(vm: VoteManager, author, index): return f"Option number {index} not found" -def set_end_time(vm: VoteManager, author, time_string): +def set_end_time(vm: VoteManager, author_id, time_string): now = time.time() - vote = vm.get_configuring_vote(author.id) + vote = vm.get_configuring_vote(author_id) cal = parsedatetime.Calendar() end_time_readable = cal.parse(time_string)[0] end_time = time.mktime(end_time_readable) @@ -179,24 +182,24 @@ def set_end_time(vm: VoteManager, author, time_string): return f"Vote set to end at {time.strftime('%Y-%m-%d %H:%M:%S', end_time_readable)} UTC" -def preview(vm: VoteManager, author): - vote = vm.get_configuring_vote(author.id) +def preview(vm: VoteManager, author_id): + vote = vm.get_configuring_vote(author_id) return [create_embed(vote), vote] -def cancel_vote(vm: VoteManager, author, title): - v_id = vm.vote_lookup[(author.id, title)] +def cancel_vote(vm: VoteManager, author_id, title): + v_id = vm.vote_lookup[(author_id, title)] if v_id in vm.sent_votes.keys(): vm.cancel_sent_vote(v_id) else: - vm.cancel_configuring_vote(author.id) + vm.cancel_configuring_vote(author_id) return f"Vote {title} has been cancelled." @assign_session -def current_votes(author, guild, session: Session): +def current_votes(author_id, guild_id, session: Session): embed = discord.Embed(title="Your current votes") - votes = session.execute(select(Votes.title).filter_by(author_id=author.id, guild_id=guild.id)).all() + votes = session.execute(select(Votes.title).filter_by(author_id=author_id, guild_id=guild_id)).all() body_string = "" for title in votes: body_string += f"{title[0]}\n" @@ -204,13 +207,14 @@ def current_votes(author, guild, session: Session): return embed -async def send_vote(vm: VoteManager, author, guild): - vote = vm.get_configuring_vote(author.id) +async def send_vote(bot: koalabot.KoalaBot, vm: VoteManager, author_id, guild_id): + vote = vm.get_configuring_vote(author_id) + guild = bot.get_guild(guild_id) if not vote.is_ready(): return "Please add more than 1 option to vote for" - vm.configuring_votes.pop(author.id) + vm.configuring_votes.pop(author_id) vm.sent_votes[vote.id] = vote users = [x for x in guild.members if not x.bot] @@ -234,10 +238,10 @@ async def send_vote(vm: VoteManager, author, guild): return f"Sent vote to {len(users)} users" -async def close(bot: koalabot.KoalaBot, vm: VoteManager, author, title): - vote_id = vm.vote_lookup[(author.id, title)] +async def close(bot: koalabot.KoalaBot, vm: VoteManager, author_id, title): + vote_id = vm.vote_lookup[(author_id, title)] if vote_id not in vm.sent_votes.keys(): - if author.id in vm.configuring_votes.keys(): + if author_id in vm.configuring_votes.keys(): return f"That vote has not been sent yet. Please send it to your audience with {koalabot.COMMAND_PREFIX}vote send {title}" else: return "You have no votes of that title to close" @@ -257,8 +261,10 @@ async def close(bot: koalabot.KoalaBot, vm: VoteManager, author, title): return embed -async def results(bot: koalabot.KoalaBot, vm: VoteManager, author, title): - vote_id = vm.vote_lookup.get((author.id, title)) +async def results(bot: koalabot.KoalaBot, vm: VoteManager, author_id, title): + vote_id = vm.vote_lookup.get((author_id, title)) + author = bot.get_user(author_id) + if vote_id is None: raise ValueError(f"{title} is not a valid vote title for user {author.name}") diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index 0156c54b..8922c4ac 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -35,7 +35,7 @@ def test_create_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - assert core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) == f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." + assert core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) == f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." @pytest.mark.asyncio @@ -45,7 +45,7 @@ async def test_vote_already_created(bot: commands.Bot, cog): await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") - assert core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) == f"You already have an active vote in {guild.name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." + assert core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) == f"You already have an active vote in {guild.name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." @assign_session @@ -57,7 +57,7 @@ async def test_vote_already_sent(bot: commands.Bot, cog, session): session.add(Votes(vote_id=111, author_id=author.id, guild_id=guild.id, title="Test Vote")) session.commit() - assert core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) == "You already have a vote with title Test Vote sent!" + assert core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) == "You already have a vote with title Test Vote sent!" @pytest.mark.asyncio @@ -67,7 +67,7 @@ async def test_add_role(bot: commands.Bot, cog, session): role: discord.Role = dpytest.back.make_role("testRole", guild, id_num=555) await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") - assert core.set_roles(cog.vote_manager, author, role, "add") == f"Vote will be sent to those with the {role.name} role" + assert core.set_roles(bot, cog.vote_manager, author.id, guild.id, role.id, "add") == f"Vote will be sent to those with the {role.name} role" @pytest.mark.asyncio @@ -77,9 +77,9 @@ async def test_remove_role(bot: commands.Bot, cog): role: discord.Role = dpytest.back.make_role("testRole", guild, id_num=555) await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") - core.set_roles(cog.vote_manager, author, role, "add") + core.set_roles(bot, cog.vote_manager, author.id, guild.id, role.id, "add") - assert core.set_roles(cog.vote_manager, author, role, "remove") == f"Vote will no longer be sent to those with the {role.name} role" + assert core.set_roles(bot, cog.vote_manager, author.id, guild.id, role.id, "remove") == f"Vote will no longer be sent to those with the {role.name} role" @pytest.mark.asyncio @@ -87,9 +87,9 @@ async def test_set_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] chair: discord.Member = guild.members[1] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert await core.set_chair(cog.vote_manager, author, chair) == f"Set chair to {chair.name}" + assert await core.set_chair(bot, cog.vote_manager, author.id, chair.id) == f"Set chair to {chair.name}" # failing because idk how to mock a blocked dm channel @@ -98,22 +98,22 @@ async def test_set_chair_no_dms(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] chair: discord.Member = guild.members[1] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) # dpytest.back.start_private_message? # pytest.raises is NOT the way to go here. the Forbidden is excepted, not thrown. with mock.patch('discord.channel.DMChannel', mock.Mock(side_effect=Exception('discord.Forbidden'))): with pytest.raises(discord.Forbidden, match="Chair not set as requested user is not accepting direct messages."): - await core.set_chair(cog.vote_manager, author, chair) + await core.set_chair(bot, cog.vote_manager, author.id, chair.id) @pytest.mark.asyncio async def test_set_no_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert await core.set_chair(cog.vote_manager, author) == "Results will be sent to the channel vote is closed in" + assert await core.set_chair(bot, cog.vote_manager, author.id) == "Results will be sent to the channel vote is closed in" # make_voice_channel doesn't exist even though it's in their documentation @@ -122,105 +122,105 @@ def test_set_channel(bot: commands.Bot, cog): author: discord.Member = guild.members[0] channel = dpytest.back.make_voice_channel("Voice Channel", guild) - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert core.set_channel(cog.vote_manager, author, channel) == f"Set target channel to {channel.name}" + assert core.set_channel(bot, cog.vote_manager, author.id, channel.id) == f"Set target channel to {channel.name}" def test_set_no_channel(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert core.set_channel(cog.vote_manager, author) == "Removed channel restriction on vote" + assert core.set_channel(bot, cog.vote_manager, author.id) == "Removed channel restriction on vote" def test_add_option(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert core.add_option(cog.vote_manager, author, "Option 1+Option description") == "Option Option 1 with description Option description added to vote" + assert core.add_option(cog.vote_manager, author.id, "Option 1+Option description") == "Option Option 1 with description Option description added to vote" def test_add_option_wrong_formatting(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert core.add_option(cog.vote_manager, author, "Option 1") == "Example usage: k!vote addOption option title+option description" + assert core.add_option(cog.vote_manager, author.id, "Option 1") == "Example usage: k!vote addOption option title+option description" def test_add_option_too_many(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) x = 0 while (x < 11): - core.add_option(cog.vote_manager, author, "more+options") + core.add_option(cog.vote_manager, author.id, "more+options") x += 1 - assert core.add_option(cog.vote_manager, author, "more options+please?") == "Vote has maximum number of options already (10)" + assert core.add_option(cog.vote_manager, author.id, "more options+please?") == "Vote has maximum number of options already (10)" def test_add_option_too_long(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) test_option = "i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup+i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably." x = 0 while (x < 5): - core.add_option(cog.vote_manager, author, test_option) + core.add_option(cog.vote_manager, author.id, test_option) x += 1 - assert core.add_option(cog.vote_manager, author, test_option) == "Option string is too long. The total length of all the vote options cannot be over 1500 characters." + assert core.add_option(cog.vote_manager, author.id, test_option) == "Option string is too long. The total length of all the vote options cannot be over 1500 characters." def test_remove_option(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - core.add_option(cog.vote_manager, author, "test+option") + core.add_option(cog.vote_manager, author.id, "test+option") - assert core.remove_option(cog.vote_manager, author, 0) == "Option number 0 removed" + assert core.remove_option(cog.vote_manager, author.id, 0) == "Option number 0 removed" def test_remove_option_bad(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert core.remove_option(cog.vote_manager, author, 0) == "Option number 0 not found" + assert core.remove_option(cog.vote_manager, author.id, 0) == "Option number 0 not found" def test_set_end_time(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert core.set_end_time(cog.vote_manager, author, "2222-12-30 13:30") == "Vote set to end at 2222-12-30 13:30:00 UTC" + assert core.set_end_time(cog.vote_manager, author.id, "2222-12-30 13:30") == "Vote set to end at 2222-12-30 13:30:00 UTC" def test_set_impossible_end_time(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert core.set_end_time(cog.vote_manager, author, "2020-01-15 12:50") == "You can't set a vote to end in the past" + assert core.set_end_time(cog.vote_manager, author.id, "2020-01-15 12:50") == "You can't set a vote to end in the past" def test_preview(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - prev = core.preview(cog.vote_manager, author) + prev = core.preview(cog.vote_manager, author.id) assert prev[0].title == "Test Vote" @@ -228,30 +228,30 @@ def test_preview(bot: commands.Bot, cog): async def test_cancel_sent_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - core.add_option(cog.vote_manager, author, "Option 1+Option description") - core.add_option(cog.vote_manager, author, "Option 2+Option description2") + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.add_option(cog.vote_manager, author.id, "Option 1+Option description") + core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") - await core.send_vote(cog.vote_manager, author, guild) + await core.send_vote(bot, cog.vote_manager, author.id, guild.id) - assert core.cancel_vote(cog.vote_manager, author, "Test Vote") == "Vote Test Vote has been cancelled." + assert core.cancel_vote(cog.vote_manager, author.id, "Test Vote") == "Vote Test Vote has been cancelled." def test_cancel_unsent_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert core.cancel_vote(cog.vote_manager, author, "Test Vote") == "Vote Test Vote has been cancelled." + assert core.cancel_vote(cog.vote_manager, author.id, "Test Vote") == "Vote Test Vote has been cancelled." @pytest.mark.asyncio async def test_current_votes(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - embed = core.current_votes(author, guild) + embed = core.current_votes(author.id, guild.id) assert embed.title == "Your current votes" @@ -259,13 +259,13 @@ async def test_current_votes(bot: commands.Bot, cog): async def test_close_no_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - core.add_option(cog.vote_manager, author, "Option 1+Option description") - core.add_option(cog.vote_manager, author, "Option 2+Option description2") + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.add_option(cog.vote_manager, author.id, "Option 1+Option description") + core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") - await core.send_vote(cog.vote_manager, author, guild) + await core.send_vote(bot, cog.vote_manager, author.id, guild.id) - embed = await core.close(bot, cog.vote_manager, author, "Test Vote") + embed = await core.close(bot, cog.vote_manager, author.id, "Test Vote") assert embed.title == "Test Vote Results:" assert embed.fields[0].name == "Option 1" assert embed.fields[1].name == "Option 2" @@ -276,54 +276,54 @@ async def test_close_with_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] chair: discord.Member = guild.members[1] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - core.add_option(cog.vote_manager, author, "Option 1+Option description") - core.add_option(cog.vote_manager, author, "Option 2+Option description2") - await core.set_chair(cog.vote_manager, author, chair) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.add_option(cog.vote_manager, author.id, "Option 1+Option description") + core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") + await core.set_chair(bot, cog.vote_manager, author.id, chair.id) - await core.send_vote(cog.vote_manager, author, guild) + await core.send_vote(bot, cog.vote_manager, author.id, guild.id) - assert await core.close(bot, cog.vote_manager, author, "Test Vote") == f"Sent results to {chair}" + assert await core.close(bot, cog.vote_manager, author.id, "Test Vote") == f"Sent results to {chair}" @pytest.mark.asyncio async def test_send_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - core.add_option(cog.vote_manager, author, "Option 1+Option description") - core.add_option(cog.vote_manager, author, "Option 2+Option description2") + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.add_option(cog.vote_manager, author.id, "Option 1+Option description") + core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") # not sure how to assert DM sent - assert await core.send_vote(cog.vote_manager, author, guild) == "Sent vote to 1 users" + assert await core.send_vote(bot, cog.vote_manager, author.id, guild.id) == "Sent vote to 1 users" @pytest.mark.asyncio async def test_send_vote_bad_options(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) # no options - assert await core.send_vote(cog.vote_manager, author, guild) == "Please add more than 1 option to vote for" + assert await core.send_vote(bot, cog.vote_manager, author.id, guild.id) == "Please add more than 1 option to vote for" # only 1 option - core.add_option(cog.vote_manager, author, "Option 1+Option description") - assert await core.send_vote(cog.vote_manager, author, guild) == "Please add more than 1 option to vote for" + core.add_option(cog.vote_manager, author.id, "Option 1+Option description") + assert await core.send_vote(bot, cog.vote_manager, author.id, guild.id) == "Please add more than 1 option to vote for" @pytest.mark.asyncio async def test_get_results(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - core.add_option(cog.vote_manager, author, "Option 1+Option description") - core.add_option(cog.vote_manager, author, "Option 2+Option description2") + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.add_option(cog.vote_manager, author.id, "Option 1+Option description") + core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") - await core.send_vote(cog.vote_manager, author, guild) + await core.send_vote(bot, cog.vote_manager, author.id, guild.id) - embed = await core.results(bot, cog.vote_manager, author, "Test Vote") + embed = await core.results(bot, cog.vote_manager, author.id, "Test Vote") assert embed.title == "Test Vote Results:" assert embed.fields[0].name == "Option 1" assert embed.fields[1].name == "Option 2" @@ -333,11 +333,11 @@ async def test_get_results(bot: commands.Bot, cog): async def test_results_vote_not_sent(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - core.add_option(cog.vote_manager, author, "Option 1+Option description") - core.add_option(cog.vote_manager, author, "Option 2+Option description2") + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.add_option(cog.vote_manager, author.id, "Option 1+Option description") + core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") - assert await core.results(bot, cog.vote_manager, author, "Test Vote") == "That vote has not been sent yet. Please send it to your audience with k!vote send Test Vote" + assert await core.results(bot, cog.vote_manager, author.id, "Test Vote") == "That vote has not been sent yet. Please send it to your audience with k!vote send Test Vote" @pytest.mark.asyncio @@ -346,4 +346,4 @@ async def test_results_invalid_vote(bot: commands.Bot, cog): author: discord.Member = guild.members[0] with pytest.raises(ValueError, match=f"invalid is not a valid vote title for user {author.name}"): - await core.results(bot, cog.vote_manager, author, "invalid") \ No newline at end of file + await core.results(bot, cog.vote_manager, author.id, "invalid") \ No newline at end of file From 358ad62ec5dccd04b020b0ec11f3c93627412971 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Mon, 22 May 2023 14:12:08 +0100 Subject: [PATCH 11/41] feat: set up vote api --- koala/cogs/voting/api.py | 82 ++++++++++++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 12 deletions(-) diff --git a/koala/cogs/voting/api.py b/koala/cogs/voting/api.py index e1d94cf3..aabcefe5 100644 --- a/koala/cogs/voting/api.py +++ b/koala/cogs/voting/api.py @@ -1,7 +1,8 @@ # Futures # Built-in/Generic Imports # Libs -from http.client import CREATED +from http.client import CREATED, OK +from typing import Optional import discord from aiohttp import web @@ -40,38 +41,95 @@ def register(self, app): return app +# how to do vote_manager @parse_request - async def post_new_vote(self): + async def post_new_vote(self, title, author_id, guild_id, options: list, + roles: Optional[list], chair_id: Optional[int], end_time: Optional[str]): """ Create a new vote. - :param : - :return: The list of ScheduledActivities + :param title: The name of the vote + :param author_id: The author id of the vote + :param guild_id: The guild id of the vote + :param options: The options for the votes + :param roles: The target roles for the votes + :param chair_id: The chair id of the vote + :param end_time: The end time of the vote + :return: """ - pass + try: + await core.start_vote(self, self.vote_manager, title, author_id, guild_id) + + for item in options: + core.add_option(self.vote_manager, author_id, item) + + if roles: + for item in roles: + core.set_roles(self, self.vote_manager, author_id, guild_id, item, "add") + + if chair_id: + core.set_chair(self, self.vote_manager, author_id, chair_id) + + if end_time: + core.set_end_time(self.vote_manager, author_id, end_time) + + await core.send_vote(self, self.vote_manager, author_id, guild_id) + + except Exception as e: + logger.error(e) + raise web.HTTPUnprocessableEntity() + + return build_response(CREATED, {'message': f'Vote {title} created'}) @parse_request - async def get_current_votes(self): + def get_current_votes(self, author_id, guild_id): """ Gets list of open votes. + :param author_id: The author id of the vote + :param guild: The guild id of the vote + :return: """ - pass + try: + embed = core.current_votes(author_id, guild_id) + except Exception as e: + logger.error(e) + raise web.HTTPUnprocessableEntity() + + return build_response(OK, embed) @parse_request - async def post_close_results(self): + async def post_close_results(self, author_id, title): """ Gets results and closes the vote. + :param author_id: The author id of the vote + :param title: The title of the vote + :return: """ - pass - + try: + embed = await core.close(self, self.vote_manager, author_id, title) + except Exception as e: + logger.error(e) + raise web.HTTPUnprocessableEntity() + + return build_response(OK, embed) + @parse_request - async def get_results(self): + async def get_results(self, author_id, title): """ Gets results, but does not close the vote. + :param author_id: The author id of the vote + :param title: The title of the vote + :return: """ - pass + try: + embed = core.results(self, self.vote_manager, author_id, title) + except Exception as e: + logger.error(e) + raise web.HTTPUnprocessableEntity() + + return build_response(OK, embed) def setup(bot: Bot): From 1e9b0926ed64ad83f10440efcdadd6a2e10244ba Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Mon, 22 May 2023 14:57:38 +0100 Subject: [PATCH 12/41] test: add no vote test for current_votes --- tests/cogs/voting/test_core.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index 8922c4ac..1622f41b 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -245,8 +245,7 @@ def test_cancel_unsent_vote(bot: commands.Bot, cog): assert core.cancel_vote(cog.vote_manager, author.id, "Test Vote") == "Vote Test Vote has been cancelled." -@pytest.mark.asyncio -async def test_current_votes(bot: commands.Bot, cog): +def test_current_votes(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) @@ -255,6 +254,15 @@ async def test_current_votes(bot: commands.Bot, cog): assert embed.title == "Your current votes" +def test_current_votes_no_votes(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + embed = core.current_votes(author.id, guild.id) + assert embed.title == "Your current votes" + assert embed.description == "No current votes" + + @pytest.mark.asyncio async def test_close_no_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] From 77fff9698b6c6b7c62612da9ba164a6c6404d2e3 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Mon, 22 May 2023 21:52:25 +0100 Subject: [PATCH 13/41] feat: vote_manager (vm) all in core --- koala/cogs/voting/cog.py | 38 ++++----- koala/cogs/voting/core.py | 43 ++++++---- tests/cogs/voting/test_cog.py | 14 ++-- tests/cogs/voting/test_core.py | 148 ++++++++++++++++----------------- 4 files changed, 128 insertions(+), 115 deletions(-) diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 2d093c44..c2288087 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -29,20 +29,18 @@ def currently_configuring(): :return: True if the user has an active vote, false if not """ async def predicate(ctx): - cog = ctx.command.cog if koalabot.is_dm_channel(ctx): return False - return ctx.author.id in cog.vote_manager.configuring_votes.keys() and cog.vote_manager.configuring_votes[ctx.author.id].guild == ctx.guild.id + return ctx.author.id in core.vm.configuring_votes.keys() and core.vm.configuring_votes[ctx.author.id].guild == ctx.guild.id return commands.check(predicate) def has_current_votes(): async def predicate(ctx): - cog = ctx.command.cog if koalabot.is_dm_channel(ctx): return False - return ctx.author.id in map(lambda x: x[0], cog.vote_manager.vote_lookup.keys()) + return ctx.author.id in map(lambda x: x[0], core.vm.vote_lookup.keys()) return commands.check(predicate) @@ -71,8 +69,8 @@ def __init__(self, bot): """ self.bot = bot insert_extension("Vote", 0, True, True) - self.vote_manager = VoteManager() - self.vote_manager.load_from_db() + # self.vote_manager = VoteManager() + # self.vote_manager.load_from_db() self.running = False @commands.Cog.listener() @@ -132,7 +130,7 @@ async def start_vote(self, ctx, *, title): Creates a new vote :param title: The title of the vote """ - await ctx.send(core.start_vote(self.bot, self.vote_manager, title, ctx.author.id, ctx.guild.id)) + await ctx.send(core.start_vote(self.bot, title, ctx.author.id, ctx.guild.id)) @currently_configuring() @@ -144,7 +142,7 @@ async def add_role(self, ctx, *, role: discord.Role): If no roles are added, the vote will go to all users in a guild (unless a target voice channel has been set) :param role: role id (e.g. 135496683009081345) or a role ping (e.g. @Student) """ - await ctx.send(core.set_roles(self.bot, self.vote_manager, ctx.author.id, ctx.guild.id, role.id, "add")) + await ctx.send(core.set_roles(self.bot, ctx.author.id, ctx.guild.id, role.id, "add")) @currently_configuring() @@ -155,7 +153,7 @@ async def remove_role(self, ctx, *, role: discord.Role): Removes a role to the list of roles the vote will be sent to :param role: role id (e.g. 135496683009081345) or a role ping (e.g. @Student) """ - await ctx.send(core.set_roles(self.bot, self.vote_manager, ctx.author.id, ctx.guild.id, role.id, "remove")) + await ctx.send(core.set_roles(self.bot, ctx.author.id, ctx.guild.id, role.id, "remove")) @currently_configuring() @@ -167,7 +165,7 @@ async def set_chair(self, ctx, *, chair: discord.Member = None): If no chair defaults to sending the message to the channel the vote is closed in :param chair: user id (e.g. 135496683009081345) or ping (e.g. @ito#8813) """ - await ctx.send(await core.set_chair(self.bot, self.vote_manager, ctx.author.id, getattr(chair, 'id', None))) + await ctx.send(await core.set_chair(self.bot, ctx.author.id, getattr(chair, 'id', None))) @currently_configuring() @@ -179,9 +177,11 @@ async def set_channel(self, ctx, *, channel: discord.VoiceChannel = None): If there isn't one set votes will go to all users in a guild (unless target roles have been added) :param channel: channel id (e.g. 135496683009081345) or mention (e.g. #cool-channel) """ - await ctx.send(core.set_channel(self.bot, self.vote_manager, ctx.author.id, channel.id)) + await ctx.send(core.set_channel(self.bot, ctx.author.id, channel.id)) +# PROCESS THE + IN OPTIONS HERE +# NO + IN CORE!!! @currently_configuring() @commands.check(vote_is_enabled) @vote.command(name="addOption") @@ -191,7 +191,7 @@ async def add_option(self, ctx, *, option_string): separate the title and description with a "+" e.g. option title+option description :param option_string: a title and description for the option separated by a '+' """ - await ctx.send(core.add_option(self.vote_manager, ctx.author.id, option_string)) + await ctx.send(core.add_option(ctx.author.id, option_string)) @currently_configuring() @@ -202,7 +202,7 @@ async def remove_option(self, ctx, index: int): Removes an option from a vote based on it's index :param index: the number of the option """ - await ctx.send(core.remove_option(self.vote_manager, ctx.author.id, index)) + await ctx.send(core.remove_option(ctx.author.id, index)) @currently_configuring() @@ -215,7 +215,7 @@ async def set_end_time(self, ctx, *, time_string): :param time_string: string representing a time e.g. "2021-03-22 12:56" or "tomorrow at 10am" or "in 5 days and 15 minutes" :return: """ - await ctx.send(core.set_end_time(self.vote_manager, ctx.author.id, time_string)) + await ctx.send(core.set_end_time(ctx.author.id, time_string)) @currently_configuring() @@ -225,7 +225,7 @@ async def preview_vote(self, ctx): """ Generates a preview of what users will see with the current configuration of the vote """ - prev = core.preview(self.vote_manager, ctx.author.id) + prev = core.preview(ctx.author.id) msg = await ctx.send(embed=prev[0]) await add_reactions(prev[1], msg) @@ -238,7 +238,7 @@ async def cancel_vote(self, ctx, *, title): Cancels a vote you are setting up or have sent :param title: title of the vote to cancel """ - await ctx.send(core.cancel_vote(self.vote_manager, ctx.author.id, title)) + await ctx.send(core.cancel_vote(ctx.author.id, title)) @commands.check(vote_is_enabled) @@ -258,7 +258,7 @@ async def send_vote(self, ctx): """ Sends a vote to all users within the restrictions set with the current options added """ - await ctx.send(await core.send_vote(self.bot, self.vote_manager, ctx.author.id, ctx.guild)) + await ctx.send(await core.send_vote(self.bot, ctx.author.id, ctx.guild)) @commands.check(vote_is_enabled) @@ -268,7 +268,7 @@ async def close(self, ctx, *, title): """ Ends a vote, and collects the results """ - msg = await core.close(self.bot, self.vote_manager, ctx.author.id, title) + msg = await core.close(self.bot, ctx.author.id, title) if type(msg) is list: await ctx.send(msg[0], embed=msg[1]) elif type(msg) is discord.Embed: @@ -284,7 +284,7 @@ async def check_results(self, ctx, *, title): """ Checks the results of a vote without closing it """ - msg = await core.results(self.bot, self.vote_manager, ctx.author.id, title) + msg = await core.results(self.bot, ctx.author.id, title) if type(msg) is discord.Embed: await ctx.send(embed=msg) else: diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 5eed7d41..92ae1995 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -16,6 +16,11 @@ from .option import Option from .utils import make_result_embed + +vm = VoteManager() +vm.load_from_db() + + async def update_vote_message(bot: koalabot.KoalaBot, message_id, user_id): """ Updates the vote message with the currently selected option @@ -40,7 +45,7 @@ async def update_vote_message(bot: koalabot.KoalaBot, message_id, user_id): @assign_session -async def vote_end_loop(bot: koalabot.KoalaBot, vm: VoteManager, session: Session): +async def vote_end_loop(bot: koalabot.KoalaBot, session: Session): try: now = time.time() votes = session.execute(select(Votes.vote_id, Votes.author_id, Votes.guild_id, Votes.title, Votes.end_time) @@ -82,7 +87,7 @@ async def vote_end_loop(bot: koalabot.KoalaBot, vm: VoteManager, session: Sessio @assign_session -def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author_id, guild_id, session: Session): +def start_vote(bot: koalabot.KoalaBot, title, author_id, guild_id, session: Session): guild_name = bot.get_guild(guild_id) if vm.has_active_vote(author_id): @@ -99,7 +104,7 @@ def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author_id, guild_ return f"Vote titled `{title}` created for guild {guild_name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." -def set_roles(bot: koalabot.KoalaBot, vm: VoteManager, author_id, guild_id, role_id, action): +def set_roles(bot: koalabot.KoalaBot, author_id, guild_id, role_id, action): vote = vm.get_configuring_vote(author_id) role = bot.get_guild(guild_id).get_role(role_id) @@ -112,7 +117,7 @@ def set_roles(bot: koalabot.KoalaBot, vm: VoteManager, author_id, guild_id, role return f"Vote will no longer be sent to those with the {role.name} role" -async def set_chair(bot: koalabot.KoalaBot, vm: VoteManager, author_id, chair_id=None): +async def set_chair(bot: koalabot.KoalaBot, author_id, chair_id=None): vote = vm.get_configuring_vote(author_id) if chair_id: @@ -128,7 +133,7 @@ async def set_chair(bot: koalabot.KoalaBot, vm: VoteManager, author_id, chair_id return "Results will be sent to the channel vote is closed in" -def set_channel(bot: koalabot.KoalaBot, vm: VoteManager, author_id, channel_id=None): +def set_channel(bot: koalabot.KoalaBot, author_id, channel_id=None): vote = vm.get_configuring_vote(author_id) channel = bot.get_channel(channel_id) @@ -140,7 +145,10 @@ def set_channel(bot: koalabot.KoalaBot, vm: VoteManager, author_id, channel_id=N return "Removed channel restriction on vote" -def add_option(vm: VoteManager, author_id, option_string): +# OPTION ATTRIBUTES TITLE, DESCRPTION (OBJECT!) +# NO MORE + + +def add_option(author_id, option_string): vote = vm.get_configuring_vote(author_id) if len(vote.options) > 9: @@ -159,7 +167,7 @@ def add_option(vm: VoteManager, author_id, option_string): return f"Option {header} with description {body} added to vote" -def remove_option(vm: VoteManager, author_id, index): +def remove_option(author_id, index): vote = vm.get_configuring_vote(author_id) try: vote.remove_option(index) @@ -168,7 +176,7 @@ def remove_option(vm: VoteManager, author_id, index): return f"Option number {index} not found" -def set_end_time(vm: VoteManager, author_id, time_string): +def set_end_time(author_id, time_string): now = time.time() vote = vm.get_configuring_vote(author_id) cal = parsedatetime.Calendar() @@ -182,12 +190,12 @@ def set_end_time(vm: VoteManager, author_id, time_string): return f"Vote set to end at {time.strftime('%Y-%m-%d %H:%M:%S', end_time_readable)} UTC" -def preview(vm: VoteManager, author_id): +def preview(author_id): vote = vm.get_configuring_vote(author_id) return [create_embed(vote), vote] -def cancel_vote(vm: VoteManager, author_id, title): +def cancel_vote(author_id, title): v_id = vm.vote_lookup[(author_id, title)] if v_id in vm.sent_votes.keys(): vm.cancel_sent_vote(v_id) @@ -201,13 +209,16 @@ def current_votes(author_id, guild_id, session: Session): embed = discord.Embed(title="Your current votes") votes = session.execute(select(Votes.title).filter_by(author_id=author_id, guild_id=guild_id)).all() body_string = "" - for title in votes: - body_string += f"{title[0]}\n" - embed.add_field(name="Vote Title", value=body_string, inline=False) + if len(votes) > 0: + for title in votes: + body_string += f"{title[0]}\n" + embed.add_field(name="Vote Title", value=body_string, inline=False) + else: + embed.description = "No current votes" return embed -async def send_vote(bot: koalabot.KoalaBot, vm: VoteManager, author_id, guild_id): +async def send_vote(bot: koalabot.KoalaBot, author_id, guild_id): vote = vm.get_configuring_vote(author_id) guild = bot.get_guild(guild_id) @@ -238,7 +249,7 @@ async def send_vote(bot: koalabot.KoalaBot, vm: VoteManager, author_id, guild_id return f"Sent vote to {len(users)} users" -async def close(bot: koalabot.KoalaBot, vm: VoteManager, author_id, title): +async def close(bot: koalabot.KoalaBot, author_id, title): vote_id = vm.vote_lookup[(author_id, title)] if vote_id not in vm.sent_votes.keys(): if author_id in vm.configuring_votes.keys(): @@ -261,7 +272,7 @@ async def close(bot: koalabot.KoalaBot, vm: VoteManager, author_id, title): return embed -async def results(bot: koalabot.KoalaBot, vm: VoteManager, author_id, title): +async def results(bot: koalabot.KoalaBot, author_id, title): vote_id = vm.vote_lookup.get((author_id, title)) author = bot.get_user(author_id) diff --git a/tests/cogs/voting/test_cog.py b/tests/cogs/voting/test_cog.py index ca906955..6870f6ad 100644 --- a/tests/cogs/voting/test_cog.py +++ b/tests/cogs/voting/test_cog.py @@ -80,14 +80,16 @@ async def test_discord_vote_add_and_remove_role(cog): assert dpytest.verify().message().content( f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how " f"to configure it.") + await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote addRole {guild.roles[0].id}") assert dpytest.verify().message().content(f"Vote will be sent to those with the {guild.roles[0].name} role") - vote = cog.vote_manager.get_configuring_vote(guild.members[0].id) - assert guild.roles[0].id in vote.target_roles - await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote removeRole {guild.roles[0].id}") - assert dpytest.verify().message().content( - f"Vote will no longer be sent to those with the {guild.roles[0].name} role") - assert guild.roles[0].id not in vote.target_roles + # vote = cog.vote_manager.get_configuring_vote(guild.members[0].id) + # assert guild.roles[0].id in vote.target_roles + + # await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote removeRole {guild.roles[0].id}") + # assert dpytest.verify().message().content( + # f"Vote will no longer be sent to those with the {guild.roles[0].name} role") + # assert guild.roles[0].id not in vote.target_roles @pytest.mark.asyncio diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index 1622f41b..a79f6338 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -35,7 +35,7 @@ def test_create_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - assert core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) == f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." + assert core.start_vote(bot, "Test Vote", author.id, guild.id) == f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." @pytest.mark.asyncio @@ -45,7 +45,7 @@ async def test_vote_already_created(bot: commands.Bot, cog): await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") - assert core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) == f"You already have an active vote in {guild.name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." + assert core.start_vote(bot, "Test Vote", author.id, guild.id) == f"You already have an active vote in {guild.name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." @assign_session @@ -57,7 +57,7 @@ async def test_vote_already_sent(bot: commands.Bot, cog, session): session.add(Votes(vote_id=111, author_id=author.id, guild_id=guild.id, title="Test Vote")) session.commit() - assert core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) == "You already have a vote with title Test Vote sent!" + assert core.start_vote(bot, "Test Vote", author.id, guild.id) == "You already have a vote with title Test Vote sent!" @pytest.mark.asyncio @@ -67,7 +67,7 @@ async def test_add_role(bot: commands.Bot, cog, session): role: discord.Role = dpytest.back.make_role("testRole", guild, id_num=555) await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") - assert core.set_roles(bot, cog.vote_manager, author.id, guild.id, role.id, "add") == f"Vote will be sent to those with the {role.name} role" + assert core.set_roles(bot, author.id, guild.id, role.id, "add") == f"Vote will be sent to those with the {role.name} role" @pytest.mark.asyncio @@ -77,9 +77,9 @@ async def test_remove_role(bot: commands.Bot, cog): role: discord.Role = dpytest.back.make_role("testRole", guild, id_num=555) await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") - core.set_roles(bot, cog.vote_manager, author.id, guild.id, role.id, "add") + core.set_roles(bot, author.id, guild.id, role.id, "add") - assert core.set_roles(bot, cog.vote_manager, author.id, guild.id, role.id, "remove") == f"Vote will no longer be sent to those with the {role.name} role" + assert core.set_roles(bot, author.id, guild.id, role.id, "remove") == f"Vote will no longer be sent to those with the {role.name} role" @pytest.mark.asyncio @@ -87,9 +87,9 @@ async def test_set_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] chair: discord.Member = guild.members[1] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert await core.set_chair(bot, cog.vote_manager, author.id, chair.id) == f"Set chair to {chair.name}" + assert await core.set_chair(bot, author.id, chair.id) == f"Set chair to {chair.name}" # failing because idk how to mock a blocked dm channel @@ -98,22 +98,22 @@ async def test_set_chair_no_dms(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] chair: discord.Member = guild.members[1] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) # dpytest.back.start_private_message? # pytest.raises is NOT the way to go here. the Forbidden is excepted, not thrown. with mock.patch('discord.channel.DMChannel', mock.Mock(side_effect=Exception('discord.Forbidden'))): with pytest.raises(discord.Forbidden, match="Chair not set as requested user is not accepting direct messages."): - await core.set_chair(bot, cog.vote_manager, author.id, chair.id) + await core.set_chair(bot, author.id, chair.id) @pytest.mark.asyncio async def test_set_no_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert await core.set_chair(bot, cog.vote_manager, author.id) == "Results will be sent to the channel vote is closed in" + assert await core.set_chair(bot, author.id) == "Results will be sent to the channel vote is closed in" # make_voice_channel doesn't exist even though it's in their documentation @@ -122,105 +122,105 @@ def test_set_channel(bot: commands.Bot, cog): author: discord.Member = guild.members[0] channel = dpytest.back.make_voice_channel("Voice Channel", guild) - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.set_channel(bot, cog.vote_manager, author.id, channel.id) == f"Set target channel to {channel.name}" + assert core.set_channel(bot, author.id, channel.id) == f"Set target channel to {channel.name}" def test_set_no_channel(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.set_channel(bot, cog.vote_manager, author.id) == "Removed channel restriction on vote" + assert core.set_channel(bot, author.id) == "Removed channel restriction on vote" def test_add_option(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.add_option(cog.vote_manager, author.id, "Option 1+Option description") == "Option Option 1 with description Option description added to vote" + assert core.add_option(author.id, "Option 1+Option description") == "Option Option 1 with description Option description added to vote" def test_add_option_wrong_formatting(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.add_option(cog.vote_manager, author.id, "Option 1") == "Example usage: k!vote addOption option title+option description" + assert core.add_option(author.id, "Option 1") == "Example usage: k!vote addOption option title+option description" def test_add_option_too_many(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) x = 0 while (x < 11): - core.add_option(cog.vote_manager, author.id, "more+options") + core.add_option(author.id, "more+options") x += 1 - assert core.add_option(cog.vote_manager, author.id, "more options+please?") == "Vote has maximum number of options already (10)" + assert core.add_option(author.id, "more options+please?") == "Vote has maximum number of options already (10)" def test_add_option_too_long(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) test_option = "i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup+i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably." x = 0 while (x < 5): - core.add_option(cog.vote_manager, author.id, test_option) + core.add_option(author.id, test_option) x += 1 - assert core.add_option(cog.vote_manager, author.id, test_option) == "Option string is too long. The total length of all the vote options cannot be over 1500 characters." + assert core.add_option(author.id, test_option) == "Option string is too long. The total length of all the vote options cannot be over 1500 characters." def test_remove_option(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(cog.vote_manager, author.id, "test+option") + core.add_option(author.id, "test+option") - assert core.remove_option(cog.vote_manager, author.id, 0) == "Option number 0 removed" + assert core.remove_option(author.id, 0) == "Option number 0 removed" def test_remove_option_bad(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.remove_option(cog.vote_manager, author.id, 0) == "Option number 0 not found" + assert core.remove_option(author.id, 0) == "Option number 0 not found" def test_set_end_time(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.set_end_time(cog.vote_manager, author.id, "2222-12-30 13:30") == "Vote set to end at 2222-12-30 13:30:00 UTC" + assert core.set_end_time(author.id, "2222-12-30 13:30") == "Vote set to end at 2222-12-30 13:30:00 UTC" def test_set_impossible_end_time(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.set_end_time(cog.vote_manager, author.id, "2020-01-15 12:50") == "You can't set a vote to end in the past" + assert core.set_end_time(author.id, "2020-01-15 12:50") == "You can't set a vote to end in the past" def test_preview(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - prev = core.preview(cog.vote_manager, author.id) + prev = core.preview(author.id) assert prev[0].title == "Test Vote" @@ -228,27 +228,27 @@ def test_preview(bot: commands.Bot, cog): async def test_cancel_sent_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - core.add_option(cog.vote_manager, author.id, "Option 1+Option description") - core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") + core.start_vote(bot, "Test Vote", author.id, guild.id) + core.add_option(author.id, "Option 1+Option description") + core.add_option(author.id, "Option 2+Option description2") - await core.send_vote(bot, cog.vote_manager, author.id, guild.id) + await core.send_vote(bot, author.id, guild.id) - assert core.cancel_vote(cog.vote_manager, author.id, "Test Vote") == "Vote Test Vote has been cancelled." + assert core.cancel_vote(author.id, "Test Vote") == "Vote Test Vote has been cancelled." def test_cancel_unsent_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.cancel_vote(cog.vote_manager, author.id, "Test Vote") == "Vote Test Vote has been cancelled." + assert core.cancel_vote(author.id, "Test Vote") == "Vote Test Vote has been cancelled." def test_current_votes(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) embed = core.current_votes(author.id, guild.id) assert embed.title == "Your current votes" @@ -267,13 +267,13 @@ def test_current_votes_no_votes(bot: commands.Bot, cog): async def test_close_no_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - core.add_option(cog.vote_manager, author.id, "Option 1+Option description") - core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") + core.start_vote(bot, "Test Vote", author.id, guild.id) + core.add_option(author.id, "Option 1+Option description") + core.add_option(author.id, "Option 2+Option description2") - await core.send_vote(bot, cog.vote_manager, author.id, guild.id) + await core.send_vote(bot, author.id, guild.id) - embed = await core.close(bot, cog.vote_manager, author.id, "Test Vote") + embed = await core.close(bot, author.id, "Test Vote") assert embed.title == "Test Vote Results:" assert embed.fields[0].name == "Option 1" assert embed.fields[1].name == "Option 2" @@ -284,54 +284,54 @@ async def test_close_with_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] chair: discord.Member = guild.members[1] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - core.add_option(cog.vote_manager, author.id, "Option 1+Option description") - core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") - await core.set_chair(bot, cog.vote_manager, author.id, chair.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) + core.add_option(author.id, "Option 1+Option description") + core.add_option(author.id, "Option 2+Option description2") + await core.set_chair(bot, author.id, chair.id) - await core.send_vote(bot, cog.vote_manager, author.id, guild.id) + await core.send_vote(bot, author.id, guild.id) - assert await core.close(bot, cog.vote_manager, author.id, "Test Vote") == f"Sent results to {chair}" + assert await core.close(bot, author.id, "Test Vote") == f"Sent results to {chair}" @pytest.mark.asyncio async def test_send_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - core.add_option(cog.vote_manager, author.id, "Option 1+Option description") - core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") + core.start_vote(bot, "Test Vote", author.id, guild.id) + core.add_option(author.id, "Option 1+Option description") + core.add_option(author.id, "Option 2+Option description2") # not sure how to assert DM sent - assert await core.send_vote(bot, cog.vote_manager, author.id, guild.id) == "Sent vote to 1 users" + assert await core.send_vote(bot, author.id, guild.id) == "Sent vote to 1 users" @pytest.mark.asyncio async def test_send_vote_bad_options(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) # no options - assert await core.send_vote(bot, cog.vote_manager, author.id, guild.id) == "Please add more than 1 option to vote for" + assert await core.send_vote(bot, author.id, guild.id) == "Please add more than 1 option to vote for" # only 1 option - core.add_option(cog.vote_manager, author.id, "Option 1+Option description") - assert await core.send_vote(bot, cog.vote_manager, author.id, guild.id) == "Please add more than 1 option to vote for" + core.add_option(author.id, "Option 1+Option description") + assert await core.send_vote(bot, author.id, guild.id) == "Please add more than 1 option to vote for" @pytest.mark.asyncio async def test_get_results(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - core.add_option(cog.vote_manager, author.id, "Option 1+Option description") - core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") + core.start_vote(bot, "Test Vote", author.id, guild.id) + core.add_option(author.id, "Option 1+Option description") + core.add_option(author.id, "Option 2+Option description2") - await core.send_vote(bot, cog.vote_manager, author.id, guild.id) + await core.send_vote(bot, author.id, guild.id) - embed = await core.results(bot, cog.vote_manager, author.id, "Test Vote") + embed = await core.results(bot, author.id, "Test Vote") assert embed.title == "Test Vote Results:" assert embed.fields[0].name == "Option 1" assert embed.fields[1].name == "Option 2" @@ -341,11 +341,11 @@ async def test_get_results(bot: commands.Bot, cog): async def test_results_vote_not_sent(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - core.add_option(cog.vote_manager, author.id, "Option 1+Option description") - core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") + core.start_vote(bot, "Test Vote", author.id, guild.id) + core.add_option(author.id, "Option 1+Option description") + core.add_option(author.id, "Option 2+Option description2") - assert await core.results(bot, cog.vote_manager, author.id, "Test Vote") == "That vote has not been sent yet. Please send it to your audience with k!vote send Test Vote" + assert await core.results(bot, author.id, "Test Vote") == "That vote has not been sent yet. Please send it to your audience with k!vote send Test Vote" @pytest.mark.asyncio @@ -354,4 +354,4 @@ async def test_results_invalid_vote(bot: commands.Bot, cog): author: discord.Member = guild.members[0] with pytest.raises(ValueError, match=f"invalid is not a valid vote title for user {author.name}"): - await core.results(bot, cog.vote_manager, author.id, "invalid") \ No newline at end of file + await core.results(bot, author.id, "invalid") \ No newline at end of file From 228d9f14b0cff903107ee5a30a95fe02fb10f508 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 23 May 2023 01:33:07 +0100 Subject: [PATCH 14/41] refactor: vote options as dict inst of str --- koala/cogs/voting/cog.py | 7 ++++- koala/cogs/voting/core.py | 15 +++++----- tests/cogs/voting/test_cog.py | 13 +++++---- tests/cogs/voting/test_core.py | 52 +++++++++++++++++++--------------- 4 files changed, 50 insertions(+), 37 deletions(-) diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index c2288087..15053b6c 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -191,7 +191,12 @@ async def add_option(self, ctx, *, option_string): separate the title and description with a "+" e.g. option title+option description :param option_string: a title and description for the option separated by a '+' """ - await ctx.send(core.add_option(ctx.author.id, option_string)) + if "+" not in option_string: + await ctx.send("Example usage: k!vote addOption option title+option description") + else: + header, body = option_string.split("+") + option = {'header': header, 'body': body} + await ctx.send(core.add_option(ctx.author.id, option)) @currently_configuring() diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 92ae1995..3db8bd9f 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -148,23 +148,24 @@ def set_channel(bot: koalabot.KoalaBot, author_id, channel_id=None): # OPTION ATTRIBUTES TITLE, DESCRPTION (OBJECT!) # NO MORE + -def add_option(author_id, option_string): +def add_option(author_id, option): vote = vm.get_configuring_vote(author_id) if len(vote.options) > 9: return "Vote has maximum number of options already (10)" + if 'header' not in option or 'body' not in option: + return "Option should have both header and body." + current_option_length = sum([len(x.head) + len(x.body) for x in vote.options]) - if current_option_length + len(option_string) > 1500: + if current_option_length + len(option['header']) + len(option['body']) > 1500: return "Option string is too long. The total length of all the vote options cannot be over 1500 characters." - if "+" not in option_string: - return "Example usage: k!vote addOption option title+option description" + # moved check for '+' in string to cog - header, body = option_string.split("+") - vote.add_option(Option(header, body, vm.generate_unique_opt_id())) - return f"Option {header} with description {body} added to vote" + vote.add_option(Option(option['header'], option['body'], vm.generate_unique_opt_id())) + return f"Option {option['header']} with description {option['body']} added to vote" def remove_option(author_id, index): diff --git a/tests/cogs/voting/test_cog.py b/tests/cogs/voting/test_cog.py index 6870f6ad..b52d0fa2 100644 --- a/tests/cogs/voting/test_cog.py +++ b/tests/cogs/voting/test_cog.py @@ -19,6 +19,7 @@ # Own modules import koalabot from koala.cogs import Voting +from koala.cogs.voting import core from koala.cogs.voting.models import Votes from koala.db import session_manager, insert_extension from tests.log import logger @@ -83,13 +84,13 @@ async def test_discord_vote_add_and_remove_role(cog): await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote addRole {guild.roles[0].id}") assert dpytest.verify().message().content(f"Vote will be sent to those with the {guild.roles[0].name} role") - # vote = cog.vote_manager.get_configuring_vote(guild.members[0].id) - # assert guild.roles[0].id in vote.target_roles + vote = core.vm.get_configuring_vote(guild.members[0].id) + assert guild.roles[0].id in vote.target_roles - # await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote removeRole {guild.roles[0].id}") - # assert dpytest.verify().message().content( - # f"Vote will no longer be sent to those with the {guild.roles[0].name} role") - # assert guild.roles[0].id not in vote.target_roles + await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote removeRole {guild.roles[0].id}") + assert dpytest.verify().message().content( + f"Vote will no longer be sent to those with the {guild.roles[0].name} role") + assert guild.roles[0].id not in vote.target_roles @pytest.mark.asyncio diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index a79f6338..7875e493 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -15,6 +15,10 @@ from tests.log import logger from koala.cogs.voting import core +# Variables +option1 = {'header': 'option1', 'body': 'desc1'} +option2 = {'header': 'option2', 'body': 'desc2'} + @pytest_asyncio.fixture(scope="function", autouse=True) async def cog(bot: commands.Bot): @@ -142,7 +146,7 @@ def test_add_option(bot: commands.Bot, cog): core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.add_option(author.id, "Option 1+Option description") == "Option Option 1 with description Option description added to vote" + assert core.add_option(author.id, option1) == "Option option1 with description desc1 added to vote" def test_add_option_wrong_formatting(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] @@ -150,7 +154,9 @@ def test_add_option_wrong_formatting(bot: commands.Bot, cog): core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.add_option(author.id, "Option 1") == "Example usage: k!vote addOption option title+option description" + option = {'header': 'Option 1'} + + assert core.add_option(author.id, option) == "Option should have both header and body." def test_add_option_too_many(bot: commands.Bot, cog): @@ -161,10 +167,10 @@ def test_add_option_too_many(bot: commands.Bot, cog): x = 0 while (x < 11): - core.add_option(author.id, "more+options") + core.add_option(author.id, option1) x += 1 - assert core.add_option(author.id, "more options+please?") == "Vote has maximum number of options already (10)" + assert core.add_option(author.id, option1) == "Vote has maximum number of options already (10)" def test_add_option_too_long(bot: commands.Bot, cog): @@ -172,7 +178,7 @@ def test_add_option_too_long(bot: commands.Bot, cog): author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - test_option = "i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup+i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably." + test_option = {'header': "i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup", 'body': 'i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably.'} x = 0 while (x < 5): core.add_option(author.id, test_option) @@ -186,7 +192,7 @@ def test_remove_option(bot: commands.Bot, cog): author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, "test+option") + core.add_option(author.id, option1) assert core.remove_option(author.id, 0) == "Option number 0 removed" @@ -229,8 +235,8 @@ async def test_cancel_sent_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, "Option 1+Option description") - core.add_option(author.id, "Option 2+Option description2") + core.add_option(author.id, option1) + core.add_option(author.id, option2) await core.send_vote(bot, author.id, guild.id) @@ -268,15 +274,15 @@ async def test_close_no_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, "Option 1+Option description") - core.add_option(author.id, "Option 2+Option description2") + core.add_option(author.id, option1) + core.add_option(author.id, option2) await core.send_vote(bot, author.id, guild.id) embed = await core.close(bot, author.id, "Test Vote") assert embed.title == "Test Vote Results:" - assert embed.fields[0].name == "Option 1" - assert embed.fields[1].name == "Option 2" + assert embed.fields[0].name == "option1" + assert embed.fields[1].name == "option2" @pytest.mark.asyncio @@ -285,8 +291,8 @@ async def test_close_with_chair(bot: commands.Bot, cog): author: discord.Member = guild.members[0] chair: discord.Member = guild.members[1] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, "Option 1+Option description") - core.add_option(author.id, "Option 2+Option description2") + core.add_option(author.id, option1) + core.add_option(author.id, option2) await core.set_chair(bot, author.id, chair.id) await core.send_vote(bot, author.id, guild.id) @@ -299,8 +305,8 @@ async def test_send_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, "Option 1+Option description") - core.add_option(author.id, "Option 2+Option description2") + core.add_option(author.id, option1) + core.add_option(author.id, option2) # not sure how to assert DM sent @@ -317,7 +323,7 @@ async def test_send_vote_bad_options(bot: commands.Bot, cog): assert await core.send_vote(bot, author.id, guild.id) == "Please add more than 1 option to vote for" # only 1 option - core.add_option(author.id, "Option 1+Option description") + core.add_option(author.id, option1) assert await core.send_vote(bot, author.id, guild.id) == "Please add more than 1 option to vote for" @@ -326,15 +332,15 @@ async def test_get_results(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, "Option 1+Option description") - core.add_option(author.id, "Option 2+Option description2") + core.add_option(author.id, option1) + core.add_option(author.id, option2) await core.send_vote(bot, author.id, guild.id) embed = await core.results(bot, author.id, "Test Vote") assert embed.title == "Test Vote Results:" - assert embed.fields[0].name == "Option 1" - assert embed.fields[1].name == "Option 2" + assert embed.fields[0].name == "option1" + assert embed.fields[1].name == "option2" @pytest.mark.asyncio @@ -342,8 +348,8 @@ async def test_results_vote_not_sent(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, "Option 1+Option description") - core.add_option(author.id, "Option 2+Option description2") + core.add_option(author.id, option1) + core.add_option(author.id, option2) assert await core.results(bot, author.id, "Test Vote") == "That vote has not been sent yet. Please send it to your audience with k!vote send Test Vote" From 03d91745c80f25d283b83ece9eee4fe352211d20 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 23 May 2023 22:22:17 +0100 Subject: [PATCH 15/41] test: api tests FAILING --- koala/cogs/voting/api.py | 77 ++++++++++------ koala/cogs/voting/core.py | 8 +- tests/cogs/voting/test_api.py | 158 +++++++++++++++++++++++++++++++++ tests/cogs/voting/test_core.py | 2 +- 4 files changed, 214 insertions(+), 31 deletions(-) create mode 100644 tests/cogs/voting/test_api.py diff --git a/koala/cogs/voting/api.py b/koala/cogs/voting/api.py index aabcefe5..521e89db 100644 --- a/koala/cogs/voting/api.py +++ b/koala/cogs/voting/api.py @@ -1,12 +1,13 @@ # Futures # Built-in/Generic Imports # Libs -from http.client import CREATED, OK +from http.client import BAD_REQUEST, CREATED, OK from typing import Optional import discord from aiohttp import web from discord.ext.commands import Bot +from koala.cogs.voting.db import VoteManager from koala.rest.api import parse_request, build_response # Own modules @@ -14,7 +15,6 @@ from .log import logger # Constants -VOTING_ENDPOINT = 'voting' CONFIG_ENDPOINT = 'config' RESULTS_ENDOPINT = 'results' @@ -41,10 +41,9 @@ def register(self, app): return app -# how to do vote_manager - @parse_request + @parse_request(raw_response=True) async def post_new_vote(self, title, author_id, guild_id, options: list, - roles: Optional[list], chair_id: Optional[int], end_time: Optional[str]): + roles=None, chair_id=None, end_time=None): """ Create a new vote. :param title: The name of the vote @@ -57,22 +56,22 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, :return: """ try: - await core.start_vote(self, self.vote_manager, title, author_id, guild_id) + core.start_vote(self._bot, title, author_id, guild_id) for item in options: - core.add_option(self.vote_manager, author_id, item) + core.add_option(author_id, item) - if roles: + if roles is not None: for item in roles: - core.set_roles(self, self.vote_manager, author_id, guild_id, item, "add") + core.set_roles(self._bot, author_id, guild_id, item, "add") - if chair_id: - core.set_chair(self, self.vote_manager, author_id, chair_id) + if chair_id is not None: + await core.set_chair(self._bot, author_id, chair_id) - if end_time: - core.set_end_time(self.vote_manager, author_id, end_time) + if end_time is not None: + core.set_end_time(author_id, end_time) - await core.send_vote(self, self.vote_manager, author_id, guild_id) + await core.send_vote(self._bot, author_id, guild_id) except Exception as e: logger.error(e) @@ -81,8 +80,8 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, return build_response(CREATED, {'message': f'Vote {title} created'}) - @parse_request - def get_current_votes(self, author_id, guild_id): + @parse_request(raw_response=True) + async def get_current_votes(self, author_id, guild_id): """ Gets list of open votes. :param author_id: The author id of the vote @@ -91,14 +90,20 @@ def get_current_votes(self, author_id, guild_id): """ try: embed = core.current_votes(author_id, guild_id) + + if embed.description: + body = embed.description + else: + body = embed.fields[0].value + + return build_response(OK, {'embed_title': f'{embed.title}', 'embed_body': f'{body}'}) + except Exception as e: logger.error(e) raise web.HTTPUnprocessableEntity() - - return build_response(OK, embed) - @parse_request + @parse_request(raw_response=True) async def post_close_results(self, author_id, title): """ Gets results and closes the vote. @@ -107,15 +112,24 @@ async def post_close_results(self, author_id, title): :return: """ try: - embed = await core.close(self, self.vote_manager, author_id, title) + embed = await core.close(self._bot, author_id, title) + if embed.fields[0].name == "No votes yet!": + body = embed.fields[0].value + else: + body = "" + for item in embed.fields: + body += item.name + ", " + item.value + "\n" + + return build_response(OK, {'embed_title': f'{embed.title}', + 'embed_body': f'{body}'}) + except Exception as e: logger.error(e) raise web.HTTPUnprocessableEntity() - - return build_response(OK, embed) + - @parse_request + @parse_request(raw_response=True) async def get_results(self, author_id, title): """ Gets results, but does not close the vote. @@ -124,12 +138,23 @@ async def get_results(self, author_id, title): :return: """ try: - embed = core.results(self, self.vote_manager, author_id, title) + message = await core.results(self._bot, author_id, title) + if type(message) is discord.Embed: + if message.fields[0].name == "No votes yet!": + body = message.fields[0].value + else: + body = "" + for item in message.fields: + body += item.name + ", " + item.value + "\n" + return build_response(OK, {'embed_title': f'{message.title}', + 'embed_body': f'{body}'}) + + else: + return build_response(BAD_REQUEST, {'message': message}) + except Exception as e: logger.error(e) raise web.HTTPUnprocessableEntity() - - return build_response(OK, embed) def setup(bot: Bot): diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 3db8bd9f..7dad40df 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -275,13 +275,13 @@ async def close(bot: koalabot.KoalaBot, author_id, title): async def results(bot: koalabot.KoalaBot, author_id, title): vote_id = vm.vote_lookup.get((author_id, title)) - author = bot.get_user(author_id) - + # author = bot.get_user(author_id) + if vote_id is None: - raise ValueError(f"{title} is not a valid vote title for user {author.name}") + raise ValueError(f"{title} is not a valid vote title for user with id {author_id}") if vote_id not in vm.sent_votes.keys(): - if author.id in vm.configuring_votes.keys(): + if author_id in vm.configuring_votes.keys(): return f"That vote has not been sent yet. Please send it to your audience with {koalabot.COMMAND_PREFIX}vote send {title}" else: return "You have no votes of that title to check" diff --git a/tests/cogs/voting/test_api.py b/tests/cogs/voting/test_api.py new file mode 100644 index 00000000..347b64a5 --- /dev/null +++ b/tests/cogs/voting/test_api.py @@ -0,0 +1,158 @@ +from http.client import BAD_REQUEST, CREATED, OK, UNPROCESSABLE_ENTITY, NOT_FOUND + +# Libs +import discord +import discord.ext.test as dpytest +import pytest +from aiohttp import web +from mock import mock +import json + +import koalabot +from koala.cogs.voting.api import VotingEndpoint + + +@pytest.fixture +def api_client(bot: discord.ext.commands.Bot, aiohttp_client, loop): + app = web.Application() + endpoint = VotingEndpoint(bot) + app = endpoint.register(app) + return loop.run_until_complete(aiohttp_client(app)) + + +# POST /config +# not sure how to test for any error handling + +async def test_post_new_vote_no_optionals(api_client): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + resp = await api_client.post('/config', json= + { + 'title': 'Test', + 'author_id': author.id, + 'guild_id': guild.id, + 'options': [{'header': 'option1', 'body': 'desc1'}, + {'header': 'option2', 'body': 'desc2'}] + }) + + assert resp.status == CREATED + assert (await resp.json())['message'] == "Vote Test created" + + +async def test_post_new_vote_with_optionals(api_client): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + resp = await api_client.post('/config', json= + { + 'title': 'Test2', + 'author_id': author.id, + 'guild_id': guild.id, + 'options': [{'header': 'option1', 'body': 'desc1'}, + {'header': 'option2', 'body': 'desc2'}], + 'roles': [guild.roles[0].id], + 'chair_id': guild.members[1].id, + 'end_time': '2025-01-01 00:00:00' + }) + + assert resp.status == CREATED + assert (await resp.json())['message'] == "Vote Test2 created" + + +# GET /config + +async def test_get_current_votes(api_client): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + await api_client.post('/config', json= + { + 'title': 'Test', + 'author_id': author.id, + 'guild_id': guild.id, + 'options': [{'header': 'option1', 'body': 'desc1'}, + {'header': 'option2', 'body': 'desc2'}] + }) + + resp = await api_client.get('/config?author_id={}&guild_id={}'.format(author.id, guild.id)) + assert resp.status == OK + + jresp = await resp.json() + + assert jresp['embed_title'] == "Your current votes" + assert jresp['embed_body'] == "Test\n" + + +async def test_get_current_votes_no_votes(api_client): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + resp = await api_client.get('/config?author_id={}&guild_id={}'.format(author.id, guild.id)) + assert resp.status == OK + + jresp = await resp.json() + + assert jresp['embed_title'] == "Your current votes" + assert jresp['embed_body'] == "No current votes" + + +# POST /results +async def test_post_results(api_client): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + await api_client.post('/config', json= + { + 'title': 'Test', + 'author_id': author.id, + 'guild_id': guild.id, + 'options': [{'header': 'option1', 'body': 'desc1'}, + {'header': 'option2', 'body': 'desc2'}] + }) + + resp = await api_client.post('/results', json={ + 'author_id': author.id, + 'title': 'Test' + }) + + assert resp.status == OK + jresp = await resp.json() + + assert jresp['embed_title'] == "Test Results:" + assert jresp['embed_body'] == "option1, 1 votes\noption2, 0 votes\n" + # how the hell is this getting votes? + + resp = await api_client.get('/config?author_id={}&guild_id={}'.format(author.id, guild.id)) + + assert (await resp.json())['embed_body'] == "No current votes" + + +# GET /results +async def test_get_results(api_client): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + await api_client.post('/config', json= + { + 'title': 'Test', + 'author_id': author.id, + 'guild_id': guild.id, + 'options': [{'header': 'option1', 'body': 'desc1'}, + {'header': 'option2', 'body': 'desc2'}] + }) + + # NoneType has no attr "name" + + resp = await api_client.get('/results?author_id={}&title=Test'.format(author.id)) + assert resp.status == OK + jresp = await resp.json() + + assert jresp['embed_title'] == "Test Results:" + assert jresp['embed_body'] == "option1, 1 votes\noption2, 0 votes\n" + + # checking vote hasn't closed + resp = await api_client.get('/config?author_id={}&guild_id={}'.format(author.id, guild.id)) + + assert (await resp.json())['embed_body'] == "Test\n" + \ No newline at end of file diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index 7875e493..a4eb89b0 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -359,5 +359,5 @@ async def test_results_invalid_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - with pytest.raises(ValueError, match=f"invalid is not a valid vote title for user {author.name}"): + with pytest.raises(ValueError, match=f"invalid is not a valid vote title for user with id {author.id}"): await core.results(bot, author.id, "invalid") \ No newline at end of file From 795dae1077b53113e46d068120ca5ad1feabb58c Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Fri, 26 May 2023 22:06:14 +0100 Subject: [PATCH 16/41] test: failing not fixed at all cry --- koala/cogs/voting/api.py | 12 +++++++++--- tests/cogs/voting/test_api.py | 7 +++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/koala/cogs/voting/api.py b/koala/cogs/voting/api.py index 521e89db..149bb771 100644 --- a/koala/cogs/voting/api.py +++ b/koala/cogs/voting/api.py @@ -1,7 +1,7 @@ # Futures # Built-in/Generic Imports # Libs -from http.client import BAD_REQUEST, CREATED, OK +from http.client import BAD_REQUEST, CREATED, OK, UNPROCESSABLE_ENTITY from typing import Optional import discord @@ -72,7 +72,7 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, core.set_end_time(author_id, end_time) await core.send_vote(self._bot, author_id, guild_id) - + except Exception as e: logger.error(e) raise web.HTTPUnprocessableEntity() @@ -140,18 +140,24 @@ async def get_results(self, author_id, title): try: message = await core.results(self._bot, author_id, title) if type(message) is discord.Embed: + if message.fields[0].name == "No votes yet!": body = message.fields[0].value + else: body = "" for item in message.fields: body += item.name + ", " + item.value + "\n" + return build_response(OK, {'embed_title': f'{message.title}', 'embed_body': f'{body}'}) else: return build_response(BAD_REQUEST, {'message': message}) - + + except ValueError as e: + return build_response(UNPROCESSABLE_ENTITY, {'message': message}) + except Exception as e: logger.error(e) raise web.HTTPUnprocessableEntity() diff --git a/tests/cogs/voting/test_api.py b/tests/cogs/voting/test_api.py index 347b64a5..c860f4f1 100644 --- a/tests/cogs/voting/test_api.py +++ b/tests/cogs/voting/test_api.py @@ -133,7 +133,7 @@ async def test_get_results(api_client): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - await api_client.post('/config', json= + resp2 = await api_client.post('/config', json= { 'title': 'Test', 'author_id': author.id, @@ -142,7 +142,10 @@ async def test_get_results(api_client): {'header': 'option2', 'body': 'desc2'}] }) - # NoneType has no attr "name" + assert resp2.status == CREATED + assert (await resp2.json())['message'] == "Vote Test created" + + # for SOME REASON it thinks its an invalid vote; the post is fine resp = await api_client.get('/results?author_id={}&title=Test'.format(author.id)) assert resp.status == OK From e8aca0ccd7591a4ede1192ef5e8f4975fc6844b5 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 6 Jun 2023 02:53:55 +0800 Subject: [PATCH 17/41] fix: slight change --- koala/cogs/voting/cog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 15053b6c..4cf983f2 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -85,7 +85,7 @@ async def cog_unload(self): @tasks.loop(seconds=60.0) async def vote_end_loop(self): - await core.vote_end_loop(self.bot, self.vote_manager) + await core.vote_end_loop(self.bot, core.vm) @vote_end_loop.before_loop async def before_vote_loop(self): From 88448a21444f3d1891a662f1dc4b563720c10420 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Sat, 17 Jun 2023 17:23:08 +0800 Subject: [PATCH 18/41] fix: voting api; failing tests still exist --- koala/cogs/__init__.py | 2 +- koala/cogs/verification/api.py | 2 +- koala/cogs/voting/__init__.py | 9 +++++++-- koala/cogs/voting/api.py | 16 +++++++++++----- koala/cogs/voting/cog.py | 8 +++++--- koala/cogs/voting/core.py | 16 +++++++--------- tests/cogs/voting/test_api.py | 31 +++++++++++++------------------ tests/cogs/voting/test_core.py | 19 +++++++------------ 8 files changed, 52 insertions(+), 51 deletions(-) diff --git a/koala/cogs/__init__.py b/koala/cogs/__init__.py index c82ceaae..2951e19b 100644 --- a/koala/cogs/__init__.py +++ b/koala/cogs/__init__.py @@ -6,4 +6,4 @@ from .text_filter import TextFilter from .twitch_alert import TwitchAlert from .verification import Verification -from .voting import Voting +from .voting import Voting \ No newline at end of file diff --git a/koala/cogs/verification/api.py b/koala/cogs/verification/api.py index 2082dab0..25a929aa 100644 --- a/koala/cogs/verification/api.py +++ b/koala/cogs/verification/api.py @@ -85,4 +85,4 @@ def setup(bot: Bot): endpoint = VerifyEndpoint(bot) endpoint.register(sub_app) getattr(bot, "koala_web_app").add_subapp('/{}'.format(VERIFY_ENDPOINT), sub_app) - logger.info("Base API is ready.") + logger.info("Verify API is ready.") diff --git a/koala/cogs/voting/__init__.py b/koala/cogs/voting/__init__.py index 1c7ac185..c179c63f 100644 --- a/koala/cogs/voting/__init__.py +++ b/koala/cogs/voting/__init__.py @@ -1,2 +1,7 @@ -from . import utils, db, log, models -from .cog import Voting, setup +from . import api +from . import cog +from .cog import Voting + +async def setup(bot): + await cog.setup(bot) + api.setup(bot) \ No newline at end of file diff --git a/koala/cogs/voting/api.py b/koala/cogs/voting/api.py index 149bb771..d1d6b65a 100644 --- a/koala/cogs/voting/api.py +++ b/koala/cogs/voting/api.py @@ -1,13 +1,13 @@ # Futures # Built-in/Generic Imports # Libs -from http.client import BAD_REQUEST, CREATED, OK, UNPROCESSABLE_ENTITY -from typing import Optional +from http.client import BAD_REQUEST, CREATED, OK import discord from aiohttp import web from discord.ext.commands import Bot from koala.cogs.voting.db import VoteManager +from koala.cogs.voting.option import Option from koala.rest.api import parse_request, build_response # Own modules @@ -15,10 +15,12 @@ from .log import logger # Constants +VOTING_ENDPOINT = 'vote' CONFIG_ENDPOINT = 'config' RESULTS_ENDOPINT = 'results' # Variables +vm = VoteManager() class VotingEndpoint: """ @@ -43,7 +45,7 @@ def register(self, app): @parse_request(raw_response=True) async def post_new_vote(self, title, author_id, guild_id, options: list, - roles=None, chair_id=None, end_time=None): + roles=None, chair_id=None, channel_id=None, end_time=None): """ Create a new vote. :param title: The name of the vote @@ -52,6 +54,7 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, :param options: The options for the votes :param roles: The target roles for the votes :param chair_id: The chair id of the vote + :param channel_id: Channel id of the vote :param end_time: The end time of the vote :return: """ @@ -59,7 +62,7 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, core.start_vote(self._bot, title, author_id, guild_id) for item in options: - core.add_option(author_id, item) + core.add_option(author_id, Option(item[0], item[1], vm.generate_unique_opt_id())) if roles is not None: for item in roles: @@ -68,6 +71,9 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, if chair_id is not None: await core.set_chair(self._bot, author_id, chair_id) + if channel_id is not None: + await core.set_channel(self._bot, author_id, channel_id) + if end_time is not None: core.set_end_time(author_id, end_time) @@ -156,7 +162,7 @@ async def get_results(self, author_id, title): return build_response(BAD_REQUEST, {'message': message}) except ValueError as e: - return build_response(UNPROCESSABLE_ENTITY, {'message': message}) + raise web.HTTPUnprocessableEntity(reason="{}".format(e)) except Exception as e: logger.error(e) diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 4cf983f2..20253df2 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -14,8 +14,9 @@ import koalabot from koala.db import insert_extension from . import core -from .db import VoteManager, add_reactions +from .db import add_reactions from .log import logger +from .option import Option # Constants @@ -195,7 +196,9 @@ async def add_option(self, ctx, *, option_string): await ctx.send("Example usage: k!vote addOption option title+option description") else: header, body = option_string.split("+") - option = {'header': header, 'body': body} + option = Option() + option.head = header + option.body = body await ctx.send(core.add_option(ctx.author.id, option)) @@ -302,4 +305,3 @@ async def setup(bot: koalabot) -> None: :param bot: the bot client for KoalaBot """ await bot.add_cog(Voting(bot)) - logger.info("Voting is ready.") diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 7dad40df..4b3199a9 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -148,24 +148,23 @@ def set_channel(bot: koalabot.KoalaBot, author_id, channel_id=None): # OPTION ATTRIBUTES TITLE, DESCRPTION (OBJECT!) # NO MORE + -def add_option(author_id, option): +def add_option(author_id, option: Option): vote = vm.get_configuring_vote(author_id) if len(vote.options) > 9: return "Vote has maximum number of options already (10)" - if 'header' not in option or 'body' not in option: + if option.head is None or option.body is None: return "Option should have both header and body." current_option_length = sum([len(x.head) + len(x.body) for x in vote.options]) - if current_option_length + len(option['header']) + len(option['body']) > 1500: + if current_option_length + len(option.head) + len(option.body) > 1500: return "Option string is too long. The total length of all the vote options cannot be over 1500 characters." - # moved check for '+' in string to cog - - vote.add_option(Option(option['header'], option['body'], vm.generate_unique_opt_id())) - return f"Option {option['header']} with description {option['body']} added to vote" + option.opt_id = vm.generate_unique_opt_id() + vote.add_option(option) + return f"Option {option.head} with description {option.body} added to vote" def remove_option(author_id, index): @@ -274,8 +273,7 @@ async def close(bot: koalabot.KoalaBot, author_id, title): async def results(bot: koalabot.KoalaBot, author_id, title): - vote_id = vm.vote_lookup.get((author_id, title)) - # author = bot.get_user(author_id) + vote_id = vm.vote_lookup[(author_id, title)] if vote_id is None: raise ValueError(f"{title} is not a valid vote title for user with id {author_id}") diff --git a/tests/cogs/voting/test_api.py b/tests/cogs/voting/test_api.py index c860f4f1..a53009aa 100644 --- a/tests/cogs/voting/test_api.py +++ b/tests/cogs/voting/test_api.py @@ -5,12 +5,12 @@ import discord.ext.test as dpytest import pytest from aiohttp import web -from mock import mock -import json -import koalabot from koala.cogs.voting.api import VotingEndpoint +# Variables +options = [["option1", "body1"], + ["option2", "body2"]] @pytest.fixture def api_client(bot: discord.ext.commands.Bot, aiohttp_client, loop): @@ -32,8 +32,7 @@ async def test_post_new_vote_no_optionals(api_client): 'title': 'Test', 'author_id': author.id, 'guild_id': guild.id, - 'options': [{'header': 'option1', 'body': 'desc1'}, - {'header': 'option2', 'body': 'desc2'}] + 'options': options }) assert resp.status == CREATED @@ -49,8 +48,7 @@ async def test_post_new_vote_with_optionals(api_client): 'title': 'Test2', 'author_id': author.id, 'guild_id': guild.id, - 'options': [{'header': 'option1', 'body': 'desc1'}, - {'header': 'option2', 'body': 'desc2'}], + 'options': options, 'roles': [guild.roles[0].id], 'chair_id': guild.members[1].id, 'end_time': '2025-01-01 00:00:00' @@ -71,8 +69,7 @@ async def test_get_current_votes(api_client): 'title': 'Test', 'author_id': author.id, 'guild_id': guild.id, - 'options': [{'header': 'option1', 'body': 'desc1'}, - {'header': 'option2', 'body': 'desc2'}] + 'options': options }) resp = await api_client.get('/config?author_id={}&guild_id={}'.format(author.id, guild.id)) @@ -107,8 +104,7 @@ async def test_post_results(api_client): 'title': 'Test', 'author_id': author.id, 'guild_id': guild.id, - 'options': [{'header': 'option1', 'body': 'desc1'}, - {'header': 'option2', 'body': 'desc2'}] + 'options': options }) resp = await api_client.post('/results', json={ @@ -135,27 +131,26 @@ async def test_get_results(api_client): resp2 = await api_client.post('/config', json= { - 'title': 'Test', + 'title': 'Test2', 'author_id': author.id, 'guild_id': guild.id, - 'options': [{'header': 'option1', 'body': 'desc1'}, - {'header': 'option2', 'body': 'desc2'}] + 'options': options }) assert resp2.status == CREATED - assert (await resp2.json())['message'] == "Vote Test created" + assert (await resp2.json())['message'] == "Vote Test2 created" # for SOME REASON it thinks its an invalid vote; the post is fine - resp = await api_client.get('/results?author_id={}&title=Test'.format(author.id)) + resp = await api_client.get('/results?author_id={}&title=Test2'.format(author.id)) assert resp.status == OK jresp = await resp.json() - assert jresp['embed_title'] == "Test Results:" + assert jresp['embed_title'] == "Test2 Results:" assert jresp['embed_body'] == "option1, 1 votes\noption2, 0 votes\n" # checking vote hasn't closed resp = await api_client.get('/config?author_id={}&guild_id={}'.format(author.id, guild.id)) - assert (await resp.json())['embed_body'] == "Test\n" + assert (await resp.json())['embed_body'] == "Test2\n" \ No newline at end of file diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index a4eb89b0..ac4aed4d 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -8,16 +8,17 @@ from sqlalchemy import select from koala.cogs import Voting from koala.cogs.voting.models import Votes +from koala.cogs.voting.option import Option # Own modules import koalabot -from koala.db import assign_session, session_manager, insert_extension +from koala.db import assign_session from tests.log import logger from koala.cogs.voting import core # Variables -option1 = {'header': 'option1', 'body': 'desc1'} -option2 = {'header': 'option2', 'body': 'desc2'} +option1 = Option('option1', 'body1', 1) +option2 = Option('option2', 'body2', 2) @pytest_asyncio.fixture(scope="function", autouse=True) @@ -29,12 +30,6 @@ async def cog(bot: commands.Bot): return cog -@pytest.mark.asyncio -async def test_update_vote_message(bot: commands.Bot): - pass - # await core.update_vote_message(bot) - - def test_create_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] @@ -146,7 +141,7 @@ def test_add_option(bot: commands.Bot, cog): core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.add_option(author.id, option1) == "Option option1 with description desc1 added to vote" + assert core.add_option(author.id, option1) == "Option option1 with description body1 added to vote" def test_add_option_wrong_formatting(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] @@ -154,7 +149,7 @@ def test_add_option_wrong_formatting(bot: commands.Bot, cog): core.start_vote(bot, "Test Vote", author.id, guild.id) - option = {'header': 'Option 1'} + option = Option("head", None, 1) assert core.add_option(author.id, option) == "Option should have both header and body." @@ -178,7 +173,7 @@ def test_add_option_too_long(bot: commands.Bot, cog): author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - test_option = {'header': "i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup", 'body': 'i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably.'} + test_option = Option("i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup", 'i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably.', 3) x = 0 while (x < 5): core.add_option(author.id, test_option) From 4e31908dc2c9e99a259bc920b56a3ad11c313447 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 4 Jul 2023 20:33:40 +0100 Subject: [PATCH 19/41] fix: pass Options as objects into API --- koala/cogs/voting/api.py | 2 +- koala/cogs/voting/cog.py | 5 +---- koala/cogs/voting/core.py | 11 +++++------ tests/cogs/voting/test_api.py | 12 ++++++++++-- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/koala/cogs/voting/api.py b/koala/cogs/voting/api.py index d1d6b65a..846b132f 100644 --- a/koala/cogs/voting/api.py +++ b/koala/cogs/voting/api.py @@ -62,7 +62,7 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, core.start_vote(self._bot, title, author_id, guild_id) for item in options: - core.add_option(author_id, Option(item[0], item[1], vm.generate_unique_opt_id())) + core.add_option(author_id, item.get("header"), item.get("body")) if roles is not None: for item in roles: diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 20253df2..7e17a6eb 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -196,10 +196,7 @@ async def add_option(self, ctx, *, option_string): await ctx.send("Example usage: k!vote addOption option title+option description") else: header, body = option_string.split("+") - option = Option() - option.head = header - option.body = body - await ctx.send(core.add_option(ctx.author.id, option)) + await ctx.send(core.add_option(ctx.author.id, header, body)) @currently_configuring() diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 4b3199a9..ea0edf00 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -148,23 +148,22 @@ def set_channel(bot: koalabot.KoalaBot, author_id, channel_id=None): # OPTION ATTRIBUTES TITLE, DESCRPTION (OBJECT!) # NO MORE + -def add_option(author_id, option: Option): +def add_option(author_id, option_header, option_body): vote = vm.get_configuring_vote(author_id) if len(vote.options) > 9: return "Vote has maximum number of options already (10)" - if option.head is None or option.body is None: + if option_header is None or option_body is None: return "Option should have both header and body." current_option_length = sum([len(x.head) + len(x.body) for x in vote.options]) - if current_option_length + len(option.head) + len(option.body) > 1500: + if current_option_length + len(option_header) + len(option_body) > 1500: return "Option string is too long. The total length of all the vote options cannot be over 1500 characters." - option.opt_id = vm.generate_unique_opt_id() - vote.add_option(option) - return f"Option {option.head} with description {option.body} added to vote" + vote.add_option(Option(option_header, option_body, vm.generate_unique_opt_id())) + return f"Option {option_header} with description {option_body} added to vote" def remove_option(author_id, index): diff --git a/tests/cogs/voting/test_api.py b/tests/cogs/voting/test_api.py index a53009aa..a9486d31 100644 --- a/tests/cogs/voting/test_api.py +++ b/tests/cogs/voting/test_api.py @@ -9,8 +9,16 @@ from koala.cogs.voting.api import VotingEndpoint # Variables -options = [["option1", "body1"], - ["option2", "body2"]] +options = [ + { + "header": "option1", + "body": "option1desc" + }, + { + "header": "option2", + "body": "option2desc" + } + ], @pytest.fixture def api_client(bot: discord.ext.commands.Bot, aiohttp_client, loop): From 88af161784de41e93fb8a05aad26f1e0cc539d15 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 4 Jul 2023 22:03:18 +0100 Subject: [PATCH 20/41] test: fix most failing tests --- koala/cogs/voting/api.py | 14 ++++----- koala/cogs/voting/cog.py | 2 -- koala/cogs/voting/core.py | 14 ++++----- koala/cogs/voting/db.py | 3 +- tests/cogs/voting/test_api.py | 2 +- tests/cogs/voting/test_core.py | 57 +++++++++++++++------------------- tests/cogs/voting/test_db.py | 12 +++---- 7 files changed, 46 insertions(+), 58 deletions(-) diff --git a/koala/cogs/voting/api.py b/koala/cogs/voting/api.py index 846b132f..e452a22b 100644 --- a/koala/cogs/voting/api.py +++ b/koala/cogs/voting/api.py @@ -2,6 +2,7 @@ # Built-in/Generic Imports # Libs from http.client import BAD_REQUEST, CREATED, OK +from typing import List import discord from aiohttp import web @@ -19,9 +20,6 @@ CONFIG_ENDPOINT = 'config' RESULTS_ENDOPINT = 'results' -# Variables -vm = VoteManager() - class VotingEndpoint: """ The API endpoints for Voting @@ -44,7 +42,7 @@ def register(self, app): @parse_request(raw_response=True) - async def post_new_vote(self, title, author_id, guild_id, options: list, + async def post_new_vote(self, title, author_id, guild_id, options: List[dict], roles=None, chair_id=None, channel_id=None, end_time=None): """ Create a new vote. @@ -61,8 +59,11 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, try: core.start_vote(self._bot, title, author_id, guild_id) + if channel_id is not None: + await core.set_channel(self._bot, author_id, channel_id) + for item in options: - core.add_option(author_id, item.get("header"), item.get("body")) + core.add_option(author_id, item["header"], item["body"]) if roles is not None: for item in roles: @@ -71,9 +72,6 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, if chair_id is not None: await core.set_chair(self._bot, author_id, chair_id) - if channel_id is not None: - await core.set_channel(self._bot, author_id, channel_id) - if end_time is not None: core.set_end_time(author_id, end_time) diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 7e17a6eb..889237ea 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -181,8 +181,6 @@ async def set_channel(self, ctx, *, channel: discord.VoiceChannel = None): await ctx.send(core.set_channel(self.bot, ctx.author.id, channel.id)) -# PROCESS THE + IN OPTIONS HERE -# NO + IN CORE!!! @currently_configuring() @commands.check(vote_is_enabled) @vote.command(name="addOption") diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index ea0edf00..36005e73 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -100,7 +100,7 @@ def start_vote(bot: koalabot.KoalaBot, title, author_id, guild_id, session: Sess if len(title) > 200: return "Title too long" - vm.create_vote(author_id, guild_id, title) + vm.create_vote(author_id, guild_id, title, session) return f"Vote titled `{title}` created for guild {guild_name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." @@ -145,18 +145,12 @@ def set_channel(bot: koalabot.KoalaBot, author_id, channel_id=None): return "Removed channel restriction on vote" -# OPTION ATTRIBUTES TITLE, DESCRPTION (OBJECT!) -# NO MORE + - def add_option(author_id, option_header, option_body): vote = vm.get_configuring_vote(author_id) if len(vote.options) > 9: return "Vote has maximum number of options already (10)" - if option_header is None or option_body is None: - return "Option should have both header and body." - current_option_length = sum([len(x.head) + len(x.body) for x in vote.options]) if current_option_length + len(option_header) + len(option_body) > 1500: @@ -272,7 +266,11 @@ async def close(bot: koalabot.KoalaBot, author_id, title): async def results(bot: koalabot.KoalaBot, author_id, title): - vote_id = vm.vote_lookup[(author_id, title)] + try: + vote_id = vm.vote_lookup[(author_id, title)] + except KeyError as e: + logger.error(e) + raise ValueError(f"{title} is not a valid vote title for user with id {author_id}") if vote_id is None: raise ValueError(f"{title} is not a valid vote title for user with id {author_id}") diff --git a/koala/cogs/voting/db.py b/koala/cogs/voting/db.py index 7befea65..b4e171bb 100644 --- a/koala/cogs/voting/db.py +++ b/koala/cogs/voting/db.py @@ -10,6 +10,7 @@ # Libs import discord from sqlalchemy import select, delete +from sqlalchemy.orm import Session # Own modules from koala.db import session_manager @@ -151,7 +152,7 @@ def has_active_vote(self, author_id): """ return author_id in self.configuring_votes.keys() - def create_vote(self, author_id, guild_id, title): + def create_vote(self, author_id, guild_id, title, session: Session): """ Creates a vote object and assigns it to a users ID :param author_id: id of the author of the vote diff --git a/tests/cogs/voting/test_api.py b/tests/cogs/voting/test_api.py index a9486d31..d64ff580 100644 --- a/tests/cogs/voting/test_api.py +++ b/tests/cogs/voting/test_api.py @@ -18,7 +18,7 @@ "header": "option2", "body": "option2desc" } - ], + ] @pytest.fixture def api_client(bot: discord.ext.commands.Bot, aiohttp_client, loop): diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index ac4aed4d..2a5420b6 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -17,8 +17,10 @@ from koala.cogs.voting import core # Variables -option1 = Option('option1', 'body1', 1) -option2 = Option('option2', 'body2', 2) +opt1_header = "option1" +opt1_body = "desc1" +opt2_header = "option2" +opt2_body = "desc2" @pytest_asyncio.fixture(scope="function", autouse=True) @@ -141,17 +143,7 @@ def test_add_option(bot: commands.Bot, cog): core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.add_option(author.id, option1) == "Option option1 with description body1 added to vote" - -def test_add_option_wrong_formatting(bot: commands.Bot, cog): - guild: discord.Guild = dpytest.get_config().guilds[0] - author: discord.Member = guild.members[0] - - core.start_vote(bot, "Test Vote", author.id, guild.id) - - option = Option("head", None, 1) - - assert core.add_option(author.id, option) == "Option should have both header and body." + assert core.add_option(author.id, opt1_header, opt1_body) == "Option option1 with description desc1 added to vote" def test_add_option_too_many(bot: commands.Bot, cog): @@ -162,10 +154,10 @@ def test_add_option_too_many(bot: commands.Bot, cog): x = 0 while (x < 11): - core.add_option(author.id, option1) + core.add_option(author.id, opt1_header, opt1_body) x += 1 - assert core.add_option(author.id, option1) == "Vote has maximum number of options already (10)" + assert core.add_option(author.id, opt1_header, opt1_body) == "Vote has maximum number of options already (10)" def test_add_option_too_long(bot: commands.Bot, cog): @@ -173,13 +165,14 @@ def test_add_option_too_long(bot: commands.Bot, cog): author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - test_option = Option("i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup", 'i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably.', 3) + test_option_header = "i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup" + test_option_body = 'i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably.' x = 0 while (x < 5): - core.add_option(author.id, test_option) + core.add_option(author.id, test_option_header, test_option_body) x += 1 - assert core.add_option(author.id, test_option) == "Option string is too long. The total length of all the vote options cannot be over 1500 characters." + assert core.add_option(author.id, test_option_header, test_option_body) == "Option string is too long. The total length of all the vote options cannot be over 1500 characters." def test_remove_option(bot: commands.Bot, cog): @@ -187,7 +180,7 @@ def test_remove_option(bot: commands.Bot, cog): author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, option1) + core.add_option(author.id, opt1_header, opt1_body) assert core.remove_option(author.id, 0) == "Option number 0 removed" @@ -230,8 +223,8 @@ async def test_cancel_sent_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, option1) - core.add_option(author.id, option2) + core.add_option(author.id, opt1_header, opt1_body) + core.add_option(author.id, opt2_header, opt2_body) await core.send_vote(bot, author.id, guild.id) @@ -269,8 +262,8 @@ async def test_close_no_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, option1) - core.add_option(author.id, option2) + core.add_option(author.id, opt1_header, opt1_body) + core.add_option(author.id, opt2_header, opt2_body) await core.send_vote(bot, author.id, guild.id) @@ -286,8 +279,8 @@ async def test_close_with_chair(bot: commands.Bot, cog): author: discord.Member = guild.members[0] chair: discord.Member = guild.members[1] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, option1) - core.add_option(author.id, option2) + core.add_option(author.id, opt1_header, opt1_body) + core.add_option(author.id, opt2_header, opt2_body) await core.set_chair(bot, author.id, chair.id) await core.send_vote(bot, author.id, guild.id) @@ -300,8 +293,8 @@ async def test_send_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, option1) - core.add_option(author.id, option2) + core.add_option(author.id, opt1_header, opt1_body) + core.add_option(author.id, opt2_header, opt2_body) # not sure how to assert DM sent @@ -318,7 +311,7 @@ async def test_send_vote_bad_options(bot: commands.Bot, cog): assert await core.send_vote(bot, author.id, guild.id) == "Please add more than 1 option to vote for" # only 1 option - core.add_option(author.id, option1) + core.add_option(author.id, opt1_header, opt1_body) assert await core.send_vote(bot, author.id, guild.id) == "Please add more than 1 option to vote for" @@ -327,8 +320,8 @@ async def test_get_results(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, option1) - core.add_option(author.id, option2) + core.add_option(author.id, opt1_header, opt1_body) + core.add_option(author.id, opt2_header, opt2_body) await core.send_vote(bot, author.id, guild.id) @@ -343,8 +336,8 @@ async def test_results_vote_not_sent(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, option1) - core.add_option(author.id, option2) + core.add_option(author.id, opt1_header, opt1_body) + core.add_option(author.id, opt2_header, opt2_body) assert await core.results(bot, author.id, "Test Vote") == "That vote has not been sent yet. Please send it to your audience with k!vote send Test Vote" diff --git a/tests/cogs/voting/test_db.py b/tests/cogs/voting/test_db.py index c5068f69..0309cd8c 100644 --- a/tests/cogs/voting/test_db.py +++ b/tests/cogs/voting/test_db.py @@ -66,7 +66,7 @@ def test_votemanager_has_active_vote(): def test_votemanager_create_vote(): with session_manager() as session: - vote = vote_manager.create_vote(123, 456, "Create Vote Test") + vote = vote_manager.create_vote(123, 456, "Create Vote Test", session) assert vote.title == "Create Vote Test" in_db = session.execute(select(Votes).filter_by(author_id=123, title="Create Vote Test")).all() assert in_db @@ -101,7 +101,7 @@ def test_votemanager_sent_to(): def test_vote_set_chair(): with session_manager() as session: - vote = vote_manager.create_vote(111, 222, "Set Chair Vote Test") + vote = vote_manager.create_vote(111, 222, "Set Chair Vote Test", session) vote.set_chair(555) assert vote.chair == 555 in_db = session.execute(select(Votes).filter_by(vote_id=vote.id, chair_id=555)).all() @@ -114,7 +114,7 @@ def test_vote_set_chair(): def test_vote_set_vc(): with session_manager() as session: - vote = vote_manager.create_vote(111, 222, "Set Chair Vote Test") + vote = vote_manager.create_vote(111, 222, "Set Chair Vote Test", session) vote.set_vc(555) assert vote.target_voice_channel == 555 in_db = session.execute(select(Votes).filter_by(vote_id=vote.id, voice_id=555)).all() @@ -127,7 +127,7 @@ def test_vote_set_vc(): def test_vote_add_option(): with session_manager() as session: - vote = vote_manager.create_vote(111, 222, "Add Option Test") + vote = vote_manager.create_vote(111, 222, "Add Option Test", session) vote.add_option(Option("head", "body", 123)) assert vote.options[0].head == "head" assert vote.options[0].body == "body" @@ -137,7 +137,7 @@ def test_vote_add_option(): def test_vote_remove_option(): with session_manager() as session: - vote = vote_manager.create_vote(111, 222, "Remove Option Test") + vote = vote_manager.create_vote(111, 222, "Remove Option Test", session) vote.add_option(Option("head", "body", 123)) vote.remove_option(0) in_db = session.execute(select(VoteOptions).filter_by(opt_id=123)).all() @@ -146,7 +146,7 @@ def test_vote_remove_option(): def test_vote_register_sent(): with session_manager() as session: - vote = vote_manager.create_vote(111, 222, "Register Sent Test") + vote = vote_manager.create_vote(111, 222, "Register Sent Test", session) vote.register_sent(555, 666) assert vote.sent_to[555] == 666 in_db = session.execute(select(VoteSent).filter_by(vote_receiver_message=666)).all() From 58ac018267ed7c7bff20501e24a03b46b9e9f522 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 2 May 2023 16:28:54 +0100 Subject: [PATCH 21/41] feat: core migration --- koala/cogs/voting/cog.py | 261 ++++++--------------------------- koala/cogs/voting/core.py | 293 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 337 insertions(+), 217 deletions(-) create mode 100644 koala/cogs/voting/core.py diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 95ba1db3..2fac7d1b 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -16,6 +16,7 @@ # Own modules import koalabot from koala.db import session_manager, insert_extension +from . import core from .db import VoteManager, get_results, create_embed, add_reactions from .log import logger from .models import Votes @@ -92,57 +93,21 @@ async def cog_unload(self): @tasks.loop(seconds=60.0) async def vote_end_loop(self): - try: - with session_manager() as session: - now = time.time() - votes = session.execute(select(Votes.vote_id, Votes.author_id, Votes.guild_id, Votes.title, Votes.end_time) - .where(Votes.end_time < now)).all() - for v_id, a_id, g_id, title, end_time in votes: - if v_id in self.vote_manager.sent_votes.keys(): - vote = self.vote_manager.get_vote_from_id(v_id) - results = await get_results(self.bot, vote) - embed = await make_result_embed(vote, results) - try: - if vote.chair: - try: - chair = await self.bot.fetch_user(vote.chair) - await chair.send(f"Your vote {title} has closed") - await chair.send(embed=embed) - except discord.Forbidden: - user = await self.bot.fetch_user(vote.author) - await user.send(f"Your vote {title} has closed") - await user.send(embed=embed) - else: - try: - user = await self.bot.fetch_user(vote.author) - await user.send(f"Your vote {title} has closed") - await user.send(embed=embed) - except discord.Forbidden: - guild = await self.bot.fetch_guild(vote.guild) - user = await self.bot.fetch_user(guild.owner_id) - await user.send(f"A vote in your guild titled {title} has closed and the chair is unavailable.") - await user.send(embed=embed) - session.execute(delete(Votes).filter_by(vote_id=vote.id)) - session.commit() - self.vote_manager.cancel_sent_vote(vote.id) - except Exception as e: - session.execute(update(Votes).filter_by(vote_id=vote.id).values(end_time=time.time() + 86400)) - session.commit() - logger.error(f"error in vote loop: {e}") - except Exception as e: - logger.error("Exception in outer vote loop: %s" % e, exc_info=e) + await core.vote_end_loop(self.bot, self.vote_manager) @vote_end_loop.before_loop async def before_vote_loop(self): await self.bot.wait_until_ready() + @commands.Cog.listener() async def on_raw_reaction_add(self, payload): """ Listens for when a reaction is added to a message :param payload: payload of data about the reaction """ - await self.update_vote_message(payload.message_id, payload.user_id) + await core.update_vote_message(self.bot, payload.message_id, payload.user_id) + @commands.Cog.listener() async def on_raw_reaction_remove(self, payload): @@ -150,7 +115,8 @@ async def on_raw_reaction_remove(self, payload): Listens for when a reaction is removed from a message :param payload: payload of data about the reaction """ - await self.update_vote_message(payload.message_id, payload.user_id) + await core.update_vote_message(self.bot, payload.message_id, payload.user_id) + @commands.check(koalabot.is_admin) @commands.check(vote_is_enabled) @@ -159,8 +125,8 @@ async def vote(self, ctx): """ Use k!vote create <title> to create a vote! """ - if ctx.invoked_subcommand is None: - await ctx.send(f"Please use `{koalabot.COMMAND_PREFIX}help vote` for more information") + await ctx.send(core.vote(ctx.invoked_subcommand)) + @commands.check(koalabot.is_admin) @commands.check(vote_is_enabled) @@ -170,23 +136,8 @@ async def start_vote(self, ctx, *, title): Creates a new vote :param title: The title of the vote """ - with session_manager() as session: - if self.vote_manager.has_active_vote(ctx.author.id): - guild_name = self.bot.get_guild(self.vote_manager.get_configuring_vote(ctx.author.id).guild) - await ctx.send(f"You already have an active vote in {guild_name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one.") - return - - in_db = session.execute(select(Votes).filter_by(title=title, author_id=ctx.author.id)).all() - if in_db: - await ctx.send(f"You already have a vote with title {title} sent!") - return - - if len(title) > 200: - await ctx.send("Title too long") - return + await ctx.send(core.start_vote(self.bot, self.vote_manager, title, ctx.author, ctx.guild)) - self.vote_manager.create_vote(ctx.author.id, ctx.guild.id, title) - await ctx.send(f"Vote titled `{title}` created for guild {ctx.guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it.") @currently_configuring() @commands.check(vote_is_enabled) @@ -197,9 +148,8 @@ async def add_role(self, ctx, *, role: discord.Role): If no roles are added, the vote will go to all users in a guild (unless a target voice channel has been set) :param role: role id (e.g. 135496683009081345) or a role ping (e.g. @Student) """ - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - vote.add_role(role.id) - await ctx.send(f"Vote will be sent to those with the {role.name} role") + await ctx.send(core.set_roles(self.vote_manager, ctx.author, role, "add")) + @currently_configuring() @commands.check(vote_is_enabled) @@ -209,9 +159,8 @@ async def remove_role(self, ctx, *, role: discord.Role): Removes a role to the list of roles the vote will be sent to :param role: role id (e.g. 135496683009081345) or a role ping (e.g. @Student) """ - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - vote.remove_role(role.id) - await ctx.send(f"Vote will no longer be sent to those with the {role.name} role") + await ctx.send(core.set_roles(self.vote_manager, ctx.author, role, "remove")) + @currently_configuring() @commands.check(vote_is_enabled) @@ -222,17 +171,8 @@ async def set_chair(self, ctx, *, chair: discord.Member = None): If no chair defaults to sending the message to the channel the vote is closed in :param chair: user id (e.g. 135496683009081345) or ping (e.g. @ito#8813) """ - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - if chair: - try: - await chair.send(f"You have been selected as the chair for vote titled {vote.title}") - vote.set_chair(chair.id) - await ctx.send(f"Set chair to {chair.name}") - except discord.Forbidden: - await ctx.send("Chair not set as requested user is not accepting direct messages.") - else: - vote.set_chair(None) - await ctx.send(f"Results will be sent to the channel vote is closed in") + await ctx.send(await core.set_chair(self.vote_manager, ctx.author, chair)) + @currently_configuring() @commands.check(vote_is_enabled) @@ -243,13 +183,8 @@ async def set_channel(self, ctx, *, channel: discord.VoiceChannel = None): If there isn't one set votes will go to all users in a guild (unless target roles have been added) :param channel: channel id (e.g. 135496683009081345) or mention (e.g. #cool-channel) """ - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - if channel: - vote.set_vc(channel.id) - await ctx.send(f"Set target channel to {channel.name}") - else: - vote.set_vc() - await ctx.send("Removed channel restriction on vote") + await ctx.send(core.set_channel(self.vote_manager, ctx.author, channel)) + @currently_configuring() @commands.check(vote_is_enabled) @@ -260,20 +195,8 @@ async def add_option(self, ctx, *, option_string): separate the title and description with a "+" e.g. option title+option description :param option_string: a title and description for the option separated by a '+' """ - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - if len(vote.options) > 9: - await ctx.send("Vote has maximum number of options already (10)") - return - current_option_length = sum([len(x.head) + len(x.body) for x in vote.options]) - if current_option_length + len(option_string) > 1500: - await ctx.send(f"Option string is too long. The total length of all the vote options cannot be over 1500 characters.") - return - if "+" not in option_string: - await ctx.send("Example usage: k!vote addOption option title+option description") - return - header, body = option_string.split("+") - vote.add_option(Option(header, body, self.vote_manager.generate_unique_opt_id())) - await ctx.send(f"Option {header} with description {body} added to vote") + await ctx.send(core.add_option(self.vote_manager, ctx.author, option_string)) + @currently_configuring() @commands.check(vote_is_enabled) @@ -283,9 +206,8 @@ async def remove_option(self, ctx, index: int): Removes an option from a vote based on it's index :param index: the number of the option """ - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - vote.remove_option(index) - await ctx.send(f"Option number {index} removed") + await ctx.send(core.remove_option(self.vote_manager, ctx.author, index)) + @currently_configuring() @commands.check(vote_is_enabled) @@ -297,19 +219,8 @@ async def set_end_time(self, ctx, *, time_string): :param time_string: string representing a time e.g. "2021-03-22 12:56" or "tomorrow at 10am" or "in 5 days and 15 minutes" :return: """ - now = time.time() - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - cal = parsedatetime.Calendar() - end_time_readable = cal.parse(time_string)[0] - end_time = time.mktime(end_time_readable) - if (end_time - now) < 0: - await ctx.send("You can't set a vote to end in the past") - return - # if (end_time - now) < 599: - # await ctx.send("Please set the end time to be at least 10 minutes in the future.") - # return - vote.set_end_time(end_time) - await ctx.send(f"Vote set to end at {time.strftime('%Y-%m-%d %H:%M:%S', end_time_readable)} UTC") + await ctx.send(core.set_end_time(self.vote_manager, ctx.author, time_string)) + @currently_configuring() @commands.check(vote_is_enabled) @@ -318,9 +229,8 @@ async def preview_vote(self, ctx): """ Generates a preview of what users will see with the current configuration of the vote """ - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - msg = await ctx.send(embed=create_embed(vote)) - await add_reactions(vote, msg) + await core.preview(self.vote_manager, ctx) + @commands.check(vote_is_enabled) @has_current_votes() @@ -330,12 +240,8 @@ async def cancel_vote(self, ctx, *, title): Cancels a vote you are setting up or have sent :param title: title of the vote to cancel """ - v_id = self.vote_manager.vote_lookup[(ctx.author.id, title)] - if v_id in self.vote_manager.sent_votes.keys(): - self.vote_manager.cancel_sent_vote(v_id) - else: - self.vote_manager.cancel_configuring_vote(ctx.author.id) - await ctx.send(f"Vote {title} has been cancelled.") + await ctx.send(core.cancel_vote(self.vote_manager, ctx.author, title)) + @commands.check(vote_is_enabled) @has_current_votes() @@ -345,14 +251,8 @@ async def check_current_votes(self, ctx): Return a list of all votes you have in this guild. :return: """ - with session_manager() as session: - embed = discord.Embed(title="Your current votes") - votes = session.execute(select(Votes.title).filter_by(author_id=ctx.author.id, guild_id=ctx.guild.id)).all() - body_string = "" - for title in votes: - body_string += f"{title[0]}\n" - embed.add_field(name="Vote Title", value=body_string, inline=False) - await ctx.send(embed=embed) + await ctx.send(embed=core.current_votes(ctx.author, ctx.guild)) + @currently_configuring() @vote.command(name="send") @@ -360,34 +260,8 @@ async def send_vote(self, ctx): """ Sends a vote to all users within the restrictions set with the current options added """ - vote = self.vote_manager.get_configuring_vote(ctx.author.id) - - if not vote.is_ready(): - await ctx.send("Please add more than 1 option to vote for") - return - - self.vote_manager.configuring_votes.pop(ctx.author.id) - self.vote_manager.sent_votes[vote.id] = vote - - users = [x for x in ctx.guild.members if not x.bot] - if vote.target_voice_channel: - vc_users = discord.utils.get(ctx.guild.voice_channels, id=vote.target_voice_channel).members - users = list(set(vc_users) & set(users)) - if vote.target_roles: - role_users = [] - for role_id in vote.target_roles: - role = discord.utils.get(ctx.guild.roles, id=role_id) - role_users += role.members - role_users = list(dict.fromkeys(role_users)) - users = list(set(role_users) & set(users)) - for user in users: - try: - msg = await user.send(f"You have been asked to participate in this vote from {ctx.guild.name}.\nPlease react to make your choice (You can change your mind until the vote is closed)", embed=create_embed(vote)) - vote.register_sent(user.id, msg.id) - await add_reactions(vote, msg) - except discord.Forbidden: - logger.error(f"tried to send vote to user {user.id} but direct messages are turned off.") - await ctx.send(f"Sent vote to {len(users)} users") + await ctx.send(await core.send_vote(self.vote_manager, ctx.author, ctx.guild)) + @commands.check(vote_is_enabled) @has_current_votes() @@ -396,28 +270,14 @@ async def close(self, ctx, *, title): """ Ends a vote, and collects the results """ - vote_id = self.vote_manager.vote_lookup[(ctx.author.id, title)] - if vote_id not in self.vote_manager.sent_votes.keys(): - if ctx.author.id in self.vote_manager.configuring_votes.keys(): - await ctx.send(f"That vote has not been sent yet. Please send it to your audience with {koalabot.COMMAND_PREFIX}vote send {title}") - else: - await ctx.send("You have no votes of that title to close") - return - - vote = self.vote_manager.get_vote_from_id(vote_id) - results = await get_results(self.bot, vote) - self.vote_manager.cancel_sent_vote(vote.id) - embed = await make_result_embed(vote, results) - if vote.chair: - try: - chair = await self.bot.fetch_user(vote.chair) - await chair.send(embed=embed) - await ctx.send(f"Sent results to {chair}") - except discord.Forbidden: - await ctx.send("Chair does not accept direct messages, sending results here.") - await ctx.send(embed=embed) + msg = await core.close(self.bot, self.vote_manager, ctx.author) + if type(msg) is list: + await ctx.send(msg[0], embed=msg[1]) + elif type(msg) is discord.Embed: + await ctx.send(embed=msg) else: - await ctx.send(embed=embed) + await ctx.send(msg) + @commands.check(vote_is_enabled) @has_current_votes() @@ -426,44 +286,11 @@ async def check_results(self, ctx, *, title): """ Checks the results of a vote without closing it """ - vote_id = self.vote_manager.vote_lookup.get((ctx.author.id, title)) - if vote_id is None: - raise ValueError(f"{title} is not a valid vote title for user {ctx.author.name}") - - if vote_id not in self.vote_manager.sent_votes.keys(): - if ctx.author.id in self.vote_manager.configuring_votes.keys(): - await ctx.send( - f"That vote has not been sent yet. Please send it to your audience with {koalabot.COMMAND_PREFIX}vote send {title}") - else: - await ctx.send("You have no votes of that title to check") - return - - vote = self.vote_manager.get_vote_from_id(vote_id) - results = await get_results(self.bot, vote) - embed = await make_result_embed(vote, results) - await ctx.send(embed=embed) - - async def update_vote_message(self, message_id, user_id): - """ - Updates the vote message with the currently selected option - :param message_id: id of the message that was reacted on - :param user_id: id of the user who reacted - """ - vote = self.vote_manager.was_sent_to(message_id) - user = self.bot.get_user(user_id) - if vote and not user.bot: - msg = await user.fetch_message(message_id) - embed = msg.embeds[0] - choice = None - for reaction in msg.reactions: - if reaction.count > 1: - choice = reaction - break - if choice: - embed.set_footer(text=f"You will be voting for {choice.emoji} - {vote.options[VoteManager.emote_reference[choice.emoji]].head}") - else: - embed.set_footer(text="There are no valid choices selected") - await msg.edit(embed=embed) + msg = await core.results(self.bot, self.vote_manager, ctx.author, title) + if type(msg) is discord.Embed: + await ctx.send(embed=msg) + else: + await ctx.send(msg) async def setup(bot: koalabot) -> None: diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py new file mode 100644 index 00000000..4c13eee9 --- /dev/null +++ b/koala/cogs/voting/core.py @@ -0,0 +1,293 @@ +# Built-in/Generic Imports +import time + +# Libs +import discord +import parsedatetime.parsedatetime +from discord.ext import commands, tasks +from sqlalchemy import select, delete, update +from sqlalchemy.orm import Session + +# Own modules +import koalabot +from koala.db import assign_session, insert_extension +from .db import VoteManager, get_results, create_embed, add_reactions +from .log import logger +from .models import Votes +from .option import Option +from .utils import make_result_embed + +async def update_vote_message(bot: koalabot.KoalaBot, message_id, user_id): + """ + Updates the vote message with the currently selected option + :param message_id: id of the message that was reacted on + :param user_id: id of the user who reacted + """ + vote = bot.vote_manager.was_sent_to(message_id) + user = bot.bot.get_user(user_id) + if vote and not user.bot: + msg = await user.fetch_message(message_id) + embed = msg.embeds[0] + choice = None + for reaction in msg.reactions: + if reaction.count > 1: + choice = reaction + break + if choice: + embed.set_footer(text=f"You will be voting for {choice.emoji} - {vote.options[VoteManager.emote_reference[choice.emoji]].head}") + else: + embed.set_footer(text="There are no valid choices selected") + await msg.edit(embed=embed) + + +@assign_session +async def vote_end_loop(bot: koalabot.KoalaBot, vm: VoteManager, session: Session): + try: + now = time.time() + votes = session.execute(select(Votes.vote_id, Votes.author_id, Votes.guild_id, Votes.title, Votes.end_time) + .where(Votes.end_time < now)).all() + for v_id, a_id, g_id, title, end_time in votes: + if v_id in vm.sent_votes.keys(): + vote = vm.get_vote_from_id(v_id) + results = await get_results(bot, vote) + embed = await make_result_embed(vote, results) + try: + if vote.chair: + try: + chair = await bot.fetch_user(vote.chair) + await chair.send(f"Your vote {title} has closed") + await chair.send(embed=embed) + except discord.Forbidden: + user = await bot.fetch_user(vote.author) + await user.send(f"Your vote {title} has closed") + await user.send(embed=embed) + else: + try: + user = await bot.fetch_user(vote.author) + await user.send(f"Your vote {title} has closed") + await user.send(embed=embed) + except discord.Forbidden: + guild = await bot.fetch_guild(vote.guild) + user = await bot.fetch_user(guild.owner_id) + await user.send(f"A vote in your guild titled {title} has closed and the chair is unavailable.") + await user.send(embed=embed) + session.execute(delete(Votes).filter_by(vote_id=vote.id)) + session.commit() + vm.cancel_sent_vote(vote.id) + except Exception as e: + session.execute(update(Votes).filter_by(vote_id=vote.id).values(end_time=time.time() + 86400)) + session.commit() + logger.error(f"error in vote loop: {e}") + except Exception as e: + logger.error("Exception in outer vote loop: %s" % e, exc_info=e) + + +def vote(cmd): + if cmd is None: + return f"Please use `{koalabot.COMMAND_PREFIX}help vote` for more information" + + +@assign_session +def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author, guild, session: Session): + + # Key Error: author id in get_configuring_vote line 144 in db.py + # instantiating get_configuring_vote(ctx.author.id) in cog also gives same error so it's not the vm's problem + # guild_name = bot.get_guild(vm.get_configuring_vote(author.id).guild) + + # is there a reason we use this instead of guild.name + guild_name = author.guild.name + + # assertion error. I don't know how or what or why. They look the same sentence. + if vm.has_active_vote(author.id): + return f"You already have an active vote in {guild_name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." + + in_db = session.execute(select(Votes).filter_by(title=title, author_id=author.id)).all() + if in_db: + return f"You already have a vote with title {title} sent!" + + if len(title) > 200: + return "Title too long" + + vm.create_vote(author.id, guild.id, title) + return f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." + + +def set_roles(vm: VoteManager, author, role, action): + + # get_configuring_vote might break + vote = vm.get_configuring_vote(author.id) + + if action == "add": + vote.add_role(role.id) + return f"Vote will be sent to those with the {role.name} role" + + if action == "remove": + vote.remove_role(role.id) + return f"Vote will no longer be sent to those with the {role.name} role" + + +async def set_chair(vm: VoteManager, author, chair): + vote = vm.get_configuring_vote(author.id) + + if chair: + try: + await chair.send(f"You have been selected as the chair for vote titled {vote.title}") + vote.set_chair(chair.id) + return f"Set chair to {chair.name}" + except discord.Forbidden: + return "Chair not set as requested user is not accepting direct messages." + else: + vote.set_chair(None) + return "Results will be sent to the channel vote is closed in" + + +async def set_channel(vm: VoteManager, author, channel): + # get_configuring_vote might break + vote = vm.get_configuring_vote(author.id) + + if channel: + vote.set_vc(channel.id) + return f"Set target channel to {channel.name}" + else: + vote.set_vc() + return "Removed channel restriction on vote" + + +def add_option(vm: VoteManager, author, option_string): + # get_configuring_vote might break + vote = vm.get_configuring_vote(author.id) + + if len(vote.options) > 9: + return "Vote has maximum number of options already (10)" + + current_option_length = sum([len(x.head) + len(x.body) for x in vote.options]) + + if current_option_length + len(option_string) > 1500: + return "Option string is too long. The total length of all the vote options cannot be over 1500 characters." + + if "+" not in option_string: + return "Example usage: k!vote addOption option title+option description" + + header, body = option_string.split("+") + vote.add_option(Option(header, body, vm.generate_unique_opt_id())) + return f"Option {header} with description {body} added to vote" + + +def remove_option(vm: VoteManager, author, index): + # you know hte drill + vote = vm.get_configuring_vote(author.id) + vote.remove_option(index) + return f"Option number {index} removed" + + +def set_end_time(vm: VoteManager, author, time_string): + now = time.time() + vote = vm.get_configuring_vote(author.id) + cal = parsedatetime.Calendar() + end_time_readable = cal.parse(time_string)[0] + end_time = time.mktime(end_time_readable) + if (end_time - now) < 0: + return "You can't set a vote to end in the past" + # if (end_time - now) < 599: + # return "Please set the end time to be at least 10 minutes in the future." + vote.set_end_time(end_time) + return f"Vote set to end at {time.strftime('%Y-%m-%d %H:%M:%S', end_time_readable)} UTC" + + +async def preview(vm: VoteManager, ctx): + # get configuring vote potential error + vote = vm.get_configuring_vote(ctx.author.id) + msg = await ctx.send(embed=create_embed(vote)) + await add_reactions(vote, msg) + + +def cancel_vote(vm: VoteManager, author, title): + # i don't even know what errors this could have but there will be + v_id = vm.vote_lookup[(author.id, title)] + if v_id in vm.sent_votes.keys(): + vm.cancel_sent_vote(v_id) + else: + vm.cancel_configuring_vote(author.id) + return f"Vote {title} has been cancelled." + + +@assign_session +def current_votes(author, guild, session: Session): + embed = discord.Embed(title="Your current votes") + votes = session.execute(select(Votes.title).filter_by(author_id=author.id, guild_id=guild.id)).all() + body_string = "" + for title in votes: + body_string += f"{title[0]}\n" + embed.add_field(name="Vote Title", value=body_string, inline=False) + return embed + + +async def send_vote(vm: VoteManager, author, guild): + # configuring votes + vote = vm.get_configuring_vote(author.id) + + if not vote.is_ready(): + return "Please add more than 1 option to vote for" + + vm.configuring_votes.pop(author.id) + vm.sent_votes[vote.id] = vote + + users = [x for x in guild.members if not x.bot] + if vote.target_voice_channel: + vc_users = discord.utils.get(guild.voice_channels, id=vote.target_voice_channel).members + users = list(set(vc_users) & set(users)) + if vote.target_roles: + role_users = [] + for role_id in vote.target_roles: + role = discord.utils.get(guild.roles, id=role_id) + role_users += role.members + role_users = list(dict.fromkeys(role_users)) + users = list(set(role_users) & set(users)) + for user in users: + try: + msg = await user.send(f"You have been asked to participate in this vote from {guild.name}.\nPlease react to make your choice (You can change your mind until the vote is closed)", embed=create_embed(vote)) + vote.register_sent(user.id, msg.id) + await add_reactions(vote, msg) + except discord.Forbidden: + logger.error(f"tried to send vote to user {user.id} but direct messages are turned off.") + return f"Sent vote to {len(users)} users" + + +async def close(bot: koalabot.KoalaBot, vm: VoteManager, author, title): + vote_id = vm.vote_lookup[(author.id, title)] + if vote_id not in vm.sent_votes.keys(): + if author.id in vm.configuring_votes.keys(): + return f"That vote has not been sent yet. Please send it to your audience with {koalabot.COMMAND_PREFIX}vote send {title}" + else: + return "You have no votes of that title to close" + + vote = vm.get_vote_from_id(vote_id) + results = await get_results(bot, vote) + vm.cancel_sent_vote(vote.id) + embed = await make_result_embed(vote, results) + if vote.chair: + try: + chair = await bot.fetch_user(vote.chair) + await chair.send(embed=embed) + return f"Sent results to {chair}" + except discord.Forbidden: + return ["Chair does not accept direct messages, sending results here.", embed] + else: + return embed + + +async def results(bot: koalabot.KoalaBot, vm: VoteManager, author, title): + vote_id = vm.vote_lookup.get((author.id, title)) + if vote_id is None: + raise ValueError(f"{title} is not a valid vote title for user {author.name}") + + if vote_id not in vm.sent_votes.keys(): + if author.id in vm.configuring_votes.keys(): + return f"That vote has not been sent yet. Please send it to your audience with {koalabot.COMMAND_PREFIX}vote send {title}" + else: + return "You have no votes of that title to check" + + vote = vm.get_vote_from_id(vote_id) + results = await get_results(bot, vote) + embed = await make_result_embed(vote, results) + return embed \ No newline at end of file From 325f007ce424f0685f89c20b65b7ceb54fb8a184 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 2 May 2023 20:55:34 +0100 Subject: [PATCH 22/41] test: initial core tests --- koala/cogs/voting/cog.py | 11 ++--- koala/cogs/voting/core.py | 8 ++-- tests/cogs/voting/test_core.py | 88 ++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 tests/cogs/voting/test_core.py diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 2fac7d1b..0b9c2715 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -5,23 +5,17 @@ Commented using reStructuredText (reST) """ # Built-in/Generic Imports -import time # Libs import discord -import parsedatetime.parsedatetime from discord.ext import commands, tasks -from sqlalchemy import select, delete, update # Own modules import koalabot -from koala.db import session_manager, insert_extension +from koala.db import insert_extension from . import core -from .db import VoteManager, get_results, create_embed, add_reactions +from .db import VoteManager from .log import logger -from .models import Votes -from .option import Option -from .utils import make_result_embed # Constants @@ -118,6 +112,7 @@ async def on_raw_reaction_remove(self, payload): await core.update_vote_message(self.bot, payload.message_id, payload.user_id) + # how do you even procc this @commands.check(koalabot.is_admin) @commands.check(vote_is_enabled) @commands.group(name="vote") diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 4c13eee9..4cbee98b 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -4,13 +4,12 @@ # Libs import discord import parsedatetime.parsedatetime -from discord.ext import commands, tasks from sqlalchemy import select, delete, update from sqlalchemy.orm import Session # Own modules import koalabot -from koala.db import assign_session, insert_extension +from koala.db import assign_session from .db import VoteManager, get_results, create_embed, add_reactions from .log import logger from .models import Votes @@ -82,6 +81,7 @@ async def vote_end_loop(bot: koalabot.KoalaBot, vm: VoteManager, session: Sessio logger.error("Exception in outer vote loop: %s" % e, exc_info=e) +# is needed? raises a CommandNotFound error def vote(cmd): if cmd is None: return f"Please use `{koalabot.COMMAND_PREFIX}help vote` for more information" @@ -94,10 +94,10 @@ def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author, guild, se # instantiating get_configuring_vote(ctx.author.id) in cog also gives same error so it's not the vm's problem # guild_name = bot.get_guild(vm.get_configuring_vote(author.id).guild) - # is there a reason we use this instead of guild.name + # is there a reason we use the above instead of this guild_name = author.guild.name - # assertion error. I don't know how or what or why. They look the same sentence. + # assertion error in test_cog. I don't know how or what or why. They look the same sentence. if vm.has_active_vote(author.id): return f"You already have an active vote in {guild_name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py new file mode 100644 index 00000000..97a73b97 --- /dev/null +++ b/tests/cogs/voting/test_core.py @@ -0,0 +1,88 @@ +#Libs +import discord +import discord.ext.test as dpytest +import pytest +import pytest_asyncio +from discord.ext import commands +from sqlalchemy import select +from koala.cogs import Voting +from koala.cogs.voting.models import Votes + +# Own modules +import koalabot +from koala.db import assign_session, session_manager, insert_extension +from tests.log import logger +from koala.cogs.voting import core + + +@pytest_asyncio.fixture(scope="function", autouse=True) +async def cog(bot: commands.Bot): + cog = Voting(bot) + await bot.add_cog(cog) + dpytest.configure(bot) + logger.info("Tests starting") + return cog + + +@pytest.mark.asyncio +async def test_update_vote_message(bot: commands.Bot): + pass + # await core.update_vote_message(bot) + + +# CommandNotFound error +# @pytest.mark.asyncio +# async def test_vote_command(): +# await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote") +# assert dpytest.verify().message().content(f"Please use `{koalabot.COMMAND_PREFIX}help vote` for more information") + + +def test_create_vote(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + assert core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) == f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." + + +@pytest.mark.asyncio +async def test_vote_already_created(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") + + assert core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) == f"You already have an active vote in {guild.name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." + + +@assign_session +@pytest.mark.asyncio +async def test_vote_already_sent(bot: commands.Bot, cog, session): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + session.add(Votes(vote_id=111, author_id=author.id, guild_id=guild.id, title="Test Vote")) + session.commit() + + assert core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) == "You already have a vote with title Test Vote sent!" + + +@pytest.mark.asyncio +async def test_add_role(bot: commands.Bot, cog, session): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + role: discord.Role = dpytest.back.make_role("testRole", guild, id_num=555) + + await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") + assert core.set_roles(cog.vote_manager, author, role, "add") == f"Vote will be sent to those with the {role.name} role" + + +@pytest.mark.asyncio +async def test_remove_role(bot: commands.Bot, cog, session): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + role: discord.Role = dpytest.back.make_role("testRole", guild, id_num=555) + + await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") + core.set_roles(cog.vote_manager, author, role, "add") + + assert core.set_roles(cog.vote_manager, author, role, "remove") == f"Vote will no longer be sent to those with the {role.name} role" \ No newline at end of file From 394254cd485ed7520cf5feb942d3710b90ac8f82 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 2 May 2023 22:28:26 +0100 Subject: [PATCH 23/41] debug: peek --- tests/cogs/voting/test_cog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cogs/voting/test_cog.py b/tests/cogs/voting/test_cog.py index 80f9800f..785b9221 100644 --- a/tests/cogs/voting/test_cog.py +++ b/tests/cogs/voting/test_cog.py @@ -39,6 +39,7 @@ async def test_discord_create_vote(): config = dpytest.get_config() guild = config.guilds[0] await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") + dpytest.sent_queue.peek() assert dpytest.verify().message().content( f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote`" f" to see how to configure it.") From 188b081a99b16c0623327404e02af9981fd03aaa Mon Sep 17 00:00:00 2001 From: Jack Draper <drapj002@gmail.com> Date: Tue, 2 May 2023 22:43:21 +0100 Subject: [PATCH 24/41] fix: voting group message issue --- koala/cogs/voting/cog.py | 3 ++- koala/cogs/voting/core.py | 6 ------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 0b9c2715..8d6ced80 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -120,7 +120,8 @@ async def vote(self, ctx): """ Use k!vote create <title> to create a vote! """ - await ctx.send(core.vote(ctx.invoked_subcommand)) + if ctx.invoked_subcommand is None: + await ctx.send(f"Please use `{koalabot.COMMAND_PREFIX}help vote` for more information") @commands.check(koalabot.is_admin) diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 4cbee98b..ed1352e3 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -81,12 +81,6 @@ async def vote_end_loop(bot: koalabot.KoalaBot, vm: VoteManager, session: Sessio logger.error("Exception in outer vote loop: %s" % e, exc_info=e) -# is needed? raises a CommandNotFound error -def vote(cmd): - if cmd is None: - return f"Please use `{koalabot.COMMAND_PREFIX}help vote` for more information" - - @assign_session def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author, guild, session: Session): From 3b452af22b17e47c6b09de95f3313725ff630a6b Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 2 May 2023 22:54:42 +0100 Subject: [PATCH 25/41] test: fix cog tests --- koala/cogs/voting/core.py | 2 +- tests/cogs/voting/test_cog.py | 5 ++--- tests/cogs/voting/test_core.py | 7 ------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index ed1352e3..0f578b8d 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -103,7 +103,7 @@ def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author, guild, se return "Title too long" vm.create_vote(author.id, guild.id, title) - return f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." + return f"Vote titled `{title}` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." def set_roles(vm: VoteManager, author, role, action): diff --git a/tests/cogs/voting/test_cog.py b/tests/cogs/voting/test_cog.py index 785b9221..ca906955 100644 --- a/tests/cogs/voting/test_cog.py +++ b/tests/cogs/voting/test_cog.py @@ -9,6 +9,7 @@ # Built-in/Generic Imports # Libs +import discord import discord.ext.test as dpytest import pytest import pytest_asyncio @@ -36,10 +37,8 @@ async def cog(bot: commands.Bot): @pytest.mark.asyncio async def test_discord_create_vote(): with session_manager() as session: - config = dpytest.get_config() - guild = config.guilds[0] + guild: discord.Guild = dpytest.get_config().guilds[0] await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") - dpytest.sent_queue.peek() assert dpytest.verify().message().content( f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote`" f" to see how to configure it.") diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index 97a73b97..290fd361 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -30,13 +30,6 @@ async def test_update_vote_message(bot: commands.Bot): # await core.update_vote_message(bot) -# CommandNotFound error -# @pytest.mark.asyncio -# async def test_vote_command(): -# await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote") -# assert dpytest.verify().message().content(f"Please use `{koalabot.COMMAND_PREFIX}help vote` for more information") - - def test_create_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] From 9bcada1e72e9b206c0e66553c91920e022f80544 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 2 May 2023 23:44:13 +0100 Subject: [PATCH 26/41] test: core chair and channel tests --- koala/cogs/voting/core.py | 4 +-- tests/cogs/voting/test_core.py | 60 ++++++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 0f578b8d..63b965a8 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -120,7 +120,7 @@ def set_roles(vm: VoteManager, author, role, action): return f"Vote will no longer be sent to those with the {role.name} role" -async def set_chair(vm: VoteManager, author, chair): +async def set_chair(vm: VoteManager, author, chair=None): vote = vm.get_configuring_vote(author.id) if chair: @@ -135,7 +135,7 @@ async def set_chair(vm: VoteManager, author, chair): return "Results will be sent to the channel vote is closed in" -async def set_channel(vm: VoteManager, author, channel): +def set_channel(vm: VoteManager, author, channel=None): # get_configuring_vote might break vote = vm.get_configuring_vote(author.id) diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index 290fd361..0fc52797 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -1,6 +1,7 @@ #Libs import discord import discord.ext.test as dpytest +import mock import pytest import pytest_asyncio from discord.ext import commands @@ -70,7 +71,7 @@ async def test_add_role(bot: commands.Bot, cog, session): @pytest.mark.asyncio -async def test_remove_role(bot: commands.Bot, cog, session): +async def test_remove_role(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] role: discord.Role = dpytest.back.make_role("testRole", guild, id_num=555) @@ -78,4 +79,59 @@ async def test_remove_role(bot: commands.Bot, cog, session): await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") core.set_roles(cog.vote_manager, author, role, "add") - assert core.set_roles(cog.vote_manager, author, role, "remove") == f"Vote will no longer be sent to those with the {role.name} role" \ No newline at end of file + assert core.set_roles(cog.vote_manager, author, role, "remove") == f"Vote will no longer be sent to those with the {role.name} role" + + +@pytest.mark.asyncio +async def test_set_chair(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + chair: discord.Member = guild.members[1] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert await core.set_chair(cog.vote_manager, author, chair) == f"Set chair to {chair.name}" + + +# failing because idk how to mock a blocked dm channel +@pytest.mark.asyncio +async def test_set_chair_no_dms(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + chair: discord.Member = guild.members[1] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + # dpytest.back.start_private_message + with mock.patch('discord.channel.DMChannel', mock.Mock(side_effect=Exception('discord.Forbidden'))): + with pytest.raises(discord.Forbidden, match="Chair not set as requested user is not accepting direct messages."): + await core.set_chair(cog.vote_manager, author, chair) + + +@pytest.mark.asyncio +async def test_set_no_chair(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert await core.set_chair(cog.vote_manager, author) == "Results will be sent to the channel vote is closed in" + + +# make_voice_channel doesn't exist even though it's in their documentation +@pytest.mark.asyncio +async def test_set_channel(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + channel = dpytest.back.make_voice_channel("Voice Channel", guild) + + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert core.set_channel(cog.vote_manager, author, channel) == f"Set target channel to {channel.name}" + + +@pytest.mark.asyncio +async def test_set_no_channel(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert core.set_channel(cog.vote_manager, author) == "Removed channel restriction on vote" \ No newline at end of file From a637193d6462096b0dd334c66c4fef3d91ac723f Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Wed, 3 May 2023 00:12:49 +0100 Subject: [PATCH 27/41] test: core options, end time, stubs --- koala/cogs/voting/core.py | 21 ++--- tests/cogs/voting/test_core.py | 141 +++++++++++++++++++++++++++++++-- 2 files changed, 140 insertions(+), 22 deletions(-) diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 63b965a8..62ca51ef 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -83,15 +83,8 @@ async def vote_end_loop(bot: koalabot.KoalaBot, vm: VoteManager, session: Sessio @assign_session def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author, guild, session: Session): - - # Key Error: author id in get_configuring_vote line 144 in db.py - # instantiating get_configuring_vote(ctx.author.id) in cog also gives same error so it's not the vm's problem - # guild_name = bot.get_guild(vm.get_configuring_vote(author.id).guild) - - # is there a reason we use the above instead of this guild_name = author.guild.name - # assertion error in test_cog. I don't know how or what or why. They look the same sentence. if vm.has_active_vote(author.id): return f"You already have an active vote in {guild_name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." @@ -107,8 +100,6 @@ def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author, guild, se def set_roles(vm: VoteManager, author, role, action): - - # get_configuring_vote might break vote = vm.get_configuring_vote(author.id) if action == "add": @@ -136,7 +127,6 @@ async def set_chair(vm: VoteManager, author, chair=None): def set_channel(vm: VoteManager, author, channel=None): - # get_configuring_vote might break vote = vm.get_configuring_vote(author.id) if channel: @@ -148,7 +138,6 @@ def set_channel(vm: VoteManager, author, channel=None): def add_option(vm: VoteManager, author, option_string): - # get_configuring_vote might break vote = vm.get_configuring_vote(author.id) if len(vote.options) > 9: @@ -168,10 +157,12 @@ def add_option(vm: VoteManager, author, option_string): def remove_option(vm: VoteManager, author, index): - # you know hte drill vote = vm.get_configuring_vote(author.id) - vote.remove_option(index) - return f"Option number {index} removed" + try: + vote.remove_option(index) + return f"Option number {index} removed" + except IndexError: + return f"Option number {index} not found" def set_end_time(vm: VoteManager, author, time_string): @@ -189,14 +180,12 @@ def set_end_time(vm: VoteManager, author, time_string): async def preview(vm: VoteManager, ctx): - # get configuring vote potential error vote = vm.get_configuring_vote(ctx.author.id) msg = await ctx.send(embed=create_embed(vote)) await add_reactions(vote, msg) def cancel_vote(vm: VoteManager, author, title): - # i don't even know what errors this could have but there will be v_id = vm.vote_lookup[(author.id, title)] if v_id in vm.sent_votes.keys(): vm.cancel_sent_vote(v_id) diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index 0fc52797..27dd2bc3 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -100,7 +100,8 @@ async def test_set_chair_no_dms(bot: commands.Bot, cog): chair: discord.Member = guild.members[1] core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - # dpytest.back.start_private_message + # dpytest.back.start_private_message? + # pytest.raises is NOT the way to go here. the Forbidden is excepted, not thrown. with mock.patch('discord.channel.DMChannel', mock.Mock(side_effect=Exception('discord.Forbidden'))): with pytest.raises(discord.Forbidden, match="Chair not set as requested user is not accepting direct messages."): await core.set_chair(cog.vote_manager, author, chair) @@ -116,8 +117,7 @@ async def test_set_no_chair(bot: commands.Bot, cog): # make_voice_channel doesn't exist even though it's in their documentation -@pytest.mark.asyncio -async def test_set_channel(bot: commands.Bot, cog): +def test_set_channel(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] channel = dpytest.back.make_voice_channel("Voice Channel", guild) @@ -127,11 +127,140 @@ async def test_set_channel(bot: commands.Bot, cog): assert core.set_channel(cog.vote_manager, author, channel) == f"Set target channel to {channel.name}" -@pytest.mark.asyncio -async def test_set_no_channel(bot: commands.Bot, cog): +def test_set_no_channel(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - assert core.set_channel(cog.vote_manager, author) == "Removed channel restriction on vote" \ No newline at end of file + assert core.set_channel(cog.vote_manager, author) == "Removed channel restriction on vote" + + +def test_add_option(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert core.add_option(cog.vote_manager, author, "Option 1+Option description") == "Option Option 1 with description Option description added to vote" + +def test_add_option_wrong_formatting(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert core.add_option(cog.vote_manager, author, "Option 1") == "Example usage: k!vote addOption option title+option description" + + +def test_add_option_too_many(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + x = 0 + while (x < 11): + core.add_option(cog.vote_manager, author, "more+options") + x += 1 + + assert core.add_option(cog.vote_manager, author, "more options+please?") == "Vote has maximum number of options already (10)" + + +def test_add_option_too_long(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + test_option = "i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup+i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably." + x = 0 + while (x < 5): + core.add_option(cog.vote_manager, author, test_option) + x += 1 + + assert core.add_option(cog.vote_manager, author, test_option) == "Option string is too long. The total length of all the vote options cannot be over 1500 characters." + + +def test_remove_option(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + core.add_option(cog.vote_manager, author, "test+option") + + assert core.remove_option(cog.vote_manager, author, 0) == "Option number 0 removed" + + +def test_remove_option_bad(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert core.remove_option(cog.vote_manager, author, 0) == "Option number 0 not found" + + +def test_set_end_time(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert core.set_end_time(cog.vote_manager, author, "2222-12-30 13:30") == "Vote set to end at 2222-12-30 13:30:00 UTC" + + +def test_set_impossible_end_time(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert core.set_end_time(cog.vote_manager, author, "2020-01-15 12:50") == "You can't set a vote to end in the past" + + +def test_preview(bot: commands.Bot, cog): + # guild: discord.Guild = dpytest.get_config().guilds[0] + # author: discord.Member = guild.members[0] + # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + pass + + +def test_cancel_vote(bot: commands.Bot, cog): + # guild: discord.Guild = dpytest.get_config().guilds[0] + # author: discord.Member = guild.members[0] + # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + pass + + +def test_current_votes(bot: commands.Bot, cog): + # guild: discord.Guild = dpytest.get_config().guilds[0] + # author: discord.Member = guild.members[0] + # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + pass + + +@pytest.mark.asyncio +async def test_close(bot: commands.Bot, cog): + # guild: discord.Guild = dpytest.get_config().guilds[0] + # author: discord.Member = guild.members[0] + # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + pass + + +@pytest.mark.asyncio +async def test_send_vote(bot: commands.Bot, cog): + # guild: discord.Guild = dpytest.get_config().guilds[0] + # author: discord.Member = guild.members[0] + # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + pass + + +@pytest.mark.asyncio +async def test_get_results(bot: commands.Bot, cog): + # guild: discord.Guild = dpytest.get_config().guilds[0] + # author: discord.Member = guild.members[0] + # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + pass \ No newline at end of file From 5b14ee34b0902777ce06923655df96bc5bffaaa1 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Sun, 7 May 2023 22:45:23 +0100 Subject: [PATCH 28/41] test: add remaining core tests --- koala/cogs/voting/cog.py | 6 +- koala/cogs/voting/core.py | 8 +- tests/cogs/voting/test_core.py | 137 ++++++++++++++++++++++++++------- 3 files changed, 117 insertions(+), 34 deletions(-) diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 8d6ced80..2cc5e557 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -14,7 +14,7 @@ import koalabot from koala.db import insert_extension from . import core -from .db import VoteManager +from .db import VoteManager, add_reactions from .log import logger @@ -225,7 +225,9 @@ async def preview_vote(self, ctx): """ Generates a preview of what users will see with the current configuration of the vote """ - await core.preview(self.vote_manager, ctx) + prev = core.preview(self.vote_manager, ctx.author) + msg = await ctx.send(embed=prev[0]) + await add_reactions(prev[1], msg) @commands.check(vote_is_enabled) diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 62ca51ef..29a05453 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -179,10 +179,9 @@ def set_end_time(vm: VoteManager, author, time_string): return f"Vote set to end at {time.strftime('%Y-%m-%d %H:%M:%S', end_time_readable)} UTC" -async def preview(vm: VoteManager, ctx): - vote = vm.get_configuring_vote(ctx.author.id) - msg = await ctx.send(embed=create_embed(vote)) - await add_reactions(vote, msg) +def preview(vm: VoteManager, author): + vote = vm.get_configuring_vote(author.id) + return [create_embed(vote), vote] def cancel_vote(vm: VoteManager, author, title): @@ -206,7 +205,6 @@ def current_votes(author, guild, session: Session): async def send_vote(vm: VoteManager, author, guild): - # configuring votes vote = vm.get_configuring_vote(author.id) if not vote.is_ready(): diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index 27dd2bc3..0156c54b 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -216,51 +216,134 @@ def test_set_impossible_end_time(bot: commands.Bot, cog): def test_preview(bot: commands.Bot, cog): - # guild: discord.Guild = dpytest.get_config().guilds[0] - # author: discord.Member = guild.members[0] - # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - pass + prev = core.preview(cog.vote_manager, author) + assert prev[0].title == "Test Vote" -def test_cancel_vote(bot: commands.Bot, cog): - # guild: discord.Guild = dpytest.get_config().guilds[0] - # author: discord.Member = guild.members[0] - # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) +@pytest.mark.asyncio +async def test_cancel_sent_vote(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.add_option(cog.vote_manager, author, "Option 1+Option description") + core.add_option(cog.vote_manager, author, "Option 2+Option description2") - pass + await core.send_vote(cog.vote_manager, author, guild) + assert core.cancel_vote(cog.vote_manager, author, "Test Vote") == "Vote Test Vote has been cancelled." -def test_current_votes(bot: commands.Bot, cog): - # guild: discord.Guild = dpytest.get_config().guilds[0] - # author: discord.Member = guild.members[0] - # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - pass +def test_cancel_unsent_vote(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + assert core.cancel_vote(cog.vote_manager, author, "Test Vote") == "Vote Test Vote has been cancelled." @pytest.mark.asyncio -async def test_close(bot: commands.Bot, cog): - # guild: discord.Guild = dpytest.get_config().guilds[0] - # author: discord.Member = guild.members[0] - # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) +async def test_current_votes(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + embed = core.current_votes(author, guild) + assert embed.title == "Your current votes" - pass + +@pytest.mark.asyncio +async def test_close_no_chair(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.add_option(cog.vote_manager, author, "Option 1+Option description") + core.add_option(cog.vote_manager, author, "Option 2+Option description2") + + await core.send_vote(cog.vote_manager, author, guild) + + embed = await core.close(bot, cog.vote_manager, author, "Test Vote") + assert embed.title == "Test Vote Results:" + assert embed.fields[0].name == "Option 1" + assert embed.fields[1].name == "Option 2" +@pytest.mark.asyncio +async def test_close_with_chair(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + chair: discord.Member = guild.members[1] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.add_option(cog.vote_manager, author, "Option 1+Option description") + core.add_option(cog.vote_manager, author, "Option 2+Option description2") + await core.set_chair(cog.vote_manager, author, chair) + + await core.send_vote(cog.vote_manager, author, guild) + + assert await core.close(bot, cog.vote_manager, author, "Test Vote") == f"Sent results to {chair}" + + @pytest.mark.asyncio async def test_send_vote(bot: commands.Bot, cog): - # guild: discord.Guild = dpytest.get_config().guilds[0] - # author: discord.Member = guild.members[0] - # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.add_option(cog.vote_manager, author, "Option 1+Option description") + core.add_option(cog.vote_manager, author, "Option 2+Option description2") + + # not sure how to assert DM sent + + assert await core.send_vote(cog.vote_manager, author, guild) == "Sent vote to 1 users" - pass + +@pytest.mark.asyncio +async def test_send_vote_bad_options(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + + # no options + assert await core.send_vote(cog.vote_manager, author, guild) == "Please add more than 1 option to vote for" + + # only 1 option + core.add_option(cog.vote_manager, author, "Option 1+Option description") + assert await core.send_vote(cog.vote_manager, author, guild) == "Please add more than 1 option to vote for" @pytest.mark.asyncio async def test_get_results(bot: commands.Bot, cog): - # guild: discord.Guild = dpytest.get_config().guilds[0] - # author: discord.Member = guild.members[0] - # core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.add_option(cog.vote_manager, author, "Option 1+Option description") + core.add_option(cog.vote_manager, author, "Option 2+Option description2") + + await core.send_vote(cog.vote_manager, author, guild) + + embed = await core.results(bot, cog.vote_manager, author, "Test Vote") + assert embed.title == "Test Vote Results:" + assert embed.fields[0].name == "Option 1" + assert embed.fields[1].name == "Option 2" + + +@pytest.mark.asyncio +async def test_results_vote_not_sent(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.add_option(cog.vote_manager, author, "Option 1+Option description") + core.add_option(cog.vote_manager, author, "Option 2+Option description2") + + assert await core.results(bot, cog.vote_manager, author, "Test Vote") == "That vote has not been sent yet. Please send it to your audience with k!vote send Test Vote" + + +@pytest.mark.asyncio +async def test_results_invalid_vote(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] - pass \ No newline at end of file + with pytest.raises(ValueError, match=f"invalid is not a valid vote title for user {author.name}"): + await core.results(bot, cog.vote_manager, author, "invalid") \ No newline at end of file From dea11bf21b05563c4c15ae98f7b5603253460f0c Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Sun, 7 May 2023 22:59:19 +0100 Subject: [PATCH 29/41] feat: api skeleton --- koala/cogs/voting/api.py | 86 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 koala/cogs/voting/api.py diff --git a/koala/cogs/voting/api.py b/koala/cogs/voting/api.py new file mode 100644 index 00000000..e1d94cf3 --- /dev/null +++ b/koala/cogs/voting/api.py @@ -0,0 +1,86 @@ +# Futures +# Built-in/Generic Imports +# Libs +from http.client import CREATED + +import discord +from aiohttp import web +from discord.ext.commands import Bot + +from koala.rest.api import parse_request, build_response +# Own modules +from . import core +from .log import logger + +# Constants +VOTING_ENDPOINT = 'voting' +CONFIG_ENDPOINT = 'config' +RESULTS_ENDOPINT = 'results' + +# Variables + +class VotingEndpoint: + """ + The API endpoints for Voting + """ + def __init__(self, bot): + self._bot = bot + + def register(self, app): + """ + Register the routes for the given application + todo: review aiohttp 'views' and see if they are a better idea + :param app: The aiohttp.web.Application (likely of the sub app) + :return: app + """ + app.add_routes([web.post('/{endpoint}'.format(endpoint=CONFIG_ENDPOINT), self.post_new_vote), + web.get('/{endpoint}'.format(endpoint=CONFIG_ENDPOINT), self.get_current_votes), + web.post('/{endpoint}'.format(endpoint=RESULTS_ENDOPINT), self.post_close_results), + web.get('/{endpoint}'.format(endpoint=RESULTS_ENDOPINT), self.get_results)]) + return app + + + @parse_request + async def post_new_vote(self): + """ + Create a new vote. + :param : + :return: The list of ScheduledActivities + """ + pass + + + @parse_request + async def get_current_votes(self): + """ + Gets list of open votes. + """ + pass + + + @parse_request + async def post_close_results(self): + """ + Gets results and closes the vote. + """ + pass + + + @parse_request + async def get_results(self): + """ + Gets results, but does not close the vote. + """ + pass + + +def setup(bot: Bot): + """ + Load this cog to the KoalaBot. + :param bot: the bot client for KoalaBot + """ + sub_app = web.Application() + endpoint = VotingEndpoint(bot) + endpoint.register(sub_app) + getattr(bot, "koala_web_app").add_subapp('/{extension}'.format(extension=VOTING_ENDPOINT), sub_app) + logger.info("Voting API is ready.") From 212b35756d11fab57612dc0917a6421cfb11d671 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Mon, 22 May 2023 13:37:29 +0100 Subject: [PATCH 30/41] refactor: use id as params --- koala/cogs/voting/cog.py | 28 +++--- koala/cogs/voting/core.py | 78 +++++++++-------- tests/cogs/voting/test_core.py | 150 ++++++++++++++++----------------- 3 files changed, 131 insertions(+), 125 deletions(-) diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 2cc5e557..2d093c44 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -132,7 +132,7 @@ async def start_vote(self, ctx, *, title): Creates a new vote :param title: The title of the vote """ - await ctx.send(core.start_vote(self.bot, self.vote_manager, title, ctx.author, ctx.guild)) + await ctx.send(core.start_vote(self.bot, self.vote_manager, title, ctx.author.id, ctx.guild.id)) @currently_configuring() @@ -144,7 +144,7 @@ async def add_role(self, ctx, *, role: discord.Role): If no roles are added, the vote will go to all users in a guild (unless a target voice channel has been set) :param role: role id (e.g. 135496683009081345) or a role ping (e.g. @Student) """ - await ctx.send(core.set_roles(self.vote_manager, ctx.author, role, "add")) + await ctx.send(core.set_roles(self.bot, self.vote_manager, ctx.author.id, ctx.guild.id, role.id, "add")) @currently_configuring() @@ -155,7 +155,7 @@ async def remove_role(self, ctx, *, role: discord.Role): Removes a role to the list of roles the vote will be sent to :param role: role id (e.g. 135496683009081345) or a role ping (e.g. @Student) """ - await ctx.send(core.set_roles(self.vote_manager, ctx.author, role, "remove")) + await ctx.send(core.set_roles(self.bot, self.vote_manager, ctx.author.id, ctx.guild.id, role.id, "remove")) @currently_configuring() @@ -167,7 +167,7 @@ async def set_chair(self, ctx, *, chair: discord.Member = None): If no chair defaults to sending the message to the channel the vote is closed in :param chair: user id (e.g. 135496683009081345) or ping (e.g. @ito#8813) """ - await ctx.send(await core.set_chair(self.vote_manager, ctx.author, chair)) + await ctx.send(await core.set_chair(self.bot, self.vote_manager, ctx.author.id, getattr(chair, 'id', None))) @currently_configuring() @@ -179,7 +179,7 @@ async def set_channel(self, ctx, *, channel: discord.VoiceChannel = None): If there isn't one set votes will go to all users in a guild (unless target roles have been added) :param channel: channel id (e.g. 135496683009081345) or mention (e.g. #cool-channel) """ - await ctx.send(core.set_channel(self.vote_manager, ctx.author, channel)) + await ctx.send(core.set_channel(self.bot, self.vote_manager, ctx.author.id, channel.id)) @currently_configuring() @@ -191,7 +191,7 @@ async def add_option(self, ctx, *, option_string): separate the title and description with a "+" e.g. option title+option description :param option_string: a title and description for the option separated by a '+' """ - await ctx.send(core.add_option(self.vote_manager, ctx.author, option_string)) + await ctx.send(core.add_option(self.vote_manager, ctx.author.id, option_string)) @currently_configuring() @@ -202,7 +202,7 @@ async def remove_option(self, ctx, index: int): Removes an option from a vote based on it's index :param index: the number of the option """ - await ctx.send(core.remove_option(self.vote_manager, ctx.author, index)) + await ctx.send(core.remove_option(self.vote_manager, ctx.author.id, index)) @currently_configuring() @@ -215,7 +215,7 @@ async def set_end_time(self, ctx, *, time_string): :param time_string: string representing a time e.g. "2021-03-22 12:56" or "tomorrow at 10am" or "in 5 days and 15 minutes" :return: """ - await ctx.send(core.set_end_time(self.vote_manager, ctx.author, time_string)) + await ctx.send(core.set_end_time(self.vote_manager, ctx.author.id, time_string)) @currently_configuring() @@ -225,7 +225,7 @@ async def preview_vote(self, ctx): """ Generates a preview of what users will see with the current configuration of the vote """ - prev = core.preview(self.vote_manager, ctx.author) + prev = core.preview(self.vote_manager, ctx.author.id) msg = await ctx.send(embed=prev[0]) await add_reactions(prev[1], msg) @@ -238,7 +238,7 @@ async def cancel_vote(self, ctx, *, title): Cancels a vote you are setting up or have sent :param title: title of the vote to cancel """ - await ctx.send(core.cancel_vote(self.vote_manager, ctx.author, title)) + await ctx.send(core.cancel_vote(self.vote_manager, ctx.author.id, title)) @commands.check(vote_is_enabled) @@ -249,7 +249,7 @@ async def check_current_votes(self, ctx): Return a list of all votes you have in this guild. :return: """ - await ctx.send(embed=core.current_votes(ctx.author, ctx.guild)) + await ctx.send(embed=core.current_votes(ctx.author.id, ctx.guild.id)) @currently_configuring() @@ -258,7 +258,7 @@ async def send_vote(self, ctx): """ Sends a vote to all users within the restrictions set with the current options added """ - await ctx.send(await core.send_vote(self.vote_manager, ctx.author, ctx.guild)) + await ctx.send(await core.send_vote(self.bot, self.vote_manager, ctx.author.id, ctx.guild)) @commands.check(vote_is_enabled) @@ -268,7 +268,7 @@ async def close(self, ctx, *, title): """ Ends a vote, and collects the results """ - msg = await core.close(self.bot, self.vote_manager, ctx.author) + msg = await core.close(self.bot, self.vote_manager, ctx.author.id, title) if type(msg) is list: await ctx.send(msg[0], embed=msg[1]) elif type(msg) is discord.Embed: @@ -284,7 +284,7 @@ async def check_results(self, ctx, *, title): """ Checks the results of a vote without closing it """ - msg = await core.results(self.bot, self.vote_manager, ctx.author, title) + msg = await core.results(self.bot, self.vote_manager, ctx.author.id, title) if type(msg) is discord.Embed: await ctx.send(embed=msg) else: diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 29a05453..5eed7d41 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -82,39 +82,41 @@ async def vote_end_loop(bot: koalabot.KoalaBot, vm: VoteManager, session: Sessio @assign_session -def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author, guild, session: Session): - guild_name = author.guild.name +def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author_id, guild_id, session: Session): + guild_name = bot.get_guild(guild_id) - if vm.has_active_vote(author.id): + if vm.has_active_vote(author_id): return f"You already have an active vote in {guild_name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." - in_db = session.execute(select(Votes).filter_by(title=title, author_id=author.id)).all() + in_db = session.execute(select(Votes).filter_by(title=title, author_id=author_id)).all() if in_db: return f"You already have a vote with title {title} sent!" if len(title) > 200: return "Title too long" - vm.create_vote(author.id, guild.id, title) - return f"Vote titled `{title}` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." + vm.create_vote(author_id, guild_id, title) + return f"Vote titled `{title}` created for guild {guild_name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." -def set_roles(vm: VoteManager, author, role, action): - vote = vm.get_configuring_vote(author.id) +def set_roles(bot: koalabot.KoalaBot, vm: VoteManager, author_id, guild_id, role_id, action): + vote = vm.get_configuring_vote(author_id) + role = bot.get_guild(guild_id).get_role(role_id) if action == "add": - vote.add_role(role.id) + vote.add_role(role_id) return f"Vote will be sent to those with the {role.name} role" if action == "remove": - vote.remove_role(role.id) + vote.remove_role(role_id) return f"Vote will no longer be sent to those with the {role.name} role" -async def set_chair(vm: VoteManager, author, chair=None): - vote = vm.get_configuring_vote(author.id) +async def set_chair(bot: koalabot.KoalaBot, vm: VoteManager, author_id, chair_id=None): + vote = vm.get_configuring_vote(author_id) - if chair: + if chair_id: + chair = bot.get_user(chair_id) try: await chair.send(f"You have been selected as the chair for vote titled {vote.title}") vote.set_chair(chair.id) @@ -126,8 +128,9 @@ async def set_chair(vm: VoteManager, author, chair=None): return "Results will be sent to the channel vote is closed in" -def set_channel(vm: VoteManager, author, channel=None): - vote = vm.get_configuring_vote(author.id) +def set_channel(bot: koalabot.KoalaBot, vm: VoteManager, author_id, channel_id=None): + vote = vm.get_configuring_vote(author_id) + channel = bot.get_channel(channel_id) if channel: vote.set_vc(channel.id) @@ -137,8 +140,8 @@ def set_channel(vm: VoteManager, author, channel=None): return "Removed channel restriction on vote" -def add_option(vm: VoteManager, author, option_string): - vote = vm.get_configuring_vote(author.id) +def add_option(vm: VoteManager, author_id, option_string): + vote = vm.get_configuring_vote(author_id) if len(vote.options) > 9: return "Vote has maximum number of options already (10)" @@ -156,8 +159,8 @@ def add_option(vm: VoteManager, author, option_string): return f"Option {header} with description {body} added to vote" -def remove_option(vm: VoteManager, author, index): - vote = vm.get_configuring_vote(author.id) +def remove_option(vm: VoteManager, author_id, index): + vote = vm.get_configuring_vote(author_id) try: vote.remove_option(index) return f"Option number {index} removed" @@ -165,9 +168,9 @@ def remove_option(vm: VoteManager, author, index): return f"Option number {index} not found" -def set_end_time(vm: VoteManager, author, time_string): +def set_end_time(vm: VoteManager, author_id, time_string): now = time.time() - vote = vm.get_configuring_vote(author.id) + vote = vm.get_configuring_vote(author_id) cal = parsedatetime.Calendar() end_time_readable = cal.parse(time_string)[0] end_time = time.mktime(end_time_readable) @@ -179,24 +182,24 @@ def set_end_time(vm: VoteManager, author, time_string): return f"Vote set to end at {time.strftime('%Y-%m-%d %H:%M:%S', end_time_readable)} UTC" -def preview(vm: VoteManager, author): - vote = vm.get_configuring_vote(author.id) +def preview(vm: VoteManager, author_id): + vote = vm.get_configuring_vote(author_id) return [create_embed(vote), vote] -def cancel_vote(vm: VoteManager, author, title): - v_id = vm.vote_lookup[(author.id, title)] +def cancel_vote(vm: VoteManager, author_id, title): + v_id = vm.vote_lookup[(author_id, title)] if v_id in vm.sent_votes.keys(): vm.cancel_sent_vote(v_id) else: - vm.cancel_configuring_vote(author.id) + vm.cancel_configuring_vote(author_id) return f"Vote {title} has been cancelled." @assign_session -def current_votes(author, guild, session: Session): +def current_votes(author_id, guild_id, session: Session): embed = discord.Embed(title="Your current votes") - votes = session.execute(select(Votes.title).filter_by(author_id=author.id, guild_id=guild.id)).all() + votes = session.execute(select(Votes.title).filter_by(author_id=author_id, guild_id=guild_id)).all() body_string = "" for title in votes: body_string += f"{title[0]}\n" @@ -204,13 +207,14 @@ def current_votes(author, guild, session: Session): return embed -async def send_vote(vm: VoteManager, author, guild): - vote = vm.get_configuring_vote(author.id) +async def send_vote(bot: koalabot.KoalaBot, vm: VoteManager, author_id, guild_id): + vote = vm.get_configuring_vote(author_id) + guild = bot.get_guild(guild_id) if not vote.is_ready(): return "Please add more than 1 option to vote for" - vm.configuring_votes.pop(author.id) + vm.configuring_votes.pop(author_id) vm.sent_votes[vote.id] = vote users = [x for x in guild.members if not x.bot] @@ -234,10 +238,10 @@ async def send_vote(vm: VoteManager, author, guild): return f"Sent vote to {len(users)} users" -async def close(bot: koalabot.KoalaBot, vm: VoteManager, author, title): - vote_id = vm.vote_lookup[(author.id, title)] +async def close(bot: koalabot.KoalaBot, vm: VoteManager, author_id, title): + vote_id = vm.vote_lookup[(author_id, title)] if vote_id not in vm.sent_votes.keys(): - if author.id in vm.configuring_votes.keys(): + if author_id in vm.configuring_votes.keys(): return f"That vote has not been sent yet. Please send it to your audience with {koalabot.COMMAND_PREFIX}vote send {title}" else: return "You have no votes of that title to close" @@ -257,8 +261,10 @@ async def close(bot: koalabot.KoalaBot, vm: VoteManager, author, title): return embed -async def results(bot: koalabot.KoalaBot, vm: VoteManager, author, title): - vote_id = vm.vote_lookup.get((author.id, title)) +async def results(bot: koalabot.KoalaBot, vm: VoteManager, author_id, title): + vote_id = vm.vote_lookup.get((author_id, title)) + author = bot.get_user(author_id) + if vote_id is None: raise ValueError(f"{title} is not a valid vote title for user {author.name}") diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index 0156c54b..8922c4ac 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -35,7 +35,7 @@ def test_create_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - assert core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) == f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." + assert core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) == f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." @pytest.mark.asyncio @@ -45,7 +45,7 @@ async def test_vote_already_created(bot: commands.Bot, cog): await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") - assert core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) == f"You already have an active vote in {guild.name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." + assert core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) == f"You already have an active vote in {guild.name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." @assign_session @@ -57,7 +57,7 @@ async def test_vote_already_sent(bot: commands.Bot, cog, session): session.add(Votes(vote_id=111, author_id=author.id, guild_id=guild.id, title="Test Vote")) session.commit() - assert core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) == "You already have a vote with title Test Vote sent!" + assert core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) == "You already have a vote with title Test Vote sent!" @pytest.mark.asyncio @@ -67,7 +67,7 @@ async def test_add_role(bot: commands.Bot, cog, session): role: discord.Role = dpytest.back.make_role("testRole", guild, id_num=555) await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") - assert core.set_roles(cog.vote_manager, author, role, "add") == f"Vote will be sent to those with the {role.name} role" + assert core.set_roles(bot, cog.vote_manager, author.id, guild.id, role.id, "add") == f"Vote will be sent to those with the {role.name} role" @pytest.mark.asyncio @@ -77,9 +77,9 @@ async def test_remove_role(bot: commands.Bot, cog): role: discord.Role = dpytest.back.make_role("testRole", guild, id_num=555) await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") - core.set_roles(cog.vote_manager, author, role, "add") + core.set_roles(bot, cog.vote_manager, author.id, guild.id, role.id, "add") - assert core.set_roles(cog.vote_manager, author, role, "remove") == f"Vote will no longer be sent to those with the {role.name} role" + assert core.set_roles(bot, cog.vote_manager, author.id, guild.id, role.id, "remove") == f"Vote will no longer be sent to those with the {role.name} role" @pytest.mark.asyncio @@ -87,9 +87,9 @@ async def test_set_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] chair: discord.Member = guild.members[1] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert await core.set_chair(cog.vote_manager, author, chair) == f"Set chair to {chair.name}" + assert await core.set_chair(bot, cog.vote_manager, author.id, chair.id) == f"Set chair to {chair.name}" # failing because idk how to mock a blocked dm channel @@ -98,22 +98,22 @@ async def test_set_chair_no_dms(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] chair: discord.Member = guild.members[1] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) # dpytest.back.start_private_message? # pytest.raises is NOT the way to go here. the Forbidden is excepted, not thrown. with mock.patch('discord.channel.DMChannel', mock.Mock(side_effect=Exception('discord.Forbidden'))): with pytest.raises(discord.Forbidden, match="Chair not set as requested user is not accepting direct messages."): - await core.set_chair(cog.vote_manager, author, chair) + await core.set_chair(bot, cog.vote_manager, author.id, chair.id) @pytest.mark.asyncio async def test_set_no_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert await core.set_chair(cog.vote_manager, author) == "Results will be sent to the channel vote is closed in" + assert await core.set_chair(bot, cog.vote_manager, author.id) == "Results will be sent to the channel vote is closed in" # make_voice_channel doesn't exist even though it's in their documentation @@ -122,105 +122,105 @@ def test_set_channel(bot: commands.Bot, cog): author: discord.Member = guild.members[0] channel = dpytest.back.make_voice_channel("Voice Channel", guild) - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert core.set_channel(cog.vote_manager, author, channel) == f"Set target channel to {channel.name}" + assert core.set_channel(bot, cog.vote_manager, author.id, channel.id) == f"Set target channel to {channel.name}" def test_set_no_channel(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert core.set_channel(cog.vote_manager, author) == "Removed channel restriction on vote" + assert core.set_channel(bot, cog.vote_manager, author.id) == "Removed channel restriction on vote" def test_add_option(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert core.add_option(cog.vote_manager, author, "Option 1+Option description") == "Option Option 1 with description Option description added to vote" + assert core.add_option(cog.vote_manager, author.id, "Option 1+Option description") == "Option Option 1 with description Option description added to vote" def test_add_option_wrong_formatting(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert core.add_option(cog.vote_manager, author, "Option 1") == "Example usage: k!vote addOption option title+option description" + assert core.add_option(cog.vote_manager, author.id, "Option 1") == "Example usage: k!vote addOption option title+option description" def test_add_option_too_many(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) x = 0 while (x < 11): - core.add_option(cog.vote_manager, author, "more+options") + core.add_option(cog.vote_manager, author.id, "more+options") x += 1 - assert core.add_option(cog.vote_manager, author, "more options+please?") == "Vote has maximum number of options already (10)" + assert core.add_option(cog.vote_manager, author.id, "more options+please?") == "Vote has maximum number of options already (10)" def test_add_option_too_long(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) test_option = "i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup+i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably." x = 0 while (x < 5): - core.add_option(cog.vote_manager, author, test_option) + core.add_option(cog.vote_manager, author.id, test_option) x += 1 - assert core.add_option(cog.vote_manager, author, test_option) == "Option string is too long. The total length of all the vote options cannot be over 1500 characters." + assert core.add_option(cog.vote_manager, author.id, test_option) == "Option string is too long. The total length of all the vote options cannot be over 1500 characters." def test_remove_option(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - core.add_option(cog.vote_manager, author, "test+option") + core.add_option(cog.vote_manager, author.id, "test+option") - assert core.remove_option(cog.vote_manager, author, 0) == "Option number 0 removed" + assert core.remove_option(cog.vote_manager, author.id, 0) == "Option number 0 removed" def test_remove_option_bad(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert core.remove_option(cog.vote_manager, author, 0) == "Option number 0 not found" + assert core.remove_option(cog.vote_manager, author.id, 0) == "Option number 0 not found" def test_set_end_time(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert core.set_end_time(cog.vote_manager, author, "2222-12-30 13:30") == "Vote set to end at 2222-12-30 13:30:00 UTC" + assert core.set_end_time(cog.vote_manager, author.id, "2222-12-30 13:30") == "Vote set to end at 2222-12-30 13:30:00 UTC" def test_set_impossible_end_time(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert core.set_end_time(cog.vote_manager, author, "2020-01-15 12:50") == "You can't set a vote to end in the past" + assert core.set_end_time(cog.vote_manager, author.id, "2020-01-15 12:50") == "You can't set a vote to end in the past" def test_preview(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - prev = core.preview(cog.vote_manager, author) + prev = core.preview(cog.vote_manager, author.id) assert prev[0].title == "Test Vote" @@ -228,30 +228,30 @@ def test_preview(bot: commands.Bot, cog): async def test_cancel_sent_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - core.add_option(cog.vote_manager, author, "Option 1+Option description") - core.add_option(cog.vote_manager, author, "Option 2+Option description2") + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.add_option(cog.vote_manager, author.id, "Option 1+Option description") + core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") - await core.send_vote(cog.vote_manager, author, guild) + await core.send_vote(bot, cog.vote_manager, author.id, guild.id) - assert core.cancel_vote(cog.vote_manager, author, "Test Vote") == "Vote Test Vote has been cancelled." + assert core.cancel_vote(cog.vote_manager, author.id, "Test Vote") == "Vote Test Vote has been cancelled." def test_cancel_unsent_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - assert core.cancel_vote(cog.vote_manager, author, "Test Vote") == "Vote Test Vote has been cancelled." + assert core.cancel_vote(cog.vote_manager, author.id, "Test Vote") == "Vote Test Vote has been cancelled." @pytest.mark.asyncio async def test_current_votes(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - embed = core.current_votes(author, guild) + embed = core.current_votes(author.id, guild.id) assert embed.title == "Your current votes" @@ -259,13 +259,13 @@ async def test_current_votes(bot: commands.Bot, cog): async def test_close_no_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - core.add_option(cog.vote_manager, author, "Option 1+Option description") - core.add_option(cog.vote_manager, author, "Option 2+Option description2") + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.add_option(cog.vote_manager, author.id, "Option 1+Option description") + core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") - await core.send_vote(cog.vote_manager, author, guild) + await core.send_vote(bot, cog.vote_manager, author.id, guild.id) - embed = await core.close(bot, cog.vote_manager, author, "Test Vote") + embed = await core.close(bot, cog.vote_manager, author.id, "Test Vote") assert embed.title == "Test Vote Results:" assert embed.fields[0].name == "Option 1" assert embed.fields[1].name == "Option 2" @@ -276,54 +276,54 @@ async def test_close_with_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] chair: discord.Member = guild.members[1] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - core.add_option(cog.vote_manager, author, "Option 1+Option description") - core.add_option(cog.vote_manager, author, "Option 2+Option description2") - await core.set_chair(cog.vote_manager, author, chair) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.add_option(cog.vote_manager, author.id, "Option 1+Option description") + core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") + await core.set_chair(bot, cog.vote_manager, author.id, chair.id) - await core.send_vote(cog.vote_manager, author, guild) + await core.send_vote(bot, cog.vote_manager, author.id, guild.id) - assert await core.close(bot, cog.vote_manager, author, "Test Vote") == f"Sent results to {chair}" + assert await core.close(bot, cog.vote_manager, author.id, "Test Vote") == f"Sent results to {chair}" @pytest.mark.asyncio async def test_send_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - core.add_option(cog.vote_manager, author, "Option 1+Option description") - core.add_option(cog.vote_manager, author, "Option 2+Option description2") + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.add_option(cog.vote_manager, author.id, "Option 1+Option description") + core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") # not sure how to assert DM sent - assert await core.send_vote(cog.vote_manager, author, guild) == "Sent vote to 1 users" + assert await core.send_vote(bot, cog.vote_manager, author.id, guild.id) == "Sent vote to 1 users" @pytest.mark.asyncio async def test_send_vote_bad_options(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) # no options - assert await core.send_vote(cog.vote_manager, author, guild) == "Please add more than 1 option to vote for" + assert await core.send_vote(bot, cog.vote_manager, author.id, guild.id) == "Please add more than 1 option to vote for" # only 1 option - core.add_option(cog.vote_manager, author, "Option 1+Option description") - assert await core.send_vote(cog.vote_manager, author, guild) == "Please add more than 1 option to vote for" + core.add_option(cog.vote_manager, author.id, "Option 1+Option description") + assert await core.send_vote(bot, cog.vote_manager, author.id, guild.id) == "Please add more than 1 option to vote for" @pytest.mark.asyncio async def test_get_results(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - core.add_option(cog.vote_manager, author, "Option 1+Option description") - core.add_option(cog.vote_manager, author, "Option 2+Option description2") + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.add_option(cog.vote_manager, author.id, "Option 1+Option description") + core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") - await core.send_vote(cog.vote_manager, author, guild) + await core.send_vote(bot, cog.vote_manager, author.id, guild.id) - embed = await core.results(bot, cog.vote_manager, author, "Test Vote") + embed = await core.results(bot, cog.vote_manager, author.id, "Test Vote") assert embed.title == "Test Vote Results:" assert embed.fields[0].name == "Option 1" assert embed.fields[1].name == "Option 2" @@ -333,11 +333,11 @@ async def test_get_results(bot: commands.Bot, cog): async def test_results_vote_not_sent(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author, guild) - core.add_option(cog.vote_manager, author, "Option 1+Option description") - core.add_option(cog.vote_manager, author, "Option 2+Option description2") + core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.add_option(cog.vote_manager, author.id, "Option 1+Option description") + core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") - assert await core.results(bot, cog.vote_manager, author, "Test Vote") == "That vote has not been sent yet. Please send it to your audience with k!vote send Test Vote" + assert await core.results(bot, cog.vote_manager, author.id, "Test Vote") == "That vote has not been sent yet. Please send it to your audience with k!vote send Test Vote" @pytest.mark.asyncio @@ -346,4 +346,4 @@ async def test_results_invalid_vote(bot: commands.Bot, cog): author: discord.Member = guild.members[0] with pytest.raises(ValueError, match=f"invalid is not a valid vote title for user {author.name}"): - await core.results(bot, cog.vote_manager, author, "invalid") \ No newline at end of file + await core.results(bot, cog.vote_manager, author.id, "invalid") \ No newline at end of file From 9599849fe370922cd654b51c84c22efb7eb2ede0 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Mon, 22 May 2023 14:12:08 +0100 Subject: [PATCH 31/41] feat: set up vote api --- koala/cogs/voting/api.py | 82 ++++++++++++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 12 deletions(-) diff --git a/koala/cogs/voting/api.py b/koala/cogs/voting/api.py index e1d94cf3..aabcefe5 100644 --- a/koala/cogs/voting/api.py +++ b/koala/cogs/voting/api.py @@ -1,7 +1,8 @@ # Futures # Built-in/Generic Imports # Libs -from http.client import CREATED +from http.client import CREATED, OK +from typing import Optional import discord from aiohttp import web @@ -40,38 +41,95 @@ def register(self, app): return app +# how to do vote_manager @parse_request - async def post_new_vote(self): + async def post_new_vote(self, title, author_id, guild_id, options: list, + roles: Optional[list], chair_id: Optional[int], end_time: Optional[str]): """ Create a new vote. - :param : - :return: The list of ScheduledActivities + :param title: The name of the vote + :param author_id: The author id of the vote + :param guild_id: The guild id of the vote + :param options: The options for the votes + :param roles: The target roles for the votes + :param chair_id: The chair id of the vote + :param end_time: The end time of the vote + :return: """ - pass + try: + await core.start_vote(self, self.vote_manager, title, author_id, guild_id) + + for item in options: + core.add_option(self.vote_manager, author_id, item) + + if roles: + for item in roles: + core.set_roles(self, self.vote_manager, author_id, guild_id, item, "add") + + if chair_id: + core.set_chair(self, self.vote_manager, author_id, chair_id) + + if end_time: + core.set_end_time(self.vote_manager, author_id, end_time) + + await core.send_vote(self, self.vote_manager, author_id, guild_id) + + except Exception as e: + logger.error(e) + raise web.HTTPUnprocessableEntity() + + return build_response(CREATED, {'message': f'Vote {title} created'}) @parse_request - async def get_current_votes(self): + def get_current_votes(self, author_id, guild_id): """ Gets list of open votes. + :param author_id: The author id of the vote + :param guild: The guild id of the vote + :return: """ - pass + try: + embed = core.current_votes(author_id, guild_id) + except Exception as e: + logger.error(e) + raise web.HTTPUnprocessableEntity() + + return build_response(OK, embed) @parse_request - async def post_close_results(self): + async def post_close_results(self, author_id, title): """ Gets results and closes the vote. + :param author_id: The author id of the vote + :param title: The title of the vote + :return: """ - pass - + try: + embed = await core.close(self, self.vote_manager, author_id, title) + except Exception as e: + logger.error(e) + raise web.HTTPUnprocessableEntity() + + return build_response(OK, embed) + @parse_request - async def get_results(self): + async def get_results(self, author_id, title): """ Gets results, but does not close the vote. + :param author_id: The author id of the vote + :param title: The title of the vote + :return: """ - pass + try: + embed = core.results(self, self.vote_manager, author_id, title) + except Exception as e: + logger.error(e) + raise web.HTTPUnprocessableEntity() + + return build_response(OK, embed) def setup(bot: Bot): From 929f7ca36505647204fd4fd5536fe75f2da313ae Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Mon, 22 May 2023 14:57:38 +0100 Subject: [PATCH 32/41] test: add no vote test for current_votes --- tests/cogs/voting/test_core.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index 8922c4ac..1622f41b 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -245,8 +245,7 @@ def test_cancel_unsent_vote(bot: commands.Bot, cog): assert core.cancel_vote(cog.vote_manager, author.id, "Test Vote") == "Vote Test Vote has been cancelled." -@pytest.mark.asyncio -async def test_current_votes(bot: commands.Bot, cog): +def test_current_votes(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) @@ -255,6 +254,15 @@ async def test_current_votes(bot: commands.Bot, cog): assert embed.title == "Your current votes" +def test_current_votes_no_votes(bot: commands.Bot, cog): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + embed = core.current_votes(author.id, guild.id) + assert embed.title == "Your current votes" + assert embed.description == "No current votes" + + @pytest.mark.asyncio async def test_close_no_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] From 662d7e80c451b3019a8eb8fc1b134490e1dc199b Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Mon, 22 May 2023 21:52:25 +0100 Subject: [PATCH 33/41] feat: vote_manager (vm) all in core --- koala/cogs/voting/cog.py | 38 ++++----- koala/cogs/voting/core.py | 43 ++++++---- tests/cogs/voting/test_cog.py | 14 ++-- tests/cogs/voting/test_core.py | 148 ++++++++++++++++----------------- 4 files changed, 128 insertions(+), 115 deletions(-) diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 2d093c44..c2288087 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -29,20 +29,18 @@ def currently_configuring(): :return: True if the user has an active vote, false if not """ async def predicate(ctx): - cog = ctx.command.cog if koalabot.is_dm_channel(ctx): return False - return ctx.author.id in cog.vote_manager.configuring_votes.keys() and cog.vote_manager.configuring_votes[ctx.author.id].guild == ctx.guild.id + return ctx.author.id in core.vm.configuring_votes.keys() and core.vm.configuring_votes[ctx.author.id].guild == ctx.guild.id return commands.check(predicate) def has_current_votes(): async def predicate(ctx): - cog = ctx.command.cog if koalabot.is_dm_channel(ctx): return False - return ctx.author.id in map(lambda x: x[0], cog.vote_manager.vote_lookup.keys()) + return ctx.author.id in map(lambda x: x[0], core.vm.vote_lookup.keys()) return commands.check(predicate) @@ -71,8 +69,8 @@ def __init__(self, bot): """ self.bot = bot insert_extension("Vote", 0, True, True) - self.vote_manager = VoteManager() - self.vote_manager.load_from_db() + # self.vote_manager = VoteManager() + # self.vote_manager.load_from_db() self.running = False @commands.Cog.listener() @@ -132,7 +130,7 @@ async def start_vote(self, ctx, *, title): Creates a new vote :param title: The title of the vote """ - await ctx.send(core.start_vote(self.bot, self.vote_manager, title, ctx.author.id, ctx.guild.id)) + await ctx.send(core.start_vote(self.bot, title, ctx.author.id, ctx.guild.id)) @currently_configuring() @@ -144,7 +142,7 @@ async def add_role(self, ctx, *, role: discord.Role): If no roles are added, the vote will go to all users in a guild (unless a target voice channel has been set) :param role: role id (e.g. 135496683009081345) or a role ping (e.g. @Student) """ - await ctx.send(core.set_roles(self.bot, self.vote_manager, ctx.author.id, ctx.guild.id, role.id, "add")) + await ctx.send(core.set_roles(self.bot, ctx.author.id, ctx.guild.id, role.id, "add")) @currently_configuring() @@ -155,7 +153,7 @@ async def remove_role(self, ctx, *, role: discord.Role): Removes a role to the list of roles the vote will be sent to :param role: role id (e.g. 135496683009081345) or a role ping (e.g. @Student) """ - await ctx.send(core.set_roles(self.bot, self.vote_manager, ctx.author.id, ctx.guild.id, role.id, "remove")) + await ctx.send(core.set_roles(self.bot, ctx.author.id, ctx.guild.id, role.id, "remove")) @currently_configuring() @@ -167,7 +165,7 @@ async def set_chair(self, ctx, *, chair: discord.Member = None): If no chair defaults to sending the message to the channel the vote is closed in :param chair: user id (e.g. 135496683009081345) or ping (e.g. @ito#8813) """ - await ctx.send(await core.set_chair(self.bot, self.vote_manager, ctx.author.id, getattr(chair, 'id', None))) + await ctx.send(await core.set_chair(self.bot, ctx.author.id, getattr(chair, 'id', None))) @currently_configuring() @@ -179,9 +177,11 @@ async def set_channel(self, ctx, *, channel: discord.VoiceChannel = None): If there isn't one set votes will go to all users in a guild (unless target roles have been added) :param channel: channel id (e.g. 135496683009081345) or mention (e.g. #cool-channel) """ - await ctx.send(core.set_channel(self.bot, self.vote_manager, ctx.author.id, channel.id)) + await ctx.send(core.set_channel(self.bot, ctx.author.id, channel.id)) +# PROCESS THE + IN OPTIONS HERE +# NO + IN CORE!!! @currently_configuring() @commands.check(vote_is_enabled) @vote.command(name="addOption") @@ -191,7 +191,7 @@ async def add_option(self, ctx, *, option_string): separate the title and description with a "+" e.g. option title+option description :param option_string: a title and description for the option separated by a '+' """ - await ctx.send(core.add_option(self.vote_manager, ctx.author.id, option_string)) + await ctx.send(core.add_option(ctx.author.id, option_string)) @currently_configuring() @@ -202,7 +202,7 @@ async def remove_option(self, ctx, index: int): Removes an option from a vote based on it's index :param index: the number of the option """ - await ctx.send(core.remove_option(self.vote_manager, ctx.author.id, index)) + await ctx.send(core.remove_option(ctx.author.id, index)) @currently_configuring() @@ -215,7 +215,7 @@ async def set_end_time(self, ctx, *, time_string): :param time_string: string representing a time e.g. "2021-03-22 12:56" or "tomorrow at 10am" or "in 5 days and 15 minutes" :return: """ - await ctx.send(core.set_end_time(self.vote_manager, ctx.author.id, time_string)) + await ctx.send(core.set_end_time(ctx.author.id, time_string)) @currently_configuring() @@ -225,7 +225,7 @@ async def preview_vote(self, ctx): """ Generates a preview of what users will see with the current configuration of the vote """ - prev = core.preview(self.vote_manager, ctx.author.id) + prev = core.preview(ctx.author.id) msg = await ctx.send(embed=prev[0]) await add_reactions(prev[1], msg) @@ -238,7 +238,7 @@ async def cancel_vote(self, ctx, *, title): Cancels a vote you are setting up or have sent :param title: title of the vote to cancel """ - await ctx.send(core.cancel_vote(self.vote_manager, ctx.author.id, title)) + await ctx.send(core.cancel_vote(ctx.author.id, title)) @commands.check(vote_is_enabled) @@ -258,7 +258,7 @@ async def send_vote(self, ctx): """ Sends a vote to all users within the restrictions set with the current options added """ - await ctx.send(await core.send_vote(self.bot, self.vote_manager, ctx.author.id, ctx.guild)) + await ctx.send(await core.send_vote(self.bot, ctx.author.id, ctx.guild)) @commands.check(vote_is_enabled) @@ -268,7 +268,7 @@ async def close(self, ctx, *, title): """ Ends a vote, and collects the results """ - msg = await core.close(self.bot, self.vote_manager, ctx.author.id, title) + msg = await core.close(self.bot, ctx.author.id, title) if type(msg) is list: await ctx.send(msg[0], embed=msg[1]) elif type(msg) is discord.Embed: @@ -284,7 +284,7 @@ async def check_results(self, ctx, *, title): """ Checks the results of a vote without closing it """ - msg = await core.results(self.bot, self.vote_manager, ctx.author.id, title) + msg = await core.results(self.bot, ctx.author.id, title) if type(msg) is discord.Embed: await ctx.send(embed=msg) else: diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 5eed7d41..92ae1995 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -16,6 +16,11 @@ from .option import Option from .utils import make_result_embed + +vm = VoteManager() +vm.load_from_db() + + async def update_vote_message(bot: koalabot.KoalaBot, message_id, user_id): """ Updates the vote message with the currently selected option @@ -40,7 +45,7 @@ async def update_vote_message(bot: koalabot.KoalaBot, message_id, user_id): @assign_session -async def vote_end_loop(bot: koalabot.KoalaBot, vm: VoteManager, session: Session): +async def vote_end_loop(bot: koalabot.KoalaBot, session: Session): try: now = time.time() votes = session.execute(select(Votes.vote_id, Votes.author_id, Votes.guild_id, Votes.title, Votes.end_time) @@ -82,7 +87,7 @@ async def vote_end_loop(bot: koalabot.KoalaBot, vm: VoteManager, session: Sessio @assign_session -def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author_id, guild_id, session: Session): +def start_vote(bot: koalabot.KoalaBot, title, author_id, guild_id, session: Session): guild_name = bot.get_guild(guild_id) if vm.has_active_vote(author_id): @@ -99,7 +104,7 @@ def start_vote(bot: koalabot.KoalaBot, vm: VoteManager, title, author_id, guild_ return f"Vote titled `{title}` created for guild {guild_name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." -def set_roles(bot: koalabot.KoalaBot, vm: VoteManager, author_id, guild_id, role_id, action): +def set_roles(bot: koalabot.KoalaBot, author_id, guild_id, role_id, action): vote = vm.get_configuring_vote(author_id) role = bot.get_guild(guild_id).get_role(role_id) @@ -112,7 +117,7 @@ def set_roles(bot: koalabot.KoalaBot, vm: VoteManager, author_id, guild_id, role return f"Vote will no longer be sent to those with the {role.name} role" -async def set_chair(bot: koalabot.KoalaBot, vm: VoteManager, author_id, chair_id=None): +async def set_chair(bot: koalabot.KoalaBot, author_id, chair_id=None): vote = vm.get_configuring_vote(author_id) if chair_id: @@ -128,7 +133,7 @@ async def set_chair(bot: koalabot.KoalaBot, vm: VoteManager, author_id, chair_id return "Results will be sent to the channel vote is closed in" -def set_channel(bot: koalabot.KoalaBot, vm: VoteManager, author_id, channel_id=None): +def set_channel(bot: koalabot.KoalaBot, author_id, channel_id=None): vote = vm.get_configuring_vote(author_id) channel = bot.get_channel(channel_id) @@ -140,7 +145,10 @@ def set_channel(bot: koalabot.KoalaBot, vm: VoteManager, author_id, channel_id=N return "Removed channel restriction on vote" -def add_option(vm: VoteManager, author_id, option_string): +# OPTION ATTRIBUTES TITLE, DESCRPTION (OBJECT!) +# NO MORE + + +def add_option(author_id, option_string): vote = vm.get_configuring_vote(author_id) if len(vote.options) > 9: @@ -159,7 +167,7 @@ def add_option(vm: VoteManager, author_id, option_string): return f"Option {header} with description {body} added to vote" -def remove_option(vm: VoteManager, author_id, index): +def remove_option(author_id, index): vote = vm.get_configuring_vote(author_id) try: vote.remove_option(index) @@ -168,7 +176,7 @@ def remove_option(vm: VoteManager, author_id, index): return f"Option number {index} not found" -def set_end_time(vm: VoteManager, author_id, time_string): +def set_end_time(author_id, time_string): now = time.time() vote = vm.get_configuring_vote(author_id) cal = parsedatetime.Calendar() @@ -182,12 +190,12 @@ def set_end_time(vm: VoteManager, author_id, time_string): return f"Vote set to end at {time.strftime('%Y-%m-%d %H:%M:%S', end_time_readable)} UTC" -def preview(vm: VoteManager, author_id): +def preview(author_id): vote = vm.get_configuring_vote(author_id) return [create_embed(vote), vote] -def cancel_vote(vm: VoteManager, author_id, title): +def cancel_vote(author_id, title): v_id = vm.vote_lookup[(author_id, title)] if v_id in vm.sent_votes.keys(): vm.cancel_sent_vote(v_id) @@ -201,13 +209,16 @@ def current_votes(author_id, guild_id, session: Session): embed = discord.Embed(title="Your current votes") votes = session.execute(select(Votes.title).filter_by(author_id=author_id, guild_id=guild_id)).all() body_string = "" - for title in votes: - body_string += f"{title[0]}\n" - embed.add_field(name="Vote Title", value=body_string, inline=False) + if len(votes) > 0: + for title in votes: + body_string += f"{title[0]}\n" + embed.add_field(name="Vote Title", value=body_string, inline=False) + else: + embed.description = "No current votes" return embed -async def send_vote(bot: koalabot.KoalaBot, vm: VoteManager, author_id, guild_id): +async def send_vote(bot: koalabot.KoalaBot, author_id, guild_id): vote = vm.get_configuring_vote(author_id) guild = bot.get_guild(guild_id) @@ -238,7 +249,7 @@ async def send_vote(bot: koalabot.KoalaBot, vm: VoteManager, author_id, guild_id return f"Sent vote to {len(users)} users" -async def close(bot: koalabot.KoalaBot, vm: VoteManager, author_id, title): +async def close(bot: koalabot.KoalaBot, author_id, title): vote_id = vm.vote_lookup[(author_id, title)] if vote_id not in vm.sent_votes.keys(): if author_id in vm.configuring_votes.keys(): @@ -261,7 +272,7 @@ async def close(bot: koalabot.KoalaBot, vm: VoteManager, author_id, title): return embed -async def results(bot: koalabot.KoalaBot, vm: VoteManager, author_id, title): +async def results(bot: koalabot.KoalaBot, author_id, title): vote_id = vm.vote_lookup.get((author_id, title)) author = bot.get_user(author_id) diff --git a/tests/cogs/voting/test_cog.py b/tests/cogs/voting/test_cog.py index ca906955..6870f6ad 100644 --- a/tests/cogs/voting/test_cog.py +++ b/tests/cogs/voting/test_cog.py @@ -80,14 +80,16 @@ async def test_discord_vote_add_and_remove_role(cog): assert dpytest.verify().message().content( f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how " f"to configure it.") + await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote addRole {guild.roles[0].id}") assert dpytest.verify().message().content(f"Vote will be sent to those with the {guild.roles[0].name} role") - vote = cog.vote_manager.get_configuring_vote(guild.members[0].id) - assert guild.roles[0].id in vote.target_roles - await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote removeRole {guild.roles[0].id}") - assert dpytest.verify().message().content( - f"Vote will no longer be sent to those with the {guild.roles[0].name} role") - assert guild.roles[0].id not in vote.target_roles + # vote = cog.vote_manager.get_configuring_vote(guild.members[0].id) + # assert guild.roles[0].id in vote.target_roles + + # await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote removeRole {guild.roles[0].id}") + # assert dpytest.verify().message().content( + # f"Vote will no longer be sent to those with the {guild.roles[0].name} role") + # assert guild.roles[0].id not in vote.target_roles @pytest.mark.asyncio diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index 1622f41b..a79f6338 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -35,7 +35,7 @@ def test_create_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - assert core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) == f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." + assert core.start_vote(bot, "Test Vote", author.id, guild.id) == f"Vote titled `Test Vote` created for guild {guild.name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." @pytest.mark.asyncio @@ -45,7 +45,7 @@ async def test_vote_already_created(bot: commands.Bot, cog): await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") - assert core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) == f"You already have an active vote in {guild.name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." + assert core.start_vote(bot, "Test Vote", author.id, guild.id) == f"You already have an active vote in {guild.name}. Please send that with `{koalabot.COMMAND_PREFIX}vote send` before creating a new one." @assign_session @@ -57,7 +57,7 @@ async def test_vote_already_sent(bot: commands.Bot, cog, session): session.add(Votes(vote_id=111, author_id=author.id, guild_id=guild.id, title="Test Vote")) session.commit() - assert core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) == "You already have a vote with title Test Vote sent!" + assert core.start_vote(bot, "Test Vote", author.id, guild.id) == "You already have a vote with title Test Vote sent!" @pytest.mark.asyncio @@ -67,7 +67,7 @@ async def test_add_role(bot: commands.Bot, cog, session): role: discord.Role = dpytest.back.make_role("testRole", guild, id_num=555) await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") - assert core.set_roles(bot, cog.vote_manager, author.id, guild.id, role.id, "add") == f"Vote will be sent to those with the {role.name} role" + assert core.set_roles(bot, author.id, guild.id, role.id, "add") == f"Vote will be sent to those with the {role.name} role" @pytest.mark.asyncio @@ -77,9 +77,9 @@ async def test_remove_role(bot: commands.Bot, cog): role: discord.Role = dpytest.back.make_role("testRole", guild, id_num=555) await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote create Test Vote") - core.set_roles(bot, cog.vote_manager, author.id, guild.id, role.id, "add") + core.set_roles(bot, author.id, guild.id, role.id, "add") - assert core.set_roles(bot, cog.vote_manager, author.id, guild.id, role.id, "remove") == f"Vote will no longer be sent to those with the {role.name} role" + assert core.set_roles(bot, author.id, guild.id, role.id, "remove") == f"Vote will no longer be sent to those with the {role.name} role" @pytest.mark.asyncio @@ -87,9 +87,9 @@ async def test_set_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] chair: discord.Member = guild.members[1] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert await core.set_chair(bot, cog.vote_manager, author.id, chair.id) == f"Set chair to {chair.name}" + assert await core.set_chair(bot, author.id, chair.id) == f"Set chair to {chair.name}" # failing because idk how to mock a blocked dm channel @@ -98,22 +98,22 @@ async def test_set_chair_no_dms(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] chair: discord.Member = guild.members[1] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) # dpytest.back.start_private_message? # pytest.raises is NOT the way to go here. the Forbidden is excepted, not thrown. with mock.patch('discord.channel.DMChannel', mock.Mock(side_effect=Exception('discord.Forbidden'))): with pytest.raises(discord.Forbidden, match="Chair not set as requested user is not accepting direct messages."): - await core.set_chair(bot, cog.vote_manager, author.id, chair.id) + await core.set_chair(bot, author.id, chair.id) @pytest.mark.asyncio async def test_set_no_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert await core.set_chair(bot, cog.vote_manager, author.id) == "Results will be sent to the channel vote is closed in" + assert await core.set_chair(bot, author.id) == "Results will be sent to the channel vote is closed in" # make_voice_channel doesn't exist even though it's in their documentation @@ -122,105 +122,105 @@ def test_set_channel(bot: commands.Bot, cog): author: discord.Member = guild.members[0] channel = dpytest.back.make_voice_channel("Voice Channel", guild) - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.set_channel(bot, cog.vote_manager, author.id, channel.id) == f"Set target channel to {channel.name}" + assert core.set_channel(bot, author.id, channel.id) == f"Set target channel to {channel.name}" def test_set_no_channel(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.set_channel(bot, cog.vote_manager, author.id) == "Removed channel restriction on vote" + assert core.set_channel(bot, author.id) == "Removed channel restriction on vote" def test_add_option(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.add_option(cog.vote_manager, author.id, "Option 1+Option description") == "Option Option 1 with description Option description added to vote" + assert core.add_option(author.id, "Option 1+Option description") == "Option Option 1 with description Option description added to vote" def test_add_option_wrong_formatting(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.add_option(cog.vote_manager, author.id, "Option 1") == "Example usage: k!vote addOption option title+option description" + assert core.add_option(author.id, "Option 1") == "Example usage: k!vote addOption option title+option description" def test_add_option_too_many(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) x = 0 while (x < 11): - core.add_option(cog.vote_manager, author.id, "more+options") + core.add_option(author.id, "more+options") x += 1 - assert core.add_option(cog.vote_manager, author.id, "more options+please?") == "Vote has maximum number of options already (10)" + assert core.add_option(author.id, "more options+please?") == "Vote has maximum number of options already (10)" def test_add_option_too_long(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) test_option = "i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup+i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably." x = 0 while (x < 5): - core.add_option(cog.vote_manager, author.id, test_option) + core.add_option(author.id, test_option) x += 1 - assert core.add_option(cog.vote_manager, author.id, test_option) == "Option string is too long. The total length of all the vote options cannot be over 1500 characters." + assert core.add_option(author.id, test_option) == "Option string is too long. The total length of all the vote options cannot be over 1500 characters." def test_remove_option(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(cog.vote_manager, author.id, "test+option") + core.add_option(author.id, "test+option") - assert core.remove_option(cog.vote_manager, author.id, 0) == "Option number 0 removed" + assert core.remove_option(author.id, 0) == "Option number 0 removed" def test_remove_option_bad(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.remove_option(cog.vote_manager, author.id, 0) == "Option number 0 not found" + assert core.remove_option(author.id, 0) == "Option number 0 not found" def test_set_end_time(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.set_end_time(cog.vote_manager, author.id, "2222-12-30 13:30") == "Vote set to end at 2222-12-30 13:30:00 UTC" + assert core.set_end_time(author.id, "2222-12-30 13:30") == "Vote set to end at 2222-12-30 13:30:00 UTC" def test_set_impossible_end_time(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.set_end_time(cog.vote_manager, author.id, "2020-01-15 12:50") == "You can't set a vote to end in the past" + assert core.set_end_time(author.id, "2020-01-15 12:50") == "You can't set a vote to end in the past" def test_preview(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - prev = core.preview(cog.vote_manager, author.id) + prev = core.preview(author.id) assert prev[0].title == "Test Vote" @@ -228,27 +228,27 @@ def test_preview(bot: commands.Bot, cog): async def test_cancel_sent_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - core.add_option(cog.vote_manager, author.id, "Option 1+Option description") - core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") + core.start_vote(bot, "Test Vote", author.id, guild.id) + core.add_option(author.id, "Option 1+Option description") + core.add_option(author.id, "Option 2+Option description2") - await core.send_vote(bot, cog.vote_manager, author.id, guild.id) + await core.send_vote(bot, author.id, guild.id) - assert core.cancel_vote(cog.vote_manager, author.id, "Test Vote") == "Vote Test Vote has been cancelled." + assert core.cancel_vote(author.id, "Test Vote") == "Vote Test Vote has been cancelled." def test_cancel_unsent_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.cancel_vote(cog.vote_manager, author.id, "Test Vote") == "Vote Test Vote has been cancelled." + assert core.cancel_vote(author.id, "Test Vote") == "Vote Test Vote has been cancelled." def test_current_votes(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) embed = core.current_votes(author.id, guild.id) assert embed.title == "Your current votes" @@ -267,13 +267,13 @@ def test_current_votes_no_votes(bot: commands.Bot, cog): async def test_close_no_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - core.add_option(cog.vote_manager, author.id, "Option 1+Option description") - core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") + core.start_vote(bot, "Test Vote", author.id, guild.id) + core.add_option(author.id, "Option 1+Option description") + core.add_option(author.id, "Option 2+Option description2") - await core.send_vote(bot, cog.vote_manager, author.id, guild.id) + await core.send_vote(bot, author.id, guild.id) - embed = await core.close(bot, cog.vote_manager, author.id, "Test Vote") + embed = await core.close(bot, author.id, "Test Vote") assert embed.title == "Test Vote Results:" assert embed.fields[0].name == "Option 1" assert embed.fields[1].name == "Option 2" @@ -284,54 +284,54 @@ async def test_close_with_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] chair: discord.Member = guild.members[1] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - core.add_option(cog.vote_manager, author.id, "Option 1+Option description") - core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") - await core.set_chair(bot, cog.vote_manager, author.id, chair.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) + core.add_option(author.id, "Option 1+Option description") + core.add_option(author.id, "Option 2+Option description2") + await core.set_chair(bot, author.id, chair.id) - await core.send_vote(bot, cog.vote_manager, author.id, guild.id) + await core.send_vote(bot, author.id, guild.id) - assert await core.close(bot, cog.vote_manager, author.id, "Test Vote") == f"Sent results to {chair}" + assert await core.close(bot, author.id, "Test Vote") == f"Sent results to {chair}" @pytest.mark.asyncio async def test_send_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - core.add_option(cog.vote_manager, author.id, "Option 1+Option description") - core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") + core.start_vote(bot, "Test Vote", author.id, guild.id) + core.add_option(author.id, "Option 1+Option description") + core.add_option(author.id, "Option 2+Option description2") # not sure how to assert DM sent - assert await core.send_vote(bot, cog.vote_manager, author.id, guild.id) == "Sent vote to 1 users" + assert await core.send_vote(bot, author.id, guild.id) == "Sent vote to 1 users" @pytest.mark.asyncio async def test_send_vote_bad_options(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) + core.start_vote(bot, "Test Vote", author.id, guild.id) # no options - assert await core.send_vote(bot, cog.vote_manager, author.id, guild.id) == "Please add more than 1 option to vote for" + assert await core.send_vote(bot, author.id, guild.id) == "Please add more than 1 option to vote for" # only 1 option - core.add_option(cog.vote_manager, author.id, "Option 1+Option description") - assert await core.send_vote(bot, cog.vote_manager, author.id, guild.id) == "Please add more than 1 option to vote for" + core.add_option(author.id, "Option 1+Option description") + assert await core.send_vote(bot, author.id, guild.id) == "Please add more than 1 option to vote for" @pytest.mark.asyncio async def test_get_results(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - core.add_option(cog.vote_manager, author.id, "Option 1+Option description") - core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") + core.start_vote(bot, "Test Vote", author.id, guild.id) + core.add_option(author.id, "Option 1+Option description") + core.add_option(author.id, "Option 2+Option description2") - await core.send_vote(bot, cog.vote_manager, author.id, guild.id) + await core.send_vote(bot, author.id, guild.id) - embed = await core.results(bot, cog.vote_manager, author.id, "Test Vote") + embed = await core.results(bot, author.id, "Test Vote") assert embed.title == "Test Vote Results:" assert embed.fields[0].name == "Option 1" assert embed.fields[1].name == "Option 2" @@ -341,11 +341,11 @@ async def test_get_results(bot: commands.Bot, cog): async def test_results_vote_not_sent(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - core.start_vote(bot, cog.vote_manager, "Test Vote", author.id, guild.id) - core.add_option(cog.vote_manager, author.id, "Option 1+Option description") - core.add_option(cog.vote_manager, author.id, "Option 2+Option description2") + core.start_vote(bot, "Test Vote", author.id, guild.id) + core.add_option(author.id, "Option 1+Option description") + core.add_option(author.id, "Option 2+Option description2") - assert await core.results(bot, cog.vote_manager, author.id, "Test Vote") == "That vote has not been sent yet. Please send it to your audience with k!vote send Test Vote" + assert await core.results(bot, author.id, "Test Vote") == "That vote has not been sent yet. Please send it to your audience with k!vote send Test Vote" @pytest.mark.asyncio @@ -354,4 +354,4 @@ async def test_results_invalid_vote(bot: commands.Bot, cog): author: discord.Member = guild.members[0] with pytest.raises(ValueError, match=f"invalid is not a valid vote title for user {author.name}"): - await core.results(bot, cog.vote_manager, author.id, "invalid") \ No newline at end of file + await core.results(bot, author.id, "invalid") \ No newline at end of file From fac5d8c675833969eb13626e2385bff9402186ce Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 23 May 2023 01:33:07 +0100 Subject: [PATCH 34/41] refactor: vote options as dict inst of str --- koala/cogs/voting/cog.py | 7 ++++- koala/cogs/voting/core.py | 15 +++++----- tests/cogs/voting/test_cog.py | 13 +++++---- tests/cogs/voting/test_core.py | 52 +++++++++++++++++++--------------- 4 files changed, 50 insertions(+), 37 deletions(-) diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index c2288087..15053b6c 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -191,7 +191,12 @@ async def add_option(self, ctx, *, option_string): separate the title and description with a "+" e.g. option title+option description :param option_string: a title and description for the option separated by a '+' """ - await ctx.send(core.add_option(ctx.author.id, option_string)) + if "+" not in option_string: + await ctx.send("Example usage: k!vote addOption option title+option description") + else: + header, body = option_string.split("+") + option = {'header': header, 'body': body} + await ctx.send(core.add_option(ctx.author.id, option)) @currently_configuring() diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 92ae1995..3db8bd9f 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -148,23 +148,24 @@ def set_channel(bot: koalabot.KoalaBot, author_id, channel_id=None): # OPTION ATTRIBUTES TITLE, DESCRPTION (OBJECT!) # NO MORE + -def add_option(author_id, option_string): +def add_option(author_id, option): vote = vm.get_configuring_vote(author_id) if len(vote.options) > 9: return "Vote has maximum number of options already (10)" + if 'header' not in option or 'body' not in option: + return "Option should have both header and body." + current_option_length = sum([len(x.head) + len(x.body) for x in vote.options]) - if current_option_length + len(option_string) > 1500: + if current_option_length + len(option['header']) + len(option['body']) > 1500: return "Option string is too long. The total length of all the vote options cannot be over 1500 characters." - if "+" not in option_string: - return "Example usage: k!vote addOption option title+option description" + # moved check for '+' in string to cog - header, body = option_string.split("+") - vote.add_option(Option(header, body, vm.generate_unique_opt_id())) - return f"Option {header} with description {body} added to vote" + vote.add_option(Option(option['header'], option['body'], vm.generate_unique_opt_id())) + return f"Option {option['header']} with description {option['body']} added to vote" def remove_option(author_id, index): diff --git a/tests/cogs/voting/test_cog.py b/tests/cogs/voting/test_cog.py index 6870f6ad..b52d0fa2 100644 --- a/tests/cogs/voting/test_cog.py +++ b/tests/cogs/voting/test_cog.py @@ -19,6 +19,7 @@ # Own modules import koalabot from koala.cogs import Voting +from koala.cogs.voting import core from koala.cogs.voting.models import Votes from koala.db import session_manager, insert_extension from tests.log import logger @@ -83,13 +84,13 @@ async def test_discord_vote_add_and_remove_role(cog): await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote addRole {guild.roles[0].id}") assert dpytest.verify().message().content(f"Vote will be sent to those with the {guild.roles[0].name} role") - # vote = cog.vote_manager.get_configuring_vote(guild.members[0].id) - # assert guild.roles[0].id in vote.target_roles + vote = core.vm.get_configuring_vote(guild.members[0].id) + assert guild.roles[0].id in vote.target_roles - # await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote removeRole {guild.roles[0].id}") - # assert dpytest.verify().message().content( - # f"Vote will no longer be sent to those with the {guild.roles[0].name} role") - # assert guild.roles[0].id not in vote.target_roles + await dpytest.message(f"{koalabot.COMMAND_PREFIX}vote removeRole {guild.roles[0].id}") + assert dpytest.verify().message().content( + f"Vote will no longer be sent to those with the {guild.roles[0].name} role") + assert guild.roles[0].id not in vote.target_roles @pytest.mark.asyncio diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index a79f6338..7875e493 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -15,6 +15,10 @@ from tests.log import logger from koala.cogs.voting import core +# Variables +option1 = {'header': 'option1', 'body': 'desc1'} +option2 = {'header': 'option2', 'body': 'desc2'} + @pytest_asyncio.fixture(scope="function", autouse=True) async def cog(bot: commands.Bot): @@ -142,7 +146,7 @@ def test_add_option(bot: commands.Bot, cog): core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.add_option(author.id, "Option 1+Option description") == "Option Option 1 with description Option description added to vote" + assert core.add_option(author.id, option1) == "Option option1 with description desc1 added to vote" def test_add_option_wrong_formatting(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] @@ -150,7 +154,9 @@ def test_add_option_wrong_formatting(bot: commands.Bot, cog): core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.add_option(author.id, "Option 1") == "Example usage: k!vote addOption option title+option description" + option = {'header': 'Option 1'} + + assert core.add_option(author.id, option) == "Option should have both header and body." def test_add_option_too_many(bot: commands.Bot, cog): @@ -161,10 +167,10 @@ def test_add_option_too_many(bot: commands.Bot, cog): x = 0 while (x < 11): - core.add_option(author.id, "more+options") + core.add_option(author.id, option1) x += 1 - assert core.add_option(author.id, "more options+please?") == "Vote has maximum number of options already (10)" + assert core.add_option(author.id, option1) == "Vote has maximum number of options already (10)" def test_add_option_too_long(bot: commands.Bot, cog): @@ -172,7 +178,7 @@ def test_add_option_too_long(bot: commands.Bot, cog): author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - test_option = "i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup+i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably." + test_option = {'header': "i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup", 'body': 'i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably.'} x = 0 while (x < 5): core.add_option(author.id, test_option) @@ -186,7 +192,7 @@ def test_remove_option(bot: commands.Bot, cog): author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, "test+option") + core.add_option(author.id, option1) assert core.remove_option(author.id, 0) == "Option number 0 removed" @@ -229,8 +235,8 @@ async def test_cancel_sent_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, "Option 1+Option description") - core.add_option(author.id, "Option 2+Option description2") + core.add_option(author.id, option1) + core.add_option(author.id, option2) await core.send_vote(bot, author.id, guild.id) @@ -268,15 +274,15 @@ async def test_close_no_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, "Option 1+Option description") - core.add_option(author.id, "Option 2+Option description2") + core.add_option(author.id, option1) + core.add_option(author.id, option2) await core.send_vote(bot, author.id, guild.id) embed = await core.close(bot, author.id, "Test Vote") assert embed.title == "Test Vote Results:" - assert embed.fields[0].name == "Option 1" - assert embed.fields[1].name == "Option 2" + assert embed.fields[0].name == "option1" + assert embed.fields[1].name == "option2" @pytest.mark.asyncio @@ -285,8 +291,8 @@ async def test_close_with_chair(bot: commands.Bot, cog): author: discord.Member = guild.members[0] chair: discord.Member = guild.members[1] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, "Option 1+Option description") - core.add_option(author.id, "Option 2+Option description2") + core.add_option(author.id, option1) + core.add_option(author.id, option2) await core.set_chair(bot, author.id, chair.id) await core.send_vote(bot, author.id, guild.id) @@ -299,8 +305,8 @@ async def test_send_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, "Option 1+Option description") - core.add_option(author.id, "Option 2+Option description2") + core.add_option(author.id, option1) + core.add_option(author.id, option2) # not sure how to assert DM sent @@ -317,7 +323,7 @@ async def test_send_vote_bad_options(bot: commands.Bot, cog): assert await core.send_vote(bot, author.id, guild.id) == "Please add more than 1 option to vote for" # only 1 option - core.add_option(author.id, "Option 1+Option description") + core.add_option(author.id, option1) assert await core.send_vote(bot, author.id, guild.id) == "Please add more than 1 option to vote for" @@ -326,15 +332,15 @@ async def test_get_results(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, "Option 1+Option description") - core.add_option(author.id, "Option 2+Option description2") + core.add_option(author.id, option1) + core.add_option(author.id, option2) await core.send_vote(bot, author.id, guild.id) embed = await core.results(bot, author.id, "Test Vote") assert embed.title == "Test Vote Results:" - assert embed.fields[0].name == "Option 1" - assert embed.fields[1].name == "Option 2" + assert embed.fields[0].name == "option1" + assert embed.fields[1].name == "option2" @pytest.mark.asyncio @@ -342,8 +348,8 @@ async def test_results_vote_not_sent(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, "Option 1+Option description") - core.add_option(author.id, "Option 2+Option description2") + core.add_option(author.id, option1) + core.add_option(author.id, option2) assert await core.results(bot, author.id, "Test Vote") == "That vote has not been sent yet. Please send it to your audience with k!vote send Test Vote" From 457775958984d792a5bf097ecbb6c0e4d751de83 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 23 May 2023 22:22:17 +0100 Subject: [PATCH 35/41] test: api tests FAILING --- koala/cogs/voting/api.py | 77 ++++++++++------ koala/cogs/voting/core.py | 8 +- tests/cogs/voting/test_api.py | 158 +++++++++++++++++++++++++++++++++ tests/cogs/voting/test_core.py | 2 +- 4 files changed, 214 insertions(+), 31 deletions(-) create mode 100644 tests/cogs/voting/test_api.py diff --git a/koala/cogs/voting/api.py b/koala/cogs/voting/api.py index aabcefe5..521e89db 100644 --- a/koala/cogs/voting/api.py +++ b/koala/cogs/voting/api.py @@ -1,12 +1,13 @@ # Futures # Built-in/Generic Imports # Libs -from http.client import CREATED, OK +from http.client import BAD_REQUEST, CREATED, OK from typing import Optional import discord from aiohttp import web from discord.ext.commands import Bot +from koala.cogs.voting.db import VoteManager from koala.rest.api import parse_request, build_response # Own modules @@ -14,7 +15,6 @@ from .log import logger # Constants -VOTING_ENDPOINT = 'voting' CONFIG_ENDPOINT = 'config' RESULTS_ENDOPINT = 'results' @@ -41,10 +41,9 @@ def register(self, app): return app -# how to do vote_manager - @parse_request + @parse_request(raw_response=True) async def post_new_vote(self, title, author_id, guild_id, options: list, - roles: Optional[list], chair_id: Optional[int], end_time: Optional[str]): + roles=None, chair_id=None, end_time=None): """ Create a new vote. :param title: The name of the vote @@ -57,22 +56,22 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, :return: """ try: - await core.start_vote(self, self.vote_manager, title, author_id, guild_id) + core.start_vote(self._bot, title, author_id, guild_id) for item in options: - core.add_option(self.vote_manager, author_id, item) + core.add_option(author_id, item) - if roles: + if roles is not None: for item in roles: - core.set_roles(self, self.vote_manager, author_id, guild_id, item, "add") + core.set_roles(self._bot, author_id, guild_id, item, "add") - if chair_id: - core.set_chair(self, self.vote_manager, author_id, chair_id) + if chair_id is not None: + await core.set_chair(self._bot, author_id, chair_id) - if end_time: - core.set_end_time(self.vote_manager, author_id, end_time) + if end_time is not None: + core.set_end_time(author_id, end_time) - await core.send_vote(self, self.vote_manager, author_id, guild_id) + await core.send_vote(self._bot, author_id, guild_id) except Exception as e: logger.error(e) @@ -81,8 +80,8 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, return build_response(CREATED, {'message': f'Vote {title} created'}) - @parse_request - def get_current_votes(self, author_id, guild_id): + @parse_request(raw_response=True) + async def get_current_votes(self, author_id, guild_id): """ Gets list of open votes. :param author_id: The author id of the vote @@ -91,14 +90,20 @@ def get_current_votes(self, author_id, guild_id): """ try: embed = core.current_votes(author_id, guild_id) + + if embed.description: + body = embed.description + else: + body = embed.fields[0].value + + return build_response(OK, {'embed_title': f'{embed.title}', 'embed_body': f'{body}'}) + except Exception as e: logger.error(e) raise web.HTTPUnprocessableEntity() - - return build_response(OK, embed) - @parse_request + @parse_request(raw_response=True) async def post_close_results(self, author_id, title): """ Gets results and closes the vote. @@ -107,15 +112,24 @@ async def post_close_results(self, author_id, title): :return: """ try: - embed = await core.close(self, self.vote_manager, author_id, title) + embed = await core.close(self._bot, author_id, title) + if embed.fields[0].name == "No votes yet!": + body = embed.fields[0].value + else: + body = "" + for item in embed.fields: + body += item.name + ", " + item.value + "\n" + + return build_response(OK, {'embed_title': f'{embed.title}', + 'embed_body': f'{body}'}) + except Exception as e: logger.error(e) raise web.HTTPUnprocessableEntity() - - return build_response(OK, embed) + - @parse_request + @parse_request(raw_response=True) async def get_results(self, author_id, title): """ Gets results, but does not close the vote. @@ -124,12 +138,23 @@ async def get_results(self, author_id, title): :return: """ try: - embed = core.results(self, self.vote_manager, author_id, title) + message = await core.results(self._bot, author_id, title) + if type(message) is discord.Embed: + if message.fields[0].name == "No votes yet!": + body = message.fields[0].value + else: + body = "" + for item in message.fields: + body += item.name + ", " + item.value + "\n" + return build_response(OK, {'embed_title': f'{message.title}', + 'embed_body': f'{body}'}) + + else: + return build_response(BAD_REQUEST, {'message': message}) + except Exception as e: logger.error(e) raise web.HTTPUnprocessableEntity() - - return build_response(OK, embed) def setup(bot: Bot): diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 3db8bd9f..7dad40df 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -275,13 +275,13 @@ async def close(bot: koalabot.KoalaBot, author_id, title): async def results(bot: koalabot.KoalaBot, author_id, title): vote_id = vm.vote_lookup.get((author_id, title)) - author = bot.get_user(author_id) - + # author = bot.get_user(author_id) + if vote_id is None: - raise ValueError(f"{title} is not a valid vote title for user {author.name}") + raise ValueError(f"{title} is not a valid vote title for user with id {author_id}") if vote_id not in vm.sent_votes.keys(): - if author.id in vm.configuring_votes.keys(): + if author_id in vm.configuring_votes.keys(): return f"That vote has not been sent yet. Please send it to your audience with {koalabot.COMMAND_PREFIX}vote send {title}" else: return "You have no votes of that title to check" diff --git a/tests/cogs/voting/test_api.py b/tests/cogs/voting/test_api.py new file mode 100644 index 00000000..347b64a5 --- /dev/null +++ b/tests/cogs/voting/test_api.py @@ -0,0 +1,158 @@ +from http.client import BAD_REQUEST, CREATED, OK, UNPROCESSABLE_ENTITY, NOT_FOUND + +# Libs +import discord +import discord.ext.test as dpytest +import pytest +from aiohttp import web +from mock import mock +import json + +import koalabot +from koala.cogs.voting.api import VotingEndpoint + + +@pytest.fixture +def api_client(bot: discord.ext.commands.Bot, aiohttp_client, loop): + app = web.Application() + endpoint = VotingEndpoint(bot) + app = endpoint.register(app) + return loop.run_until_complete(aiohttp_client(app)) + + +# POST /config +# not sure how to test for any error handling + +async def test_post_new_vote_no_optionals(api_client): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + resp = await api_client.post('/config', json= + { + 'title': 'Test', + 'author_id': author.id, + 'guild_id': guild.id, + 'options': [{'header': 'option1', 'body': 'desc1'}, + {'header': 'option2', 'body': 'desc2'}] + }) + + assert resp.status == CREATED + assert (await resp.json())['message'] == "Vote Test created" + + +async def test_post_new_vote_with_optionals(api_client): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + resp = await api_client.post('/config', json= + { + 'title': 'Test2', + 'author_id': author.id, + 'guild_id': guild.id, + 'options': [{'header': 'option1', 'body': 'desc1'}, + {'header': 'option2', 'body': 'desc2'}], + 'roles': [guild.roles[0].id], + 'chair_id': guild.members[1].id, + 'end_time': '2025-01-01 00:00:00' + }) + + assert resp.status == CREATED + assert (await resp.json())['message'] == "Vote Test2 created" + + +# GET /config + +async def test_get_current_votes(api_client): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + await api_client.post('/config', json= + { + 'title': 'Test', + 'author_id': author.id, + 'guild_id': guild.id, + 'options': [{'header': 'option1', 'body': 'desc1'}, + {'header': 'option2', 'body': 'desc2'}] + }) + + resp = await api_client.get('/config?author_id={}&guild_id={}'.format(author.id, guild.id)) + assert resp.status == OK + + jresp = await resp.json() + + assert jresp['embed_title'] == "Your current votes" + assert jresp['embed_body'] == "Test\n" + + +async def test_get_current_votes_no_votes(api_client): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + resp = await api_client.get('/config?author_id={}&guild_id={}'.format(author.id, guild.id)) + assert resp.status == OK + + jresp = await resp.json() + + assert jresp['embed_title'] == "Your current votes" + assert jresp['embed_body'] == "No current votes" + + +# POST /results +async def test_post_results(api_client): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + await api_client.post('/config', json= + { + 'title': 'Test', + 'author_id': author.id, + 'guild_id': guild.id, + 'options': [{'header': 'option1', 'body': 'desc1'}, + {'header': 'option2', 'body': 'desc2'}] + }) + + resp = await api_client.post('/results', json={ + 'author_id': author.id, + 'title': 'Test' + }) + + assert resp.status == OK + jresp = await resp.json() + + assert jresp['embed_title'] == "Test Results:" + assert jresp['embed_body'] == "option1, 1 votes\noption2, 0 votes\n" + # how the hell is this getting votes? + + resp = await api_client.get('/config?author_id={}&guild_id={}'.format(author.id, guild.id)) + + assert (await resp.json())['embed_body'] == "No current votes" + + +# GET /results +async def test_get_results(api_client): + guild: discord.Guild = dpytest.get_config().guilds[0] + author: discord.Member = guild.members[0] + + await api_client.post('/config', json= + { + 'title': 'Test', + 'author_id': author.id, + 'guild_id': guild.id, + 'options': [{'header': 'option1', 'body': 'desc1'}, + {'header': 'option2', 'body': 'desc2'}] + }) + + # NoneType has no attr "name" + + resp = await api_client.get('/results?author_id={}&title=Test'.format(author.id)) + assert resp.status == OK + jresp = await resp.json() + + assert jresp['embed_title'] == "Test Results:" + assert jresp['embed_body'] == "option1, 1 votes\noption2, 0 votes\n" + + # checking vote hasn't closed + resp = await api_client.get('/config?author_id={}&guild_id={}'.format(author.id, guild.id)) + + assert (await resp.json())['embed_body'] == "Test\n" + \ No newline at end of file diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index 7875e493..a4eb89b0 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -359,5 +359,5 @@ async def test_results_invalid_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - with pytest.raises(ValueError, match=f"invalid is not a valid vote title for user {author.name}"): + with pytest.raises(ValueError, match=f"invalid is not a valid vote title for user with id {author.id}"): await core.results(bot, author.id, "invalid") \ No newline at end of file From 1ae35247e1414de9a5170640f4a0ad44772da293 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Fri, 26 May 2023 22:06:14 +0100 Subject: [PATCH 36/41] test: failing not fixed at all cry --- koala/cogs/voting/api.py | 12 +++++++++--- tests/cogs/voting/test_api.py | 7 +++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/koala/cogs/voting/api.py b/koala/cogs/voting/api.py index 521e89db..149bb771 100644 --- a/koala/cogs/voting/api.py +++ b/koala/cogs/voting/api.py @@ -1,7 +1,7 @@ # Futures # Built-in/Generic Imports # Libs -from http.client import BAD_REQUEST, CREATED, OK +from http.client import BAD_REQUEST, CREATED, OK, UNPROCESSABLE_ENTITY from typing import Optional import discord @@ -72,7 +72,7 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, core.set_end_time(author_id, end_time) await core.send_vote(self._bot, author_id, guild_id) - + except Exception as e: logger.error(e) raise web.HTTPUnprocessableEntity() @@ -140,18 +140,24 @@ async def get_results(self, author_id, title): try: message = await core.results(self._bot, author_id, title) if type(message) is discord.Embed: + if message.fields[0].name == "No votes yet!": body = message.fields[0].value + else: body = "" for item in message.fields: body += item.name + ", " + item.value + "\n" + return build_response(OK, {'embed_title': f'{message.title}', 'embed_body': f'{body}'}) else: return build_response(BAD_REQUEST, {'message': message}) - + + except ValueError as e: + return build_response(UNPROCESSABLE_ENTITY, {'message': message}) + except Exception as e: logger.error(e) raise web.HTTPUnprocessableEntity() diff --git a/tests/cogs/voting/test_api.py b/tests/cogs/voting/test_api.py index 347b64a5..c860f4f1 100644 --- a/tests/cogs/voting/test_api.py +++ b/tests/cogs/voting/test_api.py @@ -133,7 +133,7 @@ async def test_get_results(api_client): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] - await api_client.post('/config', json= + resp2 = await api_client.post('/config', json= { 'title': 'Test', 'author_id': author.id, @@ -142,7 +142,10 @@ async def test_get_results(api_client): {'header': 'option2', 'body': 'desc2'}] }) - # NoneType has no attr "name" + assert resp2.status == CREATED + assert (await resp2.json())['message'] == "Vote Test created" + + # for SOME REASON it thinks its an invalid vote; the post is fine resp = await api_client.get('/results?author_id={}&title=Test'.format(author.id)) assert resp.status == OK From f5b8d5427f63e63c2f4ac8a2f56d1d87ed170af5 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 6 Jun 2023 02:53:55 +0800 Subject: [PATCH 37/41] fix: slight change --- koala/cogs/voting/cog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 15053b6c..4cf983f2 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -85,7 +85,7 @@ async def cog_unload(self): @tasks.loop(seconds=60.0) async def vote_end_loop(self): - await core.vote_end_loop(self.bot, self.vote_manager) + await core.vote_end_loop(self.bot, core.vm) @vote_end_loop.before_loop async def before_vote_loop(self): From 6f0dec8f709869fae8a797aeafd4e18c4e3eab84 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Sat, 17 Jun 2023 17:23:08 +0800 Subject: [PATCH 38/41] fix: voting api; failing tests still exist --- koala/cogs/__init__.py | 2 +- koala/cogs/verification/api.py | 2 +- koala/cogs/voting/__init__.py | 9 +++++++-- koala/cogs/voting/api.py | 16 +++++++++++----- koala/cogs/voting/cog.py | 8 +++++--- koala/cogs/voting/core.py | 16 +++++++--------- tests/cogs/voting/test_api.py | 31 +++++++++++++------------------ tests/cogs/voting/test_core.py | 19 +++++++------------ 8 files changed, 52 insertions(+), 51 deletions(-) diff --git a/koala/cogs/__init__.py b/koala/cogs/__init__.py index c82ceaae..2951e19b 100644 --- a/koala/cogs/__init__.py +++ b/koala/cogs/__init__.py @@ -6,4 +6,4 @@ from .text_filter import TextFilter from .twitch_alert import TwitchAlert from .verification import Verification -from .voting import Voting +from .voting import Voting \ No newline at end of file diff --git a/koala/cogs/verification/api.py b/koala/cogs/verification/api.py index 2082dab0..25a929aa 100644 --- a/koala/cogs/verification/api.py +++ b/koala/cogs/verification/api.py @@ -85,4 +85,4 @@ def setup(bot: Bot): endpoint = VerifyEndpoint(bot) endpoint.register(sub_app) getattr(bot, "koala_web_app").add_subapp('/{}'.format(VERIFY_ENDPOINT), sub_app) - logger.info("Base API is ready.") + logger.info("Verify API is ready.") diff --git a/koala/cogs/voting/__init__.py b/koala/cogs/voting/__init__.py index 1c7ac185..c179c63f 100644 --- a/koala/cogs/voting/__init__.py +++ b/koala/cogs/voting/__init__.py @@ -1,2 +1,7 @@ -from . import utils, db, log, models -from .cog import Voting, setup +from . import api +from . import cog +from .cog import Voting + +async def setup(bot): + await cog.setup(bot) + api.setup(bot) \ No newline at end of file diff --git a/koala/cogs/voting/api.py b/koala/cogs/voting/api.py index 149bb771..d1d6b65a 100644 --- a/koala/cogs/voting/api.py +++ b/koala/cogs/voting/api.py @@ -1,13 +1,13 @@ # Futures # Built-in/Generic Imports # Libs -from http.client import BAD_REQUEST, CREATED, OK, UNPROCESSABLE_ENTITY -from typing import Optional +from http.client import BAD_REQUEST, CREATED, OK import discord from aiohttp import web from discord.ext.commands import Bot from koala.cogs.voting.db import VoteManager +from koala.cogs.voting.option import Option from koala.rest.api import parse_request, build_response # Own modules @@ -15,10 +15,12 @@ from .log import logger # Constants +VOTING_ENDPOINT = 'vote' CONFIG_ENDPOINT = 'config' RESULTS_ENDOPINT = 'results' # Variables +vm = VoteManager() class VotingEndpoint: """ @@ -43,7 +45,7 @@ def register(self, app): @parse_request(raw_response=True) async def post_new_vote(self, title, author_id, guild_id, options: list, - roles=None, chair_id=None, end_time=None): + roles=None, chair_id=None, channel_id=None, end_time=None): """ Create a new vote. :param title: The name of the vote @@ -52,6 +54,7 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, :param options: The options for the votes :param roles: The target roles for the votes :param chair_id: The chair id of the vote + :param channel_id: Channel id of the vote :param end_time: The end time of the vote :return: """ @@ -59,7 +62,7 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, core.start_vote(self._bot, title, author_id, guild_id) for item in options: - core.add_option(author_id, item) + core.add_option(author_id, Option(item[0], item[1], vm.generate_unique_opt_id())) if roles is not None: for item in roles: @@ -68,6 +71,9 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, if chair_id is not None: await core.set_chair(self._bot, author_id, chair_id) + if channel_id is not None: + await core.set_channel(self._bot, author_id, channel_id) + if end_time is not None: core.set_end_time(author_id, end_time) @@ -156,7 +162,7 @@ async def get_results(self, author_id, title): return build_response(BAD_REQUEST, {'message': message}) except ValueError as e: - return build_response(UNPROCESSABLE_ENTITY, {'message': message}) + raise web.HTTPUnprocessableEntity(reason="{}".format(e)) except Exception as e: logger.error(e) diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 4cf983f2..20253df2 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -14,8 +14,9 @@ import koalabot from koala.db import insert_extension from . import core -from .db import VoteManager, add_reactions +from .db import add_reactions from .log import logger +from .option import Option # Constants @@ -195,7 +196,9 @@ async def add_option(self, ctx, *, option_string): await ctx.send("Example usage: k!vote addOption option title+option description") else: header, body = option_string.split("+") - option = {'header': header, 'body': body} + option = Option() + option.head = header + option.body = body await ctx.send(core.add_option(ctx.author.id, option)) @@ -302,4 +305,3 @@ async def setup(bot: koalabot) -> None: :param bot: the bot client for KoalaBot """ await bot.add_cog(Voting(bot)) - logger.info("Voting is ready.") diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 7dad40df..4b3199a9 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -148,24 +148,23 @@ def set_channel(bot: koalabot.KoalaBot, author_id, channel_id=None): # OPTION ATTRIBUTES TITLE, DESCRPTION (OBJECT!) # NO MORE + -def add_option(author_id, option): +def add_option(author_id, option: Option): vote = vm.get_configuring_vote(author_id) if len(vote.options) > 9: return "Vote has maximum number of options already (10)" - if 'header' not in option or 'body' not in option: + if option.head is None or option.body is None: return "Option should have both header and body." current_option_length = sum([len(x.head) + len(x.body) for x in vote.options]) - if current_option_length + len(option['header']) + len(option['body']) > 1500: + if current_option_length + len(option.head) + len(option.body) > 1500: return "Option string is too long. The total length of all the vote options cannot be over 1500 characters." - # moved check for '+' in string to cog - - vote.add_option(Option(option['header'], option['body'], vm.generate_unique_opt_id())) - return f"Option {option['header']} with description {option['body']} added to vote" + option.opt_id = vm.generate_unique_opt_id() + vote.add_option(option) + return f"Option {option.head} with description {option.body} added to vote" def remove_option(author_id, index): @@ -274,8 +273,7 @@ async def close(bot: koalabot.KoalaBot, author_id, title): async def results(bot: koalabot.KoalaBot, author_id, title): - vote_id = vm.vote_lookup.get((author_id, title)) - # author = bot.get_user(author_id) + vote_id = vm.vote_lookup[(author_id, title)] if vote_id is None: raise ValueError(f"{title} is not a valid vote title for user with id {author_id}") diff --git a/tests/cogs/voting/test_api.py b/tests/cogs/voting/test_api.py index c860f4f1..a53009aa 100644 --- a/tests/cogs/voting/test_api.py +++ b/tests/cogs/voting/test_api.py @@ -5,12 +5,12 @@ import discord.ext.test as dpytest import pytest from aiohttp import web -from mock import mock -import json -import koalabot from koala.cogs.voting.api import VotingEndpoint +# Variables +options = [["option1", "body1"], + ["option2", "body2"]] @pytest.fixture def api_client(bot: discord.ext.commands.Bot, aiohttp_client, loop): @@ -32,8 +32,7 @@ async def test_post_new_vote_no_optionals(api_client): 'title': 'Test', 'author_id': author.id, 'guild_id': guild.id, - 'options': [{'header': 'option1', 'body': 'desc1'}, - {'header': 'option2', 'body': 'desc2'}] + 'options': options }) assert resp.status == CREATED @@ -49,8 +48,7 @@ async def test_post_new_vote_with_optionals(api_client): 'title': 'Test2', 'author_id': author.id, 'guild_id': guild.id, - 'options': [{'header': 'option1', 'body': 'desc1'}, - {'header': 'option2', 'body': 'desc2'}], + 'options': options, 'roles': [guild.roles[0].id], 'chair_id': guild.members[1].id, 'end_time': '2025-01-01 00:00:00' @@ -71,8 +69,7 @@ async def test_get_current_votes(api_client): 'title': 'Test', 'author_id': author.id, 'guild_id': guild.id, - 'options': [{'header': 'option1', 'body': 'desc1'}, - {'header': 'option2', 'body': 'desc2'}] + 'options': options }) resp = await api_client.get('/config?author_id={}&guild_id={}'.format(author.id, guild.id)) @@ -107,8 +104,7 @@ async def test_post_results(api_client): 'title': 'Test', 'author_id': author.id, 'guild_id': guild.id, - 'options': [{'header': 'option1', 'body': 'desc1'}, - {'header': 'option2', 'body': 'desc2'}] + 'options': options }) resp = await api_client.post('/results', json={ @@ -135,27 +131,26 @@ async def test_get_results(api_client): resp2 = await api_client.post('/config', json= { - 'title': 'Test', + 'title': 'Test2', 'author_id': author.id, 'guild_id': guild.id, - 'options': [{'header': 'option1', 'body': 'desc1'}, - {'header': 'option2', 'body': 'desc2'}] + 'options': options }) assert resp2.status == CREATED - assert (await resp2.json())['message'] == "Vote Test created" + assert (await resp2.json())['message'] == "Vote Test2 created" # for SOME REASON it thinks its an invalid vote; the post is fine - resp = await api_client.get('/results?author_id={}&title=Test'.format(author.id)) + resp = await api_client.get('/results?author_id={}&title=Test2'.format(author.id)) assert resp.status == OK jresp = await resp.json() - assert jresp['embed_title'] == "Test Results:" + assert jresp['embed_title'] == "Test2 Results:" assert jresp['embed_body'] == "option1, 1 votes\noption2, 0 votes\n" # checking vote hasn't closed resp = await api_client.get('/config?author_id={}&guild_id={}'.format(author.id, guild.id)) - assert (await resp.json())['embed_body'] == "Test\n" + assert (await resp.json())['embed_body'] == "Test2\n" \ No newline at end of file diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index a4eb89b0..ac4aed4d 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -8,16 +8,17 @@ from sqlalchemy import select from koala.cogs import Voting from koala.cogs.voting.models import Votes +from koala.cogs.voting.option import Option # Own modules import koalabot -from koala.db import assign_session, session_manager, insert_extension +from koala.db import assign_session from tests.log import logger from koala.cogs.voting import core # Variables -option1 = {'header': 'option1', 'body': 'desc1'} -option2 = {'header': 'option2', 'body': 'desc2'} +option1 = Option('option1', 'body1', 1) +option2 = Option('option2', 'body2', 2) @pytest_asyncio.fixture(scope="function", autouse=True) @@ -29,12 +30,6 @@ async def cog(bot: commands.Bot): return cog -@pytest.mark.asyncio -async def test_update_vote_message(bot: commands.Bot): - pass - # await core.update_vote_message(bot) - - def test_create_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] @@ -146,7 +141,7 @@ def test_add_option(bot: commands.Bot, cog): core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.add_option(author.id, option1) == "Option option1 with description desc1 added to vote" + assert core.add_option(author.id, option1) == "Option option1 with description body1 added to vote" def test_add_option_wrong_formatting(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] @@ -154,7 +149,7 @@ def test_add_option_wrong_formatting(bot: commands.Bot, cog): core.start_vote(bot, "Test Vote", author.id, guild.id) - option = {'header': 'Option 1'} + option = Option("head", None, 1) assert core.add_option(author.id, option) == "Option should have both header and body." @@ -178,7 +173,7 @@ def test_add_option_too_long(bot: commands.Bot, cog): author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - test_option = {'header': "i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup", 'body': 'i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably.'} + test_option = Option("i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup", 'i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably.', 3) x = 0 while (x < 5): core.add_option(author.id, test_option) From 82230a3bb701ec6660f43c255ed925c4ba26220e Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 4 Jul 2023 20:33:40 +0100 Subject: [PATCH 39/41] fix: pass Options as objects into API --- koala/cogs/voting/api.py | 2 +- koala/cogs/voting/cog.py | 5 +---- koala/cogs/voting/core.py | 11 +++++------ tests/cogs/voting/test_api.py | 12 ++++++++++-- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/koala/cogs/voting/api.py b/koala/cogs/voting/api.py index d1d6b65a..846b132f 100644 --- a/koala/cogs/voting/api.py +++ b/koala/cogs/voting/api.py @@ -62,7 +62,7 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, core.start_vote(self._bot, title, author_id, guild_id) for item in options: - core.add_option(author_id, Option(item[0], item[1], vm.generate_unique_opt_id())) + core.add_option(author_id, item.get("header"), item.get("body")) if roles is not None: for item in roles: diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 20253df2..7e17a6eb 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -196,10 +196,7 @@ async def add_option(self, ctx, *, option_string): await ctx.send("Example usage: k!vote addOption option title+option description") else: header, body = option_string.split("+") - option = Option() - option.head = header - option.body = body - await ctx.send(core.add_option(ctx.author.id, option)) + await ctx.send(core.add_option(ctx.author.id, header, body)) @currently_configuring() diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index 4b3199a9..ea0edf00 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -148,23 +148,22 @@ def set_channel(bot: koalabot.KoalaBot, author_id, channel_id=None): # OPTION ATTRIBUTES TITLE, DESCRPTION (OBJECT!) # NO MORE + -def add_option(author_id, option: Option): +def add_option(author_id, option_header, option_body): vote = vm.get_configuring_vote(author_id) if len(vote.options) > 9: return "Vote has maximum number of options already (10)" - if option.head is None or option.body is None: + if option_header is None or option_body is None: return "Option should have both header and body." current_option_length = sum([len(x.head) + len(x.body) for x in vote.options]) - if current_option_length + len(option.head) + len(option.body) > 1500: + if current_option_length + len(option_header) + len(option_body) > 1500: return "Option string is too long. The total length of all the vote options cannot be over 1500 characters." - option.opt_id = vm.generate_unique_opt_id() - vote.add_option(option) - return f"Option {option.head} with description {option.body} added to vote" + vote.add_option(Option(option_header, option_body, vm.generate_unique_opt_id())) + return f"Option {option_header} with description {option_body} added to vote" def remove_option(author_id, index): diff --git a/tests/cogs/voting/test_api.py b/tests/cogs/voting/test_api.py index a53009aa..a9486d31 100644 --- a/tests/cogs/voting/test_api.py +++ b/tests/cogs/voting/test_api.py @@ -9,8 +9,16 @@ from koala.cogs.voting.api import VotingEndpoint # Variables -options = [["option1", "body1"], - ["option2", "body2"]] +options = [ + { + "header": "option1", + "body": "option1desc" + }, + { + "header": "option2", + "body": "option2desc" + } + ], @pytest.fixture def api_client(bot: discord.ext.commands.Bot, aiohttp_client, loop): From 649803ced1db1cb914adb298750d00e418d2ce68 Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Tue, 4 Jul 2023 22:03:18 +0100 Subject: [PATCH 40/41] test: fix most failing tests --- koala/cogs/voting/api.py | 14 ++++----- koala/cogs/voting/cog.py | 2 -- koala/cogs/voting/core.py | 14 ++++----- koala/cogs/voting/db.py | 3 +- tests/cogs/voting/test_api.py | 2 +- tests/cogs/voting/test_core.py | 57 +++++++++++++++------------------- tests/cogs/voting/test_db.py | 20 ++++++------ 7 files changed, 50 insertions(+), 62 deletions(-) diff --git a/koala/cogs/voting/api.py b/koala/cogs/voting/api.py index 846b132f..e452a22b 100644 --- a/koala/cogs/voting/api.py +++ b/koala/cogs/voting/api.py @@ -2,6 +2,7 @@ # Built-in/Generic Imports # Libs from http.client import BAD_REQUEST, CREATED, OK +from typing import List import discord from aiohttp import web @@ -19,9 +20,6 @@ CONFIG_ENDPOINT = 'config' RESULTS_ENDOPINT = 'results' -# Variables -vm = VoteManager() - class VotingEndpoint: """ The API endpoints for Voting @@ -44,7 +42,7 @@ def register(self, app): @parse_request(raw_response=True) - async def post_new_vote(self, title, author_id, guild_id, options: list, + async def post_new_vote(self, title, author_id, guild_id, options: List[dict], roles=None, chair_id=None, channel_id=None, end_time=None): """ Create a new vote. @@ -61,8 +59,11 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, try: core.start_vote(self._bot, title, author_id, guild_id) + if channel_id is not None: + await core.set_channel(self._bot, author_id, channel_id) + for item in options: - core.add_option(author_id, item.get("header"), item.get("body")) + core.add_option(author_id, item["header"], item["body"]) if roles is not None: for item in roles: @@ -71,9 +72,6 @@ async def post_new_vote(self, title, author_id, guild_id, options: list, if chair_id is not None: await core.set_chair(self._bot, author_id, chair_id) - if channel_id is not None: - await core.set_channel(self._bot, author_id, channel_id) - if end_time is not None: core.set_end_time(author_id, end_time) diff --git a/koala/cogs/voting/cog.py b/koala/cogs/voting/cog.py index 7e17a6eb..889237ea 100644 --- a/koala/cogs/voting/cog.py +++ b/koala/cogs/voting/cog.py @@ -181,8 +181,6 @@ async def set_channel(self, ctx, *, channel: discord.VoiceChannel = None): await ctx.send(core.set_channel(self.bot, ctx.author.id, channel.id)) -# PROCESS THE + IN OPTIONS HERE -# NO + IN CORE!!! @currently_configuring() @commands.check(vote_is_enabled) @vote.command(name="addOption") diff --git a/koala/cogs/voting/core.py b/koala/cogs/voting/core.py index ea0edf00..36005e73 100644 --- a/koala/cogs/voting/core.py +++ b/koala/cogs/voting/core.py @@ -100,7 +100,7 @@ def start_vote(bot: koalabot.KoalaBot, title, author_id, guild_id, session: Sess if len(title) > 200: return "Title too long" - vm.create_vote(author_id, guild_id, title) + vm.create_vote(author_id, guild_id, title, session) return f"Vote titled `{title}` created for guild {guild_name}. Use `{koalabot.COMMAND_PREFIX}help vote` to see how to configure it." @@ -145,18 +145,12 @@ def set_channel(bot: koalabot.KoalaBot, author_id, channel_id=None): return "Removed channel restriction on vote" -# OPTION ATTRIBUTES TITLE, DESCRPTION (OBJECT!) -# NO MORE + - def add_option(author_id, option_header, option_body): vote = vm.get_configuring_vote(author_id) if len(vote.options) > 9: return "Vote has maximum number of options already (10)" - if option_header is None or option_body is None: - return "Option should have both header and body." - current_option_length = sum([len(x.head) + len(x.body) for x in vote.options]) if current_option_length + len(option_header) + len(option_body) > 1500: @@ -272,7 +266,11 @@ async def close(bot: koalabot.KoalaBot, author_id, title): async def results(bot: koalabot.KoalaBot, author_id, title): - vote_id = vm.vote_lookup[(author_id, title)] + try: + vote_id = vm.vote_lookup[(author_id, title)] + except KeyError as e: + logger.error(e) + raise ValueError(f"{title} is not a valid vote title for user with id {author_id}") if vote_id is None: raise ValueError(f"{title} is not a valid vote title for user with id {author_id}") diff --git a/koala/cogs/voting/db.py b/koala/cogs/voting/db.py index 7befea65..b4e171bb 100644 --- a/koala/cogs/voting/db.py +++ b/koala/cogs/voting/db.py @@ -10,6 +10,7 @@ # Libs import discord from sqlalchemy import select, delete +from sqlalchemy.orm import Session # Own modules from koala.db import session_manager @@ -151,7 +152,7 @@ def has_active_vote(self, author_id): """ return author_id in self.configuring_votes.keys() - def create_vote(self, author_id, guild_id, title): + def create_vote(self, author_id, guild_id, title, session: Session): """ Creates a vote object and assigns it to a users ID :param author_id: id of the author of the vote diff --git a/tests/cogs/voting/test_api.py b/tests/cogs/voting/test_api.py index a9486d31..d64ff580 100644 --- a/tests/cogs/voting/test_api.py +++ b/tests/cogs/voting/test_api.py @@ -18,7 +18,7 @@ "header": "option2", "body": "option2desc" } - ], + ] @pytest.fixture def api_client(bot: discord.ext.commands.Bot, aiohttp_client, loop): diff --git a/tests/cogs/voting/test_core.py b/tests/cogs/voting/test_core.py index ac4aed4d..2a5420b6 100644 --- a/tests/cogs/voting/test_core.py +++ b/tests/cogs/voting/test_core.py @@ -17,8 +17,10 @@ from koala.cogs.voting import core # Variables -option1 = Option('option1', 'body1', 1) -option2 = Option('option2', 'body2', 2) +opt1_header = "option1" +opt1_body = "desc1" +opt2_header = "option2" +opt2_body = "desc2" @pytest_asyncio.fixture(scope="function", autouse=True) @@ -141,17 +143,7 @@ def test_add_option(bot: commands.Bot, cog): core.start_vote(bot, "Test Vote", author.id, guild.id) - assert core.add_option(author.id, option1) == "Option option1 with description body1 added to vote" - -def test_add_option_wrong_formatting(bot: commands.Bot, cog): - guild: discord.Guild = dpytest.get_config().guilds[0] - author: discord.Member = guild.members[0] - - core.start_vote(bot, "Test Vote", author.id, guild.id) - - option = Option("head", None, 1) - - assert core.add_option(author.id, option) == "Option should have both header and body." + assert core.add_option(author.id, opt1_header, opt1_body) == "Option option1 with description desc1 added to vote" def test_add_option_too_many(bot: commands.Bot, cog): @@ -162,10 +154,10 @@ def test_add_option_too_many(bot: commands.Bot, cog): x = 0 while (x < 11): - core.add_option(author.id, option1) + core.add_option(author.id, opt1_header, opt1_body) x += 1 - assert core.add_option(author.id, option1) == "Vote has maximum number of options already (10)" + assert core.add_option(author.id, opt1_header, opt1_body) == "Vote has maximum number of options already (10)" def test_add_option_too_long(bot: commands.Bot, cog): @@ -173,13 +165,14 @@ def test_add_option_too_long(bot: commands.Bot, cog): author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - test_option = Option("i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup", 'i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably.', 3) + test_option_header = "i am trying to write a lot of words here. needs to be over fifteen thousand words to be exact. but i'm separating it so it does not all get clustered into the same paragraph and become a word soup" + test_option_body = 'i was wrong, it is actually fifteen hundred words. whoever actually reads this will get a little entertainment i hope. is there a better way to test this? probably.' x = 0 while (x < 5): - core.add_option(author.id, test_option) + core.add_option(author.id, test_option_header, test_option_body) x += 1 - assert core.add_option(author.id, test_option) == "Option string is too long. The total length of all the vote options cannot be over 1500 characters." + assert core.add_option(author.id, test_option_header, test_option_body) == "Option string is too long. The total length of all the vote options cannot be over 1500 characters." def test_remove_option(bot: commands.Bot, cog): @@ -187,7 +180,7 @@ def test_remove_option(bot: commands.Bot, cog): author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, option1) + core.add_option(author.id, opt1_header, opt1_body) assert core.remove_option(author.id, 0) == "Option number 0 removed" @@ -230,8 +223,8 @@ async def test_cancel_sent_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, option1) - core.add_option(author.id, option2) + core.add_option(author.id, opt1_header, opt1_body) + core.add_option(author.id, opt2_header, opt2_body) await core.send_vote(bot, author.id, guild.id) @@ -269,8 +262,8 @@ async def test_close_no_chair(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, option1) - core.add_option(author.id, option2) + core.add_option(author.id, opt1_header, opt1_body) + core.add_option(author.id, opt2_header, opt2_body) await core.send_vote(bot, author.id, guild.id) @@ -286,8 +279,8 @@ async def test_close_with_chair(bot: commands.Bot, cog): author: discord.Member = guild.members[0] chair: discord.Member = guild.members[1] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, option1) - core.add_option(author.id, option2) + core.add_option(author.id, opt1_header, opt1_body) + core.add_option(author.id, opt2_header, opt2_body) await core.set_chair(bot, author.id, chair.id) await core.send_vote(bot, author.id, guild.id) @@ -300,8 +293,8 @@ async def test_send_vote(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, option1) - core.add_option(author.id, option2) + core.add_option(author.id, opt1_header, opt1_body) + core.add_option(author.id, opt2_header, opt2_body) # not sure how to assert DM sent @@ -318,7 +311,7 @@ async def test_send_vote_bad_options(bot: commands.Bot, cog): assert await core.send_vote(bot, author.id, guild.id) == "Please add more than 1 option to vote for" # only 1 option - core.add_option(author.id, option1) + core.add_option(author.id, opt1_header, opt1_body) assert await core.send_vote(bot, author.id, guild.id) == "Please add more than 1 option to vote for" @@ -327,8 +320,8 @@ async def test_get_results(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, option1) - core.add_option(author.id, option2) + core.add_option(author.id, opt1_header, opt1_body) + core.add_option(author.id, opt2_header, opt2_body) await core.send_vote(bot, author.id, guild.id) @@ -343,8 +336,8 @@ async def test_results_vote_not_sent(bot: commands.Bot, cog): guild: discord.Guild = dpytest.get_config().guilds[0] author: discord.Member = guild.members[0] core.start_vote(bot, "Test Vote", author.id, guild.id) - core.add_option(author.id, option1) - core.add_option(author.id, option2) + core.add_option(author.id, opt1_header, opt1_body) + core.add_option(author.id, opt2_header, opt2_body) assert await core.results(bot, author.id, "Test Vote") == "That vote has not been sent yet. Please send it to your audience with k!vote send Test Vote" diff --git a/tests/cogs/voting/test_db.py b/tests/cogs/voting/test_db.py index dee9d015..101b5920 100644 --- a/tests/cogs/voting/test_db.py +++ b/tests/cogs/voting/test_db.py @@ -66,7 +66,7 @@ def test_votemanager_has_active_vote(): def test_votemanager_create_vote(): with session_manager() as session: - vote = vote_manager.create_vote(123, 456, "Create Vote Test") + vote = vote_manager.create_vote(123, 456, "Create Vote Test", session) assert vote.title == "Create Vote Test" in_db = session.execute(select(Votes).filter_by(author_id=123, title="Create Vote Test")).all() assert in_db @@ -100,12 +100,12 @@ def test_votemanager_sent_to(): def test_vote_set_chair(): - vote = vote_manager.create_vote(111, 222, "Set Chair Vote Test") - vote.set_chair(555) - assert vote.chair == 555 with session_manager() as session: - in_db = session.execute(select(Votes).filter_by(vote_id=vote.id)).scalar() - assert in_db.chair_id == 555 + vote = vote_manager.create_vote(111, 222, "Set Chair Vote Test", session) + vote.set_chair(555) + assert vote.chair == 555 + in_db = session.execute(select(Votes).filter_by(vote_id=vote.id, chair_id=555)).all() + assert in_db vote.set_chair() assert not vote.chair session.expire(in_db) @@ -115,7 +115,7 @@ def test_vote_set_chair(): def test_vote_set_vc(): with session_manager() as session: - vote = vote_manager.create_vote(111, 222, "Set Chair Vote Test") + vote = vote_manager.create_vote(111, 222, "Set Chair Vote Test", session) vote.set_vc(555) assert vote.target_voice_channel == 555 in_db = session.execute(select(Votes).filter_by(vote_id=vote.id)).scalar() @@ -129,7 +129,7 @@ def test_vote_set_vc(): def test_vote_add_option(): with session_manager() as session: - vote = vote_manager.create_vote(111, 222, "Add Option Test") + vote = vote_manager.create_vote(111, 222, "Add Option Test", session) vote.add_option(Option("head", "body", 123)) assert vote.options[0].head == "head" assert vote.options[0].body == "body" @@ -139,7 +139,7 @@ def test_vote_add_option(): def test_vote_remove_option(): with session_manager() as session: - vote = vote_manager.create_vote(111, 222, "Remove Option Test") + vote = vote_manager.create_vote(111, 222, "Remove Option Test", session) vote.add_option(Option("head", "body", 123)) vote.remove_option(0) in_db = session.execute(select(VoteOptions).filter_by(opt_id=123)).all() @@ -148,7 +148,7 @@ def test_vote_remove_option(): def test_vote_register_sent(): with session_manager() as session: - vote = vote_manager.create_vote(111, 222, "Register Sent Test") + vote = vote_manager.create_vote(111, 222, "Register Sent Test", session) vote.register_sent(555, 666) assert vote.sent_to[555] == 666 in_db = session.execute(select(VoteSent).filter_by(vote_receiver_message=666)).all() From c7560fb1c886cdf8f58c20c04f1522a61f771d4a Mon Sep 17 00:00:00 2001 From: abluey <zoe1615@hotmail.com> Date: Mon, 16 Oct 2023 22:00:31 +0100 Subject: [PATCH 41/41] chore: changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ad354d4..78df2100 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ administrators - Fix an issue where multiple emails can be passed to bypass domain-specific verification ### Insights - Added new commands `insights` and `servers` under the insights cog +### Voting +- Add backend API ## [0.6.0] - 01-01-2023 - Upgraded to discord.py 2.1.0