Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dqb 28 choose meal #41

Merged
merged 6 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
753 changes: 411 additions & 342 deletions poetry.lock

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ module = "sqlalchemy.*"
ignore_missing_imports = true
module = "furl"

[[tool.mypy.overrides]]
ignore_missing_imports = true
module = "factory.*"

[tool.poetry]
authors = ["jplhanna <[email protected]>"]
description = "Discord bot for tracking and rewarding tasks"
Expand All @@ -73,6 +77,7 @@ typing-extensions = "^4.9.0"
bandit = {extras = ["toml"], version = "^1.7.4"}
black = "*"
coverage = {extras = ["toml"], version = "^6.4.4"}
factory-boy = "^3.3.0"
faker = "*"
flake8-pytest-style = "^1.6.0"
mypy = "*"
Expand All @@ -81,6 +86,8 @@ pytest = "<=7.4.4"
pytest-async-sqlalchemy = "*"
pytest-asyncio = "*"
pytest-cov = "*"
pytest-factoryboy = "^2.7.0"
pytest-mock = "^3.12.0"
pytest-randomly = "*"
ruff = "^0.2.0"

Expand Down Expand Up @@ -132,4 +139,5 @@ lines-between-types = 1
max-complexity = 10

[tool.ruff.lint.per-file-ignores]
"src/helpers/factories/*" = ["FBT001"]
"src/model_hub.py" = ["F401"]
15 changes: 15 additions & 0 deletions src/bot/commands.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from datetime import date
from typing import cast

from discord import Guild
from discord import Intents
from discord.ext.commands import Bot
from discord.ext.commands import Context
Expand All @@ -12,7 +16,9 @@
from src.bot.controllers import get_quest_list_text
from src.bot.controllers import get_tavern_menu
from src.bot.controllers import remove_from_tavern_menu
from src.bot.controllers import select_from_tavern_menu
from src.bot.controllers import upsert_tavern_menu
from src.bot.typeshed import RandomChoiceFlag
from src.config import DISCORD_OWNER_ID
from src.constants import DayOfWeek

Expand Down Expand Up @@ -91,3 +97,12 @@ async def tavern_menu_add(ctx: Context, *, day_of_week: DayOfWeek, menu_item: st
async def tavern_menu_remove(ctx: Context, *, menu_item: str, day_of_week: DayOfWeek | None) -> None:
res = await remove_from_tavern_menu(ctx, menu_item, day_of_week)
await ctx.send(res)


@tavern_menu.command(name="choose")
@guild_only()
async def tavern_menu_choose(ctx: Context, *, flags: RandomChoiceFlag) -> None:
if not flags.day_of_week:
flags.day_of_week = DayOfWeek(date.today().weekday())
res = await select_from_tavern_menu(cast(Guild, ctx.guild), flags.style, flags.day_of_week)
await ctx.send(res)
1 change: 1 addition & 0 deletions src/bot/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
REGISTER_FIRST_MESSAGE = "Please register first"
NO_MENU_THIS_WEEK_MESSAGE = "No menu has been created for this week."
SERVER_ONLY_BAD_REQUEST_MESSAGE = "Bad request, this feature is only available in servers."
NO_MENU_ITEMS_FOR_CHOSEN_DAY_MESSAGE = "No items to select for the day you asked."
23 changes: 23 additions & 0 deletions src/bot/controllers.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import random

from dependency_injector.wiring import Provide
from dependency_injector.wiring import inject
from discord import Guild
from discord.ext.commands import Context

from src.bot.constants import ALREADY_REGISTERED_MESSAGE
from src.bot.constants import NEW_USER_MESSAGE
from src.bot.constants import NO_MENU_ITEMS_FOR_CHOSEN_DAY_MESSAGE
from src.bot.constants import NO_MENU_THIS_WEEK_MESSAGE
from src.bot.constants import REGISTER_FIRST_MESSAGE
from src.bot.constants import SERVER_ONLY_BAD_REQUEST_MESSAGE
from src.constants import ChooseStyle
from src.constants import DayOfWeek
from src.containers import Container
from src.exceptions import NoIDProvided
Expand Down Expand Up @@ -129,3 +134,21 @@ async def remove_from_tavern_menu(
return f"{item_name_str.capitalize()} could not be found{day_of_week_error_text} in this week's menu."

return "Item successfully removed"


@inject
async def select_from_tavern_menu(
guild: Guild,
style: ChooseStyle,
day_of_week: DayOfWeek,
tavern_service: TavernService = Provide[Container.tavern_service],
) -> str:
menu = await tavern_service.get_this_weeks_menu(guild.id)
if not menu:
return NO_MENU_THIS_WEEK_MESSAGE
if not (food_items := menu.grouped_items.get(day_of_week)):
return NO_MENU_ITEMS_FOR_CHOSEN_DAY_MESSAGE
food_text = food_items[0].food.title()
if style == ChooseStyle.RANDOM:
food_text = random.choice(food_items).food.title() # nosec
return f"Order Up!\n{food_text}"
12 changes: 12 additions & 0 deletions src/bot/typeshed.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
from collections.abc import Callable
from collections.abc import Coroutine
from datetime import date
from typing import TYPE_CHECKING
from typing import Concatenate
from typing import ParamSpec

from discord.ext.commands import FlagConverter
from discord.ext.commands import flag

from src.constants import ChooseStyle
from src.constants import DayOfWeek

if TYPE_CHECKING:
from discord.ext.commands import Context
from discord.ext.commands import HybridCommand
Expand All @@ -22,3 +29,8 @@
]
else:
CommandRegisterType = Callable


class RandomChoiceFlag(FlagConverter):
style: ChooseStyle = ChooseStyle.RANDOM
day_of_week: DayOfWeek = flag(default=lambda _: DayOfWeek(date.today().weekday()))
26 changes: 20 additions & 6 deletions src/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@
from copy import copy
from unittest.mock import AsyncMock
from unittest.mock import MagicMock
from unittest.mock import Mock
from unittest.mock import sentinel

import pytest

from pytest_factoryboy import register
from sqlalchemy import create_engine
from sqlalchemy import inspect
from sqlalchemy.ext.asyncio import async_sessionmaker
from sqlmodel.ext.asyncio.session import AsyncSession

from src.config import Settings
from src.containers import Container
from src.helpers.factories import factory_classes
from src.helpers.factories.base_factories import test_session
from src.helpers.sqlalchemy_helpers import BaseModel
from src.models import User
from src.repositories import BaseRepository
Expand All @@ -28,6 +31,13 @@ def test_config_obj():
return Settings()


@pytest.fixture(scope="session", autouse=True)
def setup_factory_session(test_config_obj):
engine = create_engine(test_config_obj.db.database_uri)
test_session.configure(bind=engine)
return test_session


@pytest.fixture()
def mock_container() -> Generator[Container, None, None]:
mocked_container = copy(base_mock_container)
Expand All @@ -37,14 +47,14 @@ def mock_container() -> Generator[Container, None, None]:
mocked_container.unwire()


@pytest.fixture(scope="session")
def mocked_user() -> User:
return Mock(spec=User, discord_id=sentinel.discord_id, id=sentinel.user_id, _sa_instance_state=MagicMock())
@pytest.fixture()
def mocked_guild() -> MagicMock:
return MagicMock(id=sentinel.guild_id)


@pytest.fixture()
def mocked_ctx() -> MagicMock:
return MagicMock(author=MagicMock(id=sentinel.discord_id), guild=MagicMock(id=sentinel.guild_id))
def mocked_ctx(mocked_guild) -> MagicMock:
return MagicMock(author=MagicMock(id=sentinel.discord_id), guild=mocked_guild)


@pytest.fixture()
Expand Down Expand Up @@ -114,3 +124,7 @@ async def db_user(db_session):
if not inspect(user).was_deleted:
await db_session.delete(user)
await db_session.flush()


for cls in factory_classes:
register(cls)
7 changes: 7 additions & 0 deletions src/constants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Message Strings
from enum import Enum
from enum import IntEnum
from enum import auto

GOOD_LUCK_ADVENTURER = "You have accepted {}! Good luck adventurer"
QUEST_ALREADY_ACCEPTED = "You have already accepted this request"
Expand All @@ -15,3 +17,8 @@ class DayOfWeek(IntEnum):
THURSDAY = 5
FRIDAY = 6
SATURDAY = 7


class ChooseStyle(Enum):
RANDOM = auto()
FIRST = auto()
4 changes: 2 additions & 2 deletions src/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ def __init__(self, db_url: str) -> None:
)

async def create_database(self) -> None:
current_session = self.get_session()
await current_session.run_sync(BaseModel.metadata.create_all)
async with self._async_engine.begin() as conn:
await conn.run_sync(BaseModel.metadata.create_all)

def get_session(self) -> AsyncSession:
return self._session_factory()
Expand Down
10 changes: 10 additions & 0 deletions src/helpers/factories/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import inspect
import sys

from .base_factories import BaseFactory
from .quest_factories import * # noqa: F403
from .tavern_factories import * # noqa: F403
from .user_factories import * # noqa: F403

fact_classes = inspect.getmembers(sys.modules[__name__], lambda x: inspect.isclass(x) and issubclass(x, BaseFactory))
factory_classes = [cls for cls_name, cls in fact_classes if cls_name != "BaseFactory"]
15 changes: 15 additions & 0 deletions src/helpers/factories/base_factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from factory.alchemy import SQLAlchemyModelFactory
from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker

from src.config import DBSettings

db_settings = DBSettings()

test_session = scoped_session(sessionmaker())


class BaseFactory(SQLAlchemyModelFactory):
class Meta:
abstract = True
sqlalchemy_session = test_session
47 changes: 47 additions & 0 deletions src/helpers/factories/quest_factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from typing import Any

import factory

from pytest_factoryboy import register

from src.helpers.factories.base_factories import BaseFactory
from src.helpers.factories.user_factories import UserFactory
from src.quests import ExperienceTransaction
from src.quests import Quest
from src.quests import UserQuest


@register
class QuestFactory(BaseFactory):
id = factory.Sequence(lambda n: n + 1)

class Meta:
model = Quest

name = "Test Quest"
experience = 100


@register
class UserQuestFactory(BaseFactory):
class Meta:
model = UserQuest

date_completed = factory.Maybe("is_completed", yes_declaration=factory.Faker("date_between", start_date="-10d"))

user = factory.SubFactory(UserFactory)
quest = factory.SubFactory(QuestFactory)

@factory.post_generation
def append_to_quests(self, _create: bool, _extracted: Any, **_kwargs: Any) -> None:
self.user.quests.append(self.quest)


@register
class ExperienceTransactionFactory(BaseFactory):
class Meta:
model = ExperienceTransaction

quest = factory.SubFactory(QuestFactory)
experience = factory.SelfAttribute("quest.experience")
user = factory.SubFactory(UserFactory)
30 changes: 30 additions & 0 deletions src/helpers/factories/tavern_factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from unittest.mock import sentinel

import factory

from pytest_factoryboy import register

from src.constants import DayOfWeek
from src.helpers.factories.base_factories import BaseFactory
from src.tavern import Menu
from src.tavern.models import MenuItem


@register
class MenuItemFactory(BaseFactory):
class Meta:
model = MenuItem

food = factory.Sequence(lambda n: f"Test Food {n}")
day_of_the_week = factory.Iterator(DayOfWeek)
menu = None


@register
class MenuFactory(BaseFactory):
class Meta:
model = Menu

server_id = sentinel.guild_id
start_date = factory.Faker("date_object")
items = factory.RelatedFactoryList(MenuItemFactory, factory_related_name="menu")
16 changes: 16 additions & 0 deletions src/helpers/factories/user_factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import factory

from pytest_factoryboy import register

from src.helpers.factories.base_factories import BaseFactory
from src.models import User


@register
class UserFactory(BaseFactory):
id = factory.Sequence(lambda n: n + 1)

class Meta:
model = User

discord_id = factory.Faker("random_number", digits=10)
Loading