Start refactoring bot, add /remind add command

This commit is contained in:
2025-01-03 15:03:47 +01:00
parent 2e30cafbac
commit c8b6dbfe41
19 changed files with 279 additions and 2774 deletions

View File

@ -1,108 +0,0 @@
from datetime import datetime
import dateparser
import pytz
from apscheduler.job import Job
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from discord_reminder_bot.main import send_to_discord
class TestCountdown:
"""This tests everything.
This sets up sqlite database in memory, changes scheduler timezone
to Europe/Stockholm and creates job that runs January 18 2040 and one that
runs at 00:00.
"""
jobstores: dict[str, SQLAlchemyJobStore] = {"default": SQLAlchemyJobStore(url="sqlite:///:memory")}
job_defaults: dict[str, bool] = {"coalesce": True}
scheduler = AsyncIOScheduler(
jobstores=jobstores,
timezone=pytz.timezone("Europe/Stockholm"),
job_defaults=job_defaults,
)
parsed_date: datetime | None = dateparser.parse(
"18 January 2040",
settings={
"PREFER_DATES_FROM": "future",
"TO_TIMEZONE": "Europe/Stockholm",
},
)
assert parsed_date
run_date: str = parsed_date.strftime("%Y-%m-%d %H:%M:%S")
job: Job = scheduler.add_job(
send_to_discord,
run_date=run_date,
kwargs={
"channel_id": 865712621109772329,
"message": "Running PyTest",
"author_id": 126462229892694018,
},
)
timezone_date: datetime | None = dateparser.parse(
"00:00",
settings={
"PREFER_DATES_FROM": "future",
"TIMEZONE": "Europe/Stockholm",
"TO_TIMEZONE": "Europe/Stockholm",
},
)
assert timezone_date
timezone_run_date: str = timezone_date.strftime("%Y-%m-%d %H:%M:%S")
timezone_job: Job = scheduler.add_job(
send_to_discord,
run_date=timezone_run_date,
kwargs={
"channel_id": 865712621109772329,
"message": "Running PyTest at 00:00",
"author_id": 126462229892694018,
},
)
timezone_date2: datetime | None = dateparser.parse(
"13:37",
settings={
"PREFER_DATES_FROM": "future",
"TIMEZONE": "Europe/Stockholm",
"TO_TIMEZONE": "Europe/Stockholm",
},
)
assert timezone_date2
timezone_run_date2: str = timezone_date2.strftime("%Y-%m-%d %H:%M:%S")
timezone_job2: Job = scheduler.add_job(
send_to_discord,
run_date=timezone_run_date2,
kwargs={
"channel_id": 865712621109772329,
"message": "Running PyTest at 13:37",
"author_id": 126462229892694018,
},
)
def test_if_timezones_are_working(self) -> None: # noqa: ANN101
"""Check if timezones are working.
Args:
self: TestCountdown
"""
time_job: Job | None = self.scheduler.get_job(self.timezone_job.id)
assert time_job
assert time_job.trigger.run_date.hour == 0
assert time_job.trigger.run_date.minute == 0
assert time_job.trigger.run_date.second == 0
time_job2: Job | None = self.scheduler.get_job(self.timezone_job2.id)
assert time_job2
assert time_job2.trigger.run_date.hour == 13 # noqa: PLR2004
assert time_job2.trigger.run_date.minute == 37 # noqa: PLR2004
assert time_job2.trigger.run_date.second == 0

View File

@ -1,192 +0,0 @@
import re
from datetime import datetime
from typing import TYPE_CHECKING
import dateparser
import interactions
import pytz
from apscheduler.job import Job
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from interactions.ext.paginator import Page
from discord_reminder_bot.create_pages import (
_get_pages,
_get_pause_or_unpause_button,
_get_row_of_buttons,
_get_trigger_text,
_make_button,
_pause_job,
_unpause_job,
)
from discord_reminder_bot.main import send_to_discord
if TYPE_CHECKING:
from collections.abc import Generator
def _test_pause_unpause_button(job: Job, button_label: str) -> None:
button2: interactions.Button | None = _get_pause_or_unpause_button(job)
assert button2
assert button2.label == button_label
assert button2.style == interactions.ButtonStyle.PRIMARY
assert button2.type == interactions.ComponentType.BUTTON
assert button2.emoji is None
assert button2.custom_id == button_label.lower()
assert button2.url is None
assert button2.disabled is None
class TestCountdown:
jobstores: dict[str, SQLAlchemyJobStore] = {"default": SQLAlchemyJobStore(url="sqlite:///:memory")}
job_defaults: dict[str, bool] = {"coalesce": True}
scheduler = AsyncIOScheduler(
jobstores=jobstores,
timezone=pytz.timezone("Europe/Stockholm"),
job_defaults=job_defaults,
)
parsed_date: datetime | None = dateparser.parse(
"18 January 2040",
settings={
"PREFER_DATES_FROM": "future",
"TO_TIMEZONE": "Europe/Stockholm",
},
)
assert parsed_date
run_date: str = parsed_date.strftime("%Y-%m-%d %H:%M:%S")
normal_job: Job = scheduler.add_job(
send_to_discord,
run_date=run_date,
kwargs={
"channel_id": 865712621109772329,
"message": "Running PyTest",
"author_id": 126462229892694018,
},
)
cron_job: Job = scheduler.add_job(
send_to_discord,
"cron",
minute="0",
kwargs={
"channel_id": 865712621109772329,
"message": "Running PyTest",
"author_id": 126462229892694018,
},
)
interval_job: Job = scheduler.add_job(
send_to_discord,
"interval",
minutes=1,
kwargs={
"channel_id": 865712621109772329,
"message": "Running PyTest",
"author_id": 126462229892694018,
},
)
def test_get_trigger_text(self) -> None: # noqa: ANN101
# FIXME: This try except train should be replaced with a better solution lol
trigger_text: str = _get_trigger_text(self.normal_job)
try:
regex: str = r"2040-01-18 \d+:00 \(in \d+ days, \d+ hours, \d+ minutes\)"
assert re.match(regex, trigger_text)
except AssertionError:
try:
regex2: str = r"2040-01-18 \d+:00 \(in \d+ days, \d+ minutes\)"
assert re.match(regex2, trigger_text)
except AssertionError:
regex3: str = r"2040-01-18 \d+:00 \(in \d+ days, \d+ hours\)"
assert re.match(regex3, trigger_text)
def test_make_button(self) -> None: # noqa: ANN101
button_name: str = "Test"
button: interactions.Button = _make_button(label=button_name, style=interactions.ButtonStyle.PRIMARY)
assert button.label == button_name
assert button.style == interactions.ButtonStyle.PRIMARY
assert button.custom_id == button_name.lower()
assert button.disabled is None
assert button.emoji is None
def test_get_pause_or_unpause_button(self) -> None: # noqa: ANN101
button: interactions.Button | None = _get_pause_or_unpause_button(self.normal_job)
assert button is None
_test_pause_unpause_button(self.cron_job, "Pause")
self.cron_job.pause()
_test_pause_unpause_button(self.cron_job, "Unpause")
self.cron_job.resume()
_test_pause_unpause_button(self.interval_job, "Pause")
self.interval_job.pause()
_test_pause_unpause_button(self.interval_job, "Unpause")
self.interval_job.resume()
def test_get_row_of_buttons(self) -> None: # noqa: ANN101
row: interactions.ActionRow = _get_row_of_buttons(self.normal_job)
assert row
assert row.components
# A normal job should have 2 buttons, edit and delete
assert len(row.components) == 2 # noqa: PLR2004
row2: interactions.ActionRow = _get_row_of_buttons(self.cron_job)
assert row2
assert row2.components
# A cron job should have 3 buttons, edit, delete and pause/unpause
assert len(row2.components) == 3 # noqa: PLR2004
# A cron job should have 3 buttons, edit, delete and pause/unpause
assert len(row2.components) == 3 # noqa: PLR2004
def test_get_pages(self) -> None: # noqa: ANN101
ctx = None # TODO: We should check ctx as well and not only channel id
channel: interactions.Channel = interactions.Channel(id=interactions.Snowflake(865712621109772329))
pages: Generator[Page, None, None] = _get_pages(job=self.normal_job, channel=channel, ctx=ctx) # type: ignore # noqa: PGH003, E501
assert pages
for page in pages:
assert page
assert page.title == "Running PyTest"
assert page.components
assert page.embeds
assert page.embeds.fields is not None # type: ignore # noqa: PGH003
assert page.embeds.fields[0].name == "**Channel:**" # type: ignore # noqa: PGH003
assert page.embeds.fields[0].value == "#" # type: ignore # noqa: PGH003
assert page.embeds.fields[1].name == "**Message:**" # type: ignore # noqa: PGH003
assert page.embeds.fields[1].value == "Running PyTest" # type: ignore # noqa: PGH003
assert page.embeds.fields[2].name == "**Trigger:**" # type: ignore # noqa: PGH003
trigger_text: str = page.embeds.fields[2].value # type: ignore # noqa: PGH003
# FIXME: This try except train should be replaced with a better solution lol
try:
regex: str = r"2040-01-18 \d+:00 \(in \d+ days, \d+ hours, \d+ minutes\)"
assert re.match(regex, trigger_text)
except AssertionError:
try:
regex2: str = r"2040-01-18 \d+:00 \(in \d+ days, \d+ minutes\)"
assert re.match(regex2, trigger_text)
except AssertionError:
regex3: str = r"2040-01-18 \d+:00 \(in \d+ days, \d+ hours\)"
assert re.match(regex3, trigger_text)
# Check if type is Page
assert isinstance(page, Page)
def test_pause_job(self) -> None: # noqa: ANN101
assert _pause_job(self.interval_job, self.scheduler) == f"Job {self.interval_job.id} paused."
assert _pause_job(self.cron_job, self.scheduler) == f"Job {self.cron_job.id} paused."
assert _pause_job(self.normal_job, self.scheduler) == f"Job {self.normal_job.id} paused."
def test_unpause_job(self) -> None: # noqa: ANN101
assert _unpause_job(self.interval_job, self.scheduler) == f"Job {self.interval_job.id} unpaused."
assert _unpause_job(self.cron_job, self.scheduler) == f"Job {self.cron_job.id} unpaused."
assert _unpause_job(self.normal_job, self.scheduler) == f"Job {self.normal_job.id} unpaused."

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from discord_reminder_bot import main

View File

@ -1,68 +0,0 @@
from datetime import datetime
import tzlocal
from discord_reminder_bot.parse import ParsedTime, parse_time
def test_parse_time() -> None:
"""Test the parse_time function."""
parsed_time: ParsedTime = parse_time("18 January 2040")
assert parsed_time.err is False
assert not parsed_time.err_msg
assert parsed_time.date_to_parse == "18 January 2040"
assert parsed_time.parsed_time
assert parsed_time.parsed_time.strftime("%Y-%m-%d %H:%M:%S") == "2040-01-18 00:00:00"
parsed_time: ParsedTime = parse_time("18 January 2040 12:00")
assert parsed_time.err is False
assert not parsed_time.err_msg
assert parsed_time.date_to_parse == "18 January 2040 12:00"
assert parsed_time.parsed_time
assert parsed_time.parsed_time.strftime("%Y-%m-%d %H:%M:%S") == "2040-01-18 12:00:00"
parsed_time: ParsedTime = parse_time("18 January 2040 12:00:00")
assert parsed_time.err is False
assert not parsed_time.err_msg
assert parsed_time.date_to_parse == "18 January 2040 12:00:00"
assert parsed_time.parsed_time
assert parsed_time.parsed_time.strftime("%Y-%m-%d %H:%M:%S") == "2040-01-18 12:00:00"
parsed_time: ParsedTime = parse_time("18 January 2040 12:00:00 UTC")
assert parsed_time.err is False
assert not parsed_time.err_msg
assert parsed_time.date_to_parse == "18 January 2040 12:00:00 UTC"
assert parsed_time.parsed_time
assert parsed_time.parsed_time.strftime("%Y-%m-%d %H:%M:%S") == "2040-01-18 13:00:00"
parsed_time: ParsedTime = parse_time("18 January 2040 12:00:00 Europe/Stockholm")
assert parsed_time.err is True
assert parsed_time.err_msg == "Could not parse the date."
assert parsed_time.date_to_parse == "18 January 2040 12:00:00 Europe/Stockholm"
assert parsed_time.parsed_time is None
def test_ParsedTime() -> None: # noqa: N802
"""Test the ParsedTime class."""
parsed_time: ParsedTime = ParsedTime(
err=False,
err_msg="",
date_to_parse="18 January 2040",
parsed_time=datetime(2040, 1, 18, 0, 0, 0, tzinfo=tzlocal.get_localzone()),
)
assert parsed_time.err is False
assert not parsed_time.err_msg
assert parsed_time.date_to_parse == "18 January 2040"
assert parsed_time.parsed_time
assert parsed_time.parsed_time.strftime("%Y-%m-%d %H:%M:%S") == "2040-01-18 00:00:00"
parsed_time: ParsedTime = ParsedTime(
err=True,
err_msg="Could not parse the date.",
date_to_parse="18 January 2040 12:00:00 Europe/Stockholm",
parsed_time=None,
)
assert parsed_time.err is True
assert parsed_time.err_msg == "Could not parse the date."
assert parsed_time.date_to_parse == "18 January 2040 12:00:00 Europe/Stockholm"
assert parsed_time.parsed_time is None