From 984b049348e8de9a546bb7f8d8299bd00063a6f7 Mon Sep 17 00:00:00 2001 From: Steven Loria Date: Mon, 11 Mar 2024 21:31:17 -0400 Subject: [PATCH] remove practice sessions; remove asynctest --- bot/exts/practices/_practice_sessions.py | 147 -------- bot/exts/practices/daily_message.py | 36 +- bot/exts/practices/practice.py | 334 ------------------ requirements-dev.txt | 2 +- .../bot/exts/__snapshots__/test_practice.ambr | 168 --------- tests/bot/exts/test_practice.py | 202 ----------- tests/conftest.py | 2 +- 7 files changed, 17 insertions(+), 874 deletions(-) delete mode 100644 bot/exts/practices/_practice_sessions.py delete mode 100644 bot/exts/practices/practice.py delete mode 100644 tests/bot/exts/__snapshots__/test_practice.ambr delete mode 100644 tests/bot/exts/test_practice.py diff --git a/bot/exts/practices/_practice_sessions.py b/bot/exts/practices/_practice_sessions.py deleted file mode 100644 index dfb51d3e..00000000 --- a/bot/exts/practices/_practice_sessions.py +++ /dev/null @@ -1,147 +0,0 @@ -import datetime as dt -import logging -from typing import List, NamedTuple, Optional - -import disnake -import holiday_emojis - -from bot import settings -from bot.database import store -from bot.utils import truncate -from bot.utils.datetimes import ( - PACIFIC, - PACIFIC_CURRENT_NAME, - format_datetime, - parse_human_readable_datetime, - utcnow, -) -from bot.utils.discord import THEME_COLOR -from bot.utils.gcal import create_gcal_url -from bot.utils.gsheets import get_gsheet_client - -logger = logging.getLogger(__name__) - -COMMAND_PREFIX = settings.COMMAND_PREFIX - - -class PracticeSession(NamedTuple): - dtime: dt.datetime - host: str - mention: str - notes: str - - -async def get_practice_worksheet_for_guild(guild_id: int): - logger.info(f"fetching practice worksheet {guild_id}") - client = get_gsheet_client() - sheet_key = await store.get_guild_schedule_sheet_key(guild_id) - assert sheet_key is not None - sheet = client.open_by_key(sheet_key) - return sheet.get_worksheet(0) - - -async def get_practice_sessions( - guild_id: int, - dtime: dt.datetime, - *, - worksheet=None, - parse_settings: Optional[dict] = None, -) -> List[PracticeSession]: - worksheet = worksheet or await get_practice_worksheet_for_guild(guild_id) - all_values = worksheet.get_all_values() - return sorted( - ( - PracticeSession( - dtime=session_dtime, - host=row[1], - mention=row[2], - notes=row[3], - ) - for row in all_values[2:] # First two rows are documentation and headers - if row - and ( - session_dtime := parse_human_readable_datetime( - row[0], settings=parse_settings - )[0] - ) - # Compare within Pacific timezone to include all of US - and ( - session_dtime.astimezone(PACIFIC).date() - == dtime.astimezone(PACIFIC).date() - ) - # Filter out paused sessions - and not bool(row[4]) - ), - key=lambda s: s.dtime, - ) - - -NO_PRACTICES = """ - -*There are no scheduled practices today!* - -To schedule a practice, edit the schedule below or use the `{COMMAND_PREFIX}practice` command. -Example: `{COMMAND_PREFIX}practice today 2pm {pacific}` -""".format( - COMMAND_PREFIX=COMMAND_PREFIX, - pacific=PACIFIC_CURRENT_NAME.lower(), -) - - -def make_base_embed(dtime: dt.datetime) -> disnake.Embed: - now_pacific = utcnow().astimezone(PACIFIC) - dtime_pacific = dtime.astimezone(PACIFIC) - description = dtime_pacific.strftime("%A, %B %-d") - if dtime_pacific.date() == now_pacific.date(): - description = f"Today - {description}" - elif (dtime_pacific.date() - now_pacific.date()).days == 1: - description = f"Tomorrow - {description}" - holiday = holiday_emojis.get(dtime_pacific.date()) - if holiday and holiday.emoji: - description += f" {holiday.emoji}" - return disnake.Embed(description=description, color=THEME_COLOR) - - -async def make_practice_session_embed( - guild_id: int, sessions: List[PracticeSession], *, dtime: dt.datetime -) -> disnake.Embed: - embed = make_base_embed(dtime) - sheet_key = await store.get_guild_schedule_sheet_key(guild_id) - schedule_url = f"https://docs.google.com/spreadsheets/d/{sheet_key}/edit" - if not sessions and embed.description: - embed.description += NO_PRACTICES - else: - num_sessions = len(sessions) - for session in sessions: - title = format_datetime(session.dtime, format_type="t") - gcal_event_title = ( - f"ASL Practice: {truncate(session.notes, 50)}" - if session.notes - else "ASL Practice" - ) - gcal_url = create_gcal_url( - gcal_event_title, - start=session.dtime, - description=f"See the full schedule here: {schedule_url}", - ) - value = f"[+Google Calendar]({gcal_url})" - if session.host: - value += f"\n> Host: {session.mention or session.host}" - if session.notes: - limit = 800 // num_sessions - trailing = f"…[More]({schedule_url})" - value += ( - f"\n> Details: {truncate(session.notes, limit, trailing=trailing)}" - ) - embed.add_field(name=title, value=value, inline=False) - embed.add_field( - name="🗓 View or edit the schedule using the link below.", - value=f"[Full schedule]({schedule_url})", - ) - return embed - - -async def make_practice_sessions_today_embed(guild_id: int) -> disnake.Embed: - now = utcnow() - sessions = await get_practice_sessions(guild_id, dtime=now) - return await make_practice_session_embed(guild_id, sessions, dtime=now) diff --git a/bot/exts/practices/daily_message.py b/bot/exts/practices/daily_message.py index 1f111bca..e06f6c17 100644 --- a/bot/exts/practices/daily_message.py +++ b/bot/exts/practices/daily_message.py @@ -24,18 +24,26 @@ from bot.utils.discord import THEME_COLOR, get_event_url from bot.utils.tasks import daily_task -from ._practice_sessions import ( - get_practice_sessions, - make_base_embed, - make_practice_session_embed, -) - logger = logging.getLogger(__name__) COMMAND_PREFIX = settings.COMMAND_PREFIX HERE = Path(__file__).parent +def make_base_embed(dtime: dt.datetime) -> disnake.Embed: + now_pacific = utcnow().astimezone(PACIFIC) + dtime_pacific = dtime.astimezone(PACIFIC) + description = dtime_pacific.strftime("%A, %B %-d") + if dtime_pacific.date() == now_pacific.date(): + description = f"Today - {description}" + elif (dtime_pacific.date() - now_pacific.date()).days == 1: + description = f"Tomorrow - {description}" + holiday = holiday_emojis.get(dtime_pacific.date()) + if holiday and holiday.emoji: + description += f" {holiday.emoji}" + return disnake.Embed(description=description, color=THEME_COLOR) + + def get_today_random(dtime: Optional[dt.datetime] = None) -> random.Random: dtime = dtime or utcnow() seed = settings.DAILY_MESSAGE_RANDOM_SEED or dtime.date().toordinal() @@ -120,25 +128,11 @@ async def send_daily_message( channel = cast(disnake.TextChannel, self.bot.get_channel(channel_id)) guild = channel.guild logger.info(f'sending daily message for guild: "{guild.name}" in #{channel.name}') - guild_id = guild.id dtime = dtime or utcnow() - prefer_dates_from = ( - "current_period" if dtime.date() <= dt.date.today() else "future" - ) - parse_settings = {"PREFER_DATES_FROM": prefer_dates_from} settings = await store.get_guild_settings(guild.id) if not settings: return - embed: disnake.Embed - if settings["include_practice_schedule"]: - sessions = await get_practice_sessions( - guild_id, - dtime=dtime, - parse_settings=parse_settings, - ) - embed = await make_practice_session_embed(guild_id, sessions, dtime=dtime) - else: - embed = make_base_embed(dtime=dtime) + embed = make_base_embed(dtime=dtime) if settings["include_scheduled_events"]: # Display scheduled events for today diff --git a/bot/exts/practices/practice.py b/bot/exts/practices/practice.py deleted file mode 100644 index 17eb566e..00000000 --- a/bot/exts/practices/practice.py +++ /dev/null @@ -1,334 +0,0 @@ -import datetime as dt -import logging -from contextlib import suppress -from typing import Any, Dict, Optional, Tuple - -import disnake -import pytz -from disnake.ext import commands -from disnake.ext.commands import Context -from pytz.tzinfo import StaticTzInfo - -from bot import settings -from bot.database import store -from bot.utils import get_and_strip_quoted_text -from bot.utils.datetimes import ( - EASTERN_CURRENT_NAME, - PACIFIC, - PACIFIC_CURRENT_NAME, - NoTimeZoneError, - display_timezone, - format_datetime, - parse_human_readable_datetime, - utcnow, -) -from bot.utils.discord import display_name - -from ._practice_sessions import ( - get_practice_sessions, - get_practice_worksheet_for_guild, - make_practice_session_embed, -) - -logger = logging.getLogger(__name__) - -COMMAND_PREFIX = settings.COMMAND_PREFIX - - -async def is_in_guild(ctx: Context) -> bool: - if not bool(ctx.guild): - raise commands.errors.CheckFailure( - f"⚠️ `{COMMAND_PREFIX}{ctx.invoked_with}` must be run within a server (not a DM)." - ) - return True - - -async def has_practice_schedule(ctx: Context) -> bool: - await is_in_guild(ctx) - assert ctx.guild is not None - has_practice_schedule = await store.guild_has_practice_schedule(ctx.guild.id) - if not has_practice_schedule: - raise commands.errors.CheckFailure( - "⚠️ No configured practice schedule for this server. If you think this is a mistake, contact the bot owner." - ) - return True - - -SCHEDULE_HELP = """List the practice schedule for this server - -Defaults to sending today's schedule. -Must be used within a server (not a DM). - -Examples: -``` -{COMMAND_PREFIX}schedule -{COMMAND_PREFIX}schedule tomorrow -{COMMAND_PREFIX}schedule friday -{COMMAND_PREFIX}schedule Sept 29 -{COMMAND_PREFIX}schedule 10/3 -``` -""".format( - COMMAND_PREFIX=COMMAND_PREFIX -) - - -async def schedule_impl(guild_id: int, when: Optional[str]): - settings: Optional[Dict[str, Any]] - if when and when.strip().lower() != "today": - now_pacific = utcnow().astimezone(PACIFIC) - settings = { - "PREFER_DATES_FROM": "future", - # Workaround for https://github.com/scrapinghub/dateparser/issues/403 - "RELATIVE_BASE": now_pacific.replace(tzinfo=None), - } - dtime, _ = parse_human_readable_datetime(when, settings=settings) - dtime = dtime or utcnow() - else: - settings = None - dtime = utcnow() - sessions = await get_practice_sessions(guild_id, dtime=dtime, parse_settings=settings) - embed = await make_practice_session_embed(guild_id, sessions, dtime=dtime) - return {"embed": embed} - - -PRACTICE_HELP = """Schedule a practice session - -This will add an entry to the practice spreadsheet (use ?schedule to get the link). -Must be used within a server (not a DM). - -Tips: - -* Don't forget to include "am" or "pm". -* Don't forget to include a timezone, e.g. "{pacific}". -* If you don't include a date, today is assumed. -* You may optionally add notes within double quotes. - -Examples: -``` -{COMMAND_PREFIX}practice today 2pm {pacific} -{COMMAND_PREFIX}practice tomorrow 5pm {eastern} "chat for ~1 hour" -{COMMAND_PREFIX}practice saturday 6pm {pacific} "Game night 🎉" -{COMMAND_PREFIX}practice 9/24 6pm {eastern} "watch2gether session" -{COMMAND_PREFIX}practice "classifiers" at 6pm {pacific} -``` -""".format( - COMMAND_PREFIX=COMMAND_PREFIX, - pacific=PACIFIC_CURRENT_NAME.lower(), - eastern=EASTERN_CURRENT_NAME.lower(), -) - -PRACTICE_ERROR = """⚠️To schedule a practice, enter a time after `{COMMAND_PREFIX}practice`. -Example: `{COMMAND_PREFIX}practice today at 2pm {eastern}` -Enter `{COMMAND_PREFIX}schedule` to see today's schedule. -""".format( - COMMAND_PREFIX=COMMAND_PREFIX, eastern=EASTERN_CURRENT_NAME.lower() -) - - -def parse_practice_time( - human_readable_datetime: str, user_timezone: Optional[StaticTzInfo] = None -) -> Tuple[Optional[dt.datetime], Optional[StaticTzInfo]]: - # First try current_period to capture dates in the near future - dtime, used_timezone = parse_human_readable_datetime( - human_readable_datetime, - settings={"PREFER_DATES_FROM": "current_period"}, - user_timezone=user_timezone, - fallback_timezone=None, # Error if time zone can't be parsed - ) - # Can't parse into datetime, return early - if dtime is None: - return None, None - # If date is in the past, prefer future dates - if dtime < utcnow(): - dtime, used_timezone = parse_human_readable_datetime( - human_readable_datetime, - settings={"PREFER_DATES_FROM": "future"}, - user_timezone=user_timezone, - fallback_timezone=None, # Error if time zone can't be parsed - ) - return dtime, used_timezone - - -async def practice_impl( - *, guild_id: int, host: str, mention: str, start_time: str, user_id: int -): - if start_time.lower() in { - # Common mistakes: don't try to parse these into a datetime - "today", - "tomorrow", - "today edt", - "today est", - "today cdt", - "today cst", - "today mdt", - "today mst", - "today mdt", - "today mst", - "today pdt", - "today pst", - }: - logger.info(f"practice invoked with {start_time}. sending error message") - raise commands.errors.BadArgument(PRACTICE_ERROR) - logger.info(f"attempting to schedule new practice session: {start_time}") - human_readable_datetime, quoted = get_and_strip_quoted_text(start_time) - user_timezone = await store.get_user_timezone(user_id=user_id) - try: - dtime, used_timezone = parse_practice_time( - human_readable_datetime, user_timezone=user_timezone - ) - except NoTimeZoneError: - raise commands.errors.BadArgument( - f'⚠️Could not parse time zone from "{start_time}". Make sure to include a time zone, e.g. "{PACIFIC_CURRENT_NAME.lower()}".' - ) - except pytz.UnknownTimeZoneError: - raise commands.errors.BadArgument("⚠️Invalid time zone. Please try again.") - if not dtime: - raise commands.errors.BadArgument( - f'⚠️Could not parse "{start_time}" into a date or time. Make sure to include "am" or "pm" as well as a timezone, e.g. "{PACIFIC_CURRENT_NAME.lower()}".' - ) - assert used_timezone is not None - if dtime < utcnow(): - raise commands.errors.BadArgument( - "⚠Parsed date or time is in the past. Try again with a future date or time." - ) - notes = quoted or "" - dtime_local = dtime.astimezone(used_timezone) - display_dtime = " ".join( - ( - dtime_local.strftime("%A, %B %d %I:%M %p"), - display_timezone(used_timezone, dtime), - dtime_local.strftime("%Y"), - ) - ) - row = (display_dtime, host, mention, notes) - logger.info(f"adding new practice session to sheet: {row}") - worksheet = await get_practice_worksheet_for_guild(guild_id) - worksheet.append_row(row) - short_display_date = format_datetime(dtime) - sessions = await get_practice_sessions( - guild_id=guild_id, dtime=dtime, worksheet=worksheet - ) - embed = await make_practice_session_embed( - guild_id=guild_id, sessions=sessions, dtime=dtime - ) - if str(used_timezone) != str(user_timezone): - await store.set_user_timezone(user_id, used_timezone) - return { - "content": f"🙌 New practice scheduled for *{short_display_date}*", - "embed": embed, - # Return old and new timezone to send notification to user if they're different - "old_timezone": user_timezone, - "new_timezone": used_timezone, - } - - -TIMEZONE_CHANGE_TEMPLATE = """🙌 Thanks for scheduling a practice! I'll remember your time zone (**{new_timezone}**) so you don't need to include a time zone when scheduling future practices. -Before: `{COMMAND_PREFIX}practice tomorrow 8pm {new_timezone_display}` -After: `{COMMAND_PREFIX}practice tomorrow 8pm` -To change your time zone, just schedule another practice with a different time zone. -""" - - -class Practice(commands.Cog): - def __init__(self, bot: commands.Bot): - self.bot = bot - - @commands.command(name="practice", help=PRACTICE_HELP) - async def practice_command(self, ctx: Context, *, start_time: str): - await ctx.channel.trigger_typing() - bot = self.bot - - is_dm = not bool(ctx.guild) - if is_dm: - guild_ids_with_schedules = await store.get_guild_ids_with_practice_schedules() - members_and_guilds = [] - for guild_id in guild_ids_with_schedules: - guild = bot.get_guild(guild_id) - # Use fetch_member to check membership instead of get_member because cache might not be populated - try: - assert guild is not None - member = await guild.fetch_member(ctx.author.id) - except Exception: - pass - else: - members_and_guilds.append((member, guild)) - else: - assert ctx.guild is not None - has_practice_schedule = await store.guild_has_practice_schedule(ctx.guild.id) - if not has_practice_schedule: - raise commands.errors.CheckFailure( - "⚠️ No configured practice schedule for this server. If you think this is a mistake, contact the bot owner." - ) - members_and_guilds = [(ctx.author, ctx.guild)] - - dm_response = None - old_timezone, new_timezone = None, None - channel_id, channel = None, None - for member, guild in members_and_guilds: - assert guild is not None - guild_id = guild.id - ret = await practice_impl( - guild_id=guild_id, - host=display_name(member), - mention=member.mention, - start_time=start_time, - user_id=ctx.author.id, - ) - old_timezone = ret.pop("old_timezone") - new_timezone = ret.pop("new_timezone") - channel_id = await store.get_guild_daily_message_channel_id(guild_id) - if not channel_id: - continue - channel = bot.get_channel(channel_id) - if not channel: - continue - message = await channel.send(**ret) # type: ignore - with suppress(Exception): - await message.add_reaction("✅") - if is_dm: - if members_and_guilds: - dm_response = ( - "🙌 Thanks for scheduling a practice in the following servers:\n" - ) - for _, guild in members_and_guilds: - assert guild is not None - dm_response += f"*{guild.name}*\n" - else: - dm_response = ( - "⚠️ You are not a member of any servers that have a practice schedule." - ) - else: - if str(old_timezone) != str(new_timezone): - assert new_timezone is not None - new_timezone_display = display_timezone(new_timezone, utcnow()).lower() - dm_response = TIMEZONE_CHANGE_TEMPLATE.format( - new_timezone=new_timezone, - new_timezone_display=new_timezone_display, - COMMAND_PREFIX=COMMAND_PREFIX, - ) - # message sent outside of practice schedule channel - if channel_id and channel and ctx.channel.id != channel_id: - await ctx.channel.send(f"🙌 New practice posted in {channel.mention}.") # type: ignore - if dm_response: - try: - await ctx.author.send(dm_response) - except disnake.errors.Forbidden: - logger.warn("cannot send DM to user. skipping...") - - @commands.command(name="schedule", aliases=("sched", "practices"), help=SCHEDULE_HELP) - @commands.check(has_practice_schedule) - async def schedule_command(self, ctx: Context, *, when: Optional[str]): - await ctx.channel.trigger_typing() - assert ctx.guild is not None - ret = await schedule_impl(guild_id=ctx.guild.id, when=when) - await ctx.send(**ret) - - @practice_command.error - @schedule_command.error # type: ignore - async def practices_error(self, ctx: Context, error: Exception): - if isinstance(error, commands.errors.MissingRequiredArgument): - await ctx.send(PRACTICE_ERROR) - - -def setup(bot: commands.Bot) -> None: - bot.add_cog(Practice(bot)) diff --git a/requirements-dev.txt b/requirements-dev.txt index 718c1704..ab650dba 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,7 +4,7 @@ freezegun==1.4.0 pre-commit==3.6.2 PyJWT==2.8.0 pyright==0.0.13.post0 -pytest-asyncio==0.18.3 +pytest-asyncio==0.23.5.post1 python-semantic-release==7.34.6 SQLAlchemy-Utils==0.41.1 syrupy==3.0.6 diff --git a/tests/bot/exts/__snapshots__/test_practice.ambr b/tests/bot/exts/__snapshots__/test_practice.ambr deleted file mode 100644 index 13851ddb..00000000 --- a/tests/bot/exts/__snapshots__/test_practice.ambr +++ /dev/null @@ -1,168 +0,0 @@ -# name: test_practice["classifiers" at 6pm pdt] - tuple( - 'Friday, September 25 06:00 PM PDT 2020', - 'Steve', - '<@!12345>', - 'classifiers', - ) -# --- -# name: test_practice["games" 10am edt Sunday] - tuple( - 'Sunday, September 27 10:00 AM EDT 2020', - 'Steve', - '<@!12345>', - 'games', - ) -# --- -# name: test_practice["games" at 10am edt on 9/27] - tuple( - 'Sunday, September 27 10:00 AM EDT 2020', - 'Steve', - '<@!12345>', - 'games', - ) -# --- -# name: test_practice["games" at 10am edt on Sunday] - tuple( - 'Sunday, September 27 10:00 AM EDT 2020', - 'Steve', - '<@!12345>', - 'games', - ) -# --- -# name: test_practice[10am edt "games" on 9/27] - tuple( - 'Sunday, September 27 10:00 AM EDT 2020', - 'Steve', - '<@!12345>', - 'games', - ) -# --- -# name: test_practice[2pm edt "chat and games! \U0001f389"] - tuple( - 'Friday, September 25 02:00 PM EDT 2020', - 'Steve', - '<@!12345>', - 'chat and games! 🎉', - ) -# --- -# name: test_practice[2pm edt] - tuple( - 'Friday, September 25 02:00 PM EDT 2020', - 'Steve', - '<@!12345>', - '', - ) -# --- -# name: test_practice[9/24 1:45pm edt \u201caround 45 min.-1 hour\u201d] - tuple( - 'Friday, September 24 01:45 PM EDT 2021', - 'Steve', - '<@!12345>', - 'around 45 min.-1 hour', - ) -# --- -# name: test_practice[9/27 9am cdt] - tuple( - 'Sunday, September 27 09:00 AM CDT 2020', - 'Steve', - '<@!12345>', - '', - ) -# --- -# name: test_practice[at 2pm edt] - tuple( - 'Friday, September 25 02:00 PM EDT 2020', - 'Steve', - '<@!12345>', - '', - ) -# --- -# name: test_practice[sunday 10:30pm pdt] - tuple( - 'Sunday, September 27 10:30 PM PDT 2020', - 'Steve', - '<@!12345>', - '', - ) -# --- -# name: test_practice[tomorrow 2pm pdt] - tuple( - 'Saturday, September 26 02:00 PM PDT 2020', - 'Steve', - '<@!12345>', - '', - ) -# --- -# name: test_schedule[None] - dict({ - 'embed': Embed( - Empty=None, - author=EmbedProxy(), - color=, - colour=, - description='Today - Friday, September 25', - fields=list([ - EmbedProxy(inline=False, name='', value='[+Google Calendar](http://www.google.com/calendar/event?action=TEMPLATE&text=ASL+Practice%3A+recurring&dates=20200925T210000Z%2F20200925T220000Z&details=See+the+full+schedule+here%3A+https%3A%2F%2Fdocs.google.com%2Fspreadsheets%2Fd%2Fabc%2Fedit)\n> Host: <@!12345>\n> Details: recurring'), - EmbedProxy(inline=True, name='🗓 View or edit the schedule using the link below.', value='[Full schedule](https://docs.google.com/spreadsheets/d/abc/edit)'), - ]), - footer=EmbedProxy(), - image=EmbedProxy(), - provider=EmbedProxy(), - thumbnail=EmbedProxy(), - timestamp=None, - title=None, - type='rich', - url=None, - video=EmbedProxy(), - ), - }) -# --- -# name: test_schedule[today] - dict({ - 'embed': Embed( - Empty=None, - author=EmbedProxy(), - color=, - colour=, - description='Today - Friday, September 25', - fields=list([ - EmbedProxy(inline=False, name='', value='[+Google Calendar](http://www.google.com/calendar/event?action=TEMPLATE&text=ASL+Practice%3A+recurring&dates=20200925T210000Z%2F20200925T220000Z&details=See+the+full+schedule+here%3A+https%3A%2F%2Fdocs.google.com%2Fspreadsheets%2Fd%2Fabc%2Fedit)\n> Host: <@!12345>\n> Details: recurring'), - EmbedProxy(inline=True, name='🗓 View or edit the schedule using the link below.', value='[Full schedule](https://docs.google.com/spreadsheets/d/abc/edit)'), - ]), - footer=EmbedProxy(), - image=EmbedProxy(), - provider=EmbedProxy(), - thumbnail=EmbedProxy(), - timestamp=None, - title=None, - type='rich', - url=None, - video=EmbedProxy(), - ), - }) -# --- -# name: test_schedule_on_a_holiday - dict({ - 'embed': Embed( - Empty=None, - author=EmbedProxy(), - color=, - colour=, - description='Today - Thursday, November 26 🦃', - fields=list([ - EmbedProxy(inline=False, name='', value='[+Google Calendar](http://www.google.com/calendar/event?action=TEMPLATE&text=ASL+Practice%3A+Turkey+day+PRACTICE&dates=20201126T190000Z%2F20201126T200000Z&details=See+the+full+schedule+here%3A+https%3A%2F%2Fdocs.google.com%2Fspreadsheets%2Fd%2Fabc%2Fedit)\n> Details: Turkey day PRACTICE'), - EmbedProxy(inline=True, name='🗓 View or edit the schedule using the link below.', value='[Full schedule](https://docs.google.com/spreadsheets/d/abc/edit)'), - ]), - footer=EmbedProxy(), - image=EmbedProxy(), - provider=EmbedProxy(), - thumbnail=EmbedProxy(), - timestamp=None, - title=None, - type='rich', - url=None, - video=EmbedProxy(), - ), - }) -# --- diff --git a/tests/bot/exts/test_practice.py b/tests/bot/exts/test_practice.py deleted file mode 100644 index c01bd957..00000000 --- a/tests/bot/exts/test_practice.py +++ /dev/null @@ -1,202 +0,0 @@ -import os -import random -from unittest import mock - -import gspread -import pytest -import pytz -from asynctest import patch -from disnake.ext import commands -from freezegun import freeze_time - -# Must be before bot import -os.environ["TESTING"] = "true" - -from bot import database, settings # noqa:E402 -from bot.exts.practices import practice # noqa:E402 - -random.seed(1) - - -@pytest.fixture -async def mock_worksheet(monkeypatch, db): - monkeypatch.setattr(settings, "GOOGLE_PRIVATE_KEY", "fake", raising=True) - await db.execute( - database.guild_settings.insert(), {"guild_id": 1234, "schedule_sheet_key": "abc"} - ) - # Need to mock both of these since they get called in different files - with patch( - "bot.exts.practices.practice.get_practice_worksheet_for_guild" - ) as mock_get_worksheet, patch( - "bot.exts.practices._practice_sessions.get_practice_worksheet_for_guild" - ) as mock_get_worksheet2: - WorksheetMock = mock.Mock(spec=gspread.Worksheet) - WorksheetMock.get_all_values.return_value = [ - ["docs", "more docs", ""], - ["Start time", "Host (optional)", "Details (optional)", ""], - ["Friday 5pm EDT", "Steve", "<@!12345>", "recurring", ""], - ["Friday 6pm EDT", "Steve", "<@!12345>", "paused", "x"], - ["Wed 6pm edt", "", "", "another recurring", ""], - ["9/26 2pm PDT 2020", "Steve", "<@!12345>", "one time", ""], - ["Sunday, September 27 02:00 PM EDT 2020", "", "", "another 1️⃣", ""], - ["Nov 26 02:00 PM EDT 2020", "", "", "Turkey day PRACTICE", ""], - ] - mock_get_worksheet.return_value = mock_get_worksheet2.return_value = WorksheetMock - yield WorksheetMock - - -@pytest.mark.parametrize( - "when", - ( - None, - "today", - # FIXME: below fail locally but pass in CI - # "tomorrow", - # "saturday", - # "9/27", - ), -) -@pytest.mark.asyncio -@freeze_time("2020-09-25 14:00:00") -async def test_schedule(snapshot, mock_worksheet, store, when): - result = await practice.schedule_impl(1234, when) - assert result == snapshot - - -@pytest.mark.asyncio -@freeze_time("2020-11-26 14:00:00") -async def test_schedule_on_a_holiday(snapshot, mock_worksheet, store): - result = await practice.schedule_impl(1234, None) - assert result == snapshot - - -# FIXME: This test fails locally but passes in CI -# @pytest.mark.asyncio -# @freeze_time("2020-09-25 14:00:00") -# async def test_schedule_no_practices(snapshot, mock_worksheet): -# result = await practice.schedule_impl(1234, "9/28/2020") -# embed = result["embed"] -# assert "September 28" in embed.description -# assert "There are no scheduled practices today" in embed.description -# assert ( -# "To schedule a practice, edit the schedule below or use the" in embed.description -# ) - - -@pytest.mark.parametrize( - "start_time", - ( - "2pm edt", - "at 2pm edt", - '2pm edt "chat and games! 🎉"', - "9/24 1:45pm edt “around 45 min.-1 hour”", - "tomorrow 2pm pdt", - "sunday 10:30pm pdt", - "9/27 9am cdt", - '"games" 10am edt Sunday', - '"games" at 10am edt on Sunday', - '"games" at 10am edt on 9/27', - '10am edt "games" on 9/27', - '"classifiers" at 6pm pdt', - ), -) -@freeze_time("2020-09-25 14:00:00") -@pytest.mark.asyncio -async def test_practice(snapshot, mock_worksheet, start_time, store): - await practice.practice_impl( - guild_id=1234, - host="Steve", - mention="<@!12345>", - start_time=start_time, - user_id=12345679, - ) - mock_worksheet.append_row.assert_called_once() - assert mock_worksheet.append_row.call_args[0][0] == snapshot - - -@pytest.mark.asyncio -async def test_practice_caches_timezone(mock_worksheet, store): - await practice.practice_impl( - guild_id=1234, - host="Steve", - mention="<@!12345>", - start_time="tomorrow 8pm est", - user_id=123, - ) - timezone = await store.get_user_timezone(123) - assert timezone == pytz.timezone("America/New_York") - - -@pytest.mark.asyncio -async def test_practice_errors_if_time_zone_cannot_be_parsed(mock_worksheet, store): - with pytest.raises(commands.errors.BadArgument, match="Could not parse time zone"): - await practice.practice_impl( - guild_id=1234, - host="Steve", - mention="<@!12345>", - start_time="tomorrow 8pm", - user_id=321, - ) - - -@pytest.mark.asyncio -async def test_practice_nearby_date(snapshot, mock_worksheet, store): - with freeze_time("2020-09-25 14:00:00"): - await practice.practice_impl( - guild_id=1234, - host="Steve", - mention="<@!12345>", - start_time="10:30am edt", - user_id=123, - ) - - mock_worksheet.append_row.assert_called_once() - appended_row = mock_worksheet.append_row.call_args[0][0] - assert appended_row == ( - "Friday, September 25 10:30 AM EDT 2020", - "Steve", - "<@!12345>", - "", - ) - - -@pytest.mark.parametrize( - "start_time", - ( - "today", - "tomorrow", - "today edt", - "today pdt", - ), -) -@freeze_time("2020-09-25 14:00:00") -@pytest.mark.asyncio -async def test_practice_common_mistakes(snapshot, mock_worksheet, start_time): - with pytest.raises( - commands.errors.CommandError, match="️To schedule a practice, enter a time" - ): - await practice.practice_impl( - guild_id=1234, - host="Steve", - mention="<@!12345>", - start_time=start_time, - user_id=123, - ) - mock_worksheet.append_row.assert_not_called() - - -@freeze_time("2020-09-25 14:00:00") -@pytest.mark.asyncio -async def test_practice_invalid(mock_worksheet, store): - with pytest.raises( - commands.errors.CommandError, - match='Could not parse "invalid" into a date or time. Make sure to include "am" or "pm" as well as a timezone', - ): - await practice.practice_impl( - guild_id=1234, - host="Steve", - mention="<@!12345>", - start_time="invalid", - user_id=123, - ) - mock_worksheet.append_row.assert_not_called() diff --git a/tests/conftest.py b/tests/conftest.py index ea6d8319..731aea9c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -33,5 +33,5 @@ async def store(create_test_database): @pytest.fixture -def db(store): +async def db(store): return store.db