Compare commits

..

No commits in common. "v0.3.5" and "v0.3.3" have entirely different histories.

14 changed files with 688 additions and 980 deletions

View file

@ -1 +1 @@
3.13 3.11

View file

@ -1,6 +1,6 @@
apiVersion: v2 apiVersion: v2
appVersion: 0.3.5 appVersion: 0.3.3
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
name: huesoporro name: huesoporro
type: application type: application
version: 0.3.5 version: 0.3.3

View file

@ -8,7 +8,7 @@ fullnameOverride: ''
image: image:
pullPolicy: Always pullPolicy: Always
repository: git.roboces.dev/catalin/huesoporro repository: git.roboces.dev/catalin/huesoporro
tag: 0.3.5 tag: 0.3.3
imagePullSecrets: [] imagePullSecrets: []
ingress: ingress:
annotations: {} annotations: {}

View file

@ -19,10 +19,10 @@
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1747046372, "lastModified": 1733328505,
"owner": "edolstra", "owner": "edolstra",
"repo": "flake-compat", "repo": "flake-compat",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -46,31 +46,10 @@
"type": "github" "type": "github"
} }
}, },
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1747372754,
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "80479b6ec16fefd9c1db3ea13aeb038c60530f46",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": { "gitignore": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
"git-hooks", "pre-commit-hooks",
"nixpkgs" "nixpkgs"
] ]
}, },
@ -104,7 +83,7 @@
}, },
"nixpkgs-python": { "nixpkgs-python": {
"inputs": { "inputs": {
"flake-compat": "flake-compat_2", "flake-compat": "flake-compat",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
] ]
@ -122,15 +101,33 @@
"type": "github" "type": "github"
} }
}, },
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat_2",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1737465171,
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "9364dc02281ce2d37a1f55b6e51f7c0f65a75f17",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": { "root": {
"inputs": { "inputs": {
"devenv": "devenv", "devenv": "devenv",
"git-hooks": "git-hooks",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"nixpkgs-python": "nixpkgs-python", "nixpkgs-python": "nixpkgs-python",
"pre-commit-hooks": [ "pre-commit-hooks": "pre-commit-hooks"
"git-hooks"
]
} }
} }
}, },

View file

@ -1,6 +1,6 @@
[project] [project]
name = "huesoporro" name = "huesoporro"
version = "0.3.5" version = "0.3.3"
description = "Misc Twitch bot" description = "Misc Twitch bot"
readme = "README.md" readme = "README.md"
authors = [ authors = [
@ -24,7 +24,6 @@ dependencies = [
"pytz>=2024.2", "pytz>=2024.2",
"discord-py>=2.4.0", "discord-py>=2.4.0",
"tenacity>=9.0.0", "tenacity>=9.0.0",
"uvicorn>=0.34.0",
] ]
[project.scripts] [project.scripts]

View file

@ -1,6 +0,0 @@
{
$schema: "https://docs.renovatebot.com/renovate-schema.json",
lockFileMaintenance: {
enabled: true,
},
}

View file

@ -13,7 +13,7 @@ app = Typer()
@app.command() @app.command()
def import_vod(channel_name: str, youtube_url: str, db_path: Path | None = None): def import_vod_cc(channel_name: str, youtube_url: str, db_path: Path | None = None):
logger.info(f"Importing VOD closed captions for {channel_name} from {youtube_url}") logger.info(f"Importing VOD closed captions for {channel_name} from {youtube_url}")
s = Settings.get(db_filepath=db_path) s = Settings.get(db_filepath=db_path)
import_from_vod_action = ImportFromVODAction( import_from_vod_action = ImportFromVODAction(

View file

@ -7,7 +7,6 @@ from huesoporro.actions.chatbot.create_or_update_chatbot import (
from huesoporro.actions.chatbot.get_chatbot_by_user_id import GetChatbotByUserIdAction from huesoporro.actions.chatbot.get_chatbot_by_user_id import GetChatbotByUserIdAction
from huesoporro.actions.users.authenticate_user import AuthenticateUserAction from huesoporro.actions.users.authenticate_user import AuthenticateUserAction
from huesoporro.actions.users.get_user_by_jwt import GetUserByJWTAction from huesoporro.actions.users.get_user_by_jwt import GetUserByJWTAction
from huesoporro.bot import BotsManager
from huesoporro.infra.authenticator import TwitchAuthenticator from huesoporro.infra.authenticator import TwitchAuthenticator
from huesoporro.infra.repos import ChatbotRepo, UserRepo from huesoporro.infra.repos import ChatbotRepo, UserRepo
from huesoporro.libs.db import MarkovDatabase from huesoporro.libs.db import MarkovDatabase
@ -29,29 +28,29 @@ from huesoporro.svc.users_svcs import (
) )
async def get_settings() -> Settings: def get_settings() -> Settings:
return Settings.get() return Settings.get()
async def get_authenticator(s: Settings) -> TwitchAuthenticator: def get_authenticator(s: Settings) -> TwitchAuthenticator:
return TwitchAuthenticator(s=s) return TwitchAuthenticator(s=s)
async def get_chatbot_repo(s: Settings): def get_chatbot_repo(s: Settings):
return ChatbotRepo(s=s) return ChatbotRepo(s=s)
async def get_get_chatbot_by_user_id_svc(chatbot_repo: ChatbotRepo): def get_get_chatbot_by_user_id_svc(chatbot_repo: ChatbotRepo):
return GetChatbotByUserIdSvc(repo=chatbot_repo) return GetChatbotByUserIdSvc(repo=chatbot_repo)
async def get_get_tokens_by_auth_code_svc( def get_get_tokens_by_auth_code_svc(
twitch_authenticator: TwitchAuthenticator, s: Settings twitch_authenticator: TwitchAuthenticator, s: Settings
): ):
return GetTwitchAuthByAuthCodeSvc(s=s, authenticator=twitch_authenticator) return GetTwitchAuthByAuthCodeSvc(s=s, authenticator=twitch_authenticator)
async def get_create_chatbot_svc(chatbot_repo: ChatbotRepo): def get_create_chatbot_svc(chatbot_repo: ChatbotRepo):
return CreateChatbotSvc(repo=chatbot_repo) return CreateChatbotSvc(repo=chatbot_repo)
@ -59,19 +58,19 @@ async def get_user_repo(s: Settings):
return UserRepo(s=s) return UserRepo(s=s)
async def get_create_user_svc(user_repo: UserRepo): def get_create_user_svc(user_repo: UserRepo):
return CreateUserSvc(user_repo=user_repo) return CreateUserSvc(user_repo=user_repo)
async def get_update_user_svc(user_repo: UserRepo): def get_update_user_svc(user_repo: UserRepo):
return UpdateUserSvc(user_repo=user_repo) return UpdateUserSvc(user_repo=user_repo)
async def get_refresh_token_svc(twitch_authenticator: TwitchAuthenticator): def get_refresh_token_svc(twitch_authenticator: TwitchAuthenticator):
return RefreshTokenSvc(twitch_authenticator=twitch_authenticator) return RefreshTokenSvc(twitch_authenticator=twitch_authenticator)
async def get_is_valid_token_svc(twitch_authenticator: TwitchAuthenticator): def get_is_valid_token_svc(twitch_authenticator: TwitchAuthenticator):
return IsValidTokenSvc(authenticator=twitch_authenticator) return IsValidTokenSvc(authenticator=twitch_authenticator)
@ -119,11 +118,11 @@ async def get_sentences_storer_svc(db: MarkovDatabase):
return SentenceStorerSvc(db=db) return SentenceStorerSvc(db=db)
async def get_update_chatbot_svc(chatbot_repo: ChatbotRepo): def get_update_chatbot_svc(chatbot_repo: ChatbotRepo):
return UpdateChatbotSvc(repo=chatbot_repo) return UpdateChatbotSvc(repo=chatbot_repo)
async def get_create_or_update_chatbot_action( def get_create_or_update_chatbot_action(
create_chatbot_svc: CreateChatbotSvc, create_chatbot_svc: CreateChatbotSvc,
update_chatbot_svc: UpdateChatbotSvc, update_chatbot_svc: UpdateChatbotSvc,
get_chatbot_by_user_id_svc: GetChatbotByUserIdSvc, get_chatbot_by_user_id_svc: GetChatbotByUserIdSvc,
@ -135,7 +134,7 @@ async def get_create_or_update_chatbot_action(
) )
async def get_get_chatbot_by_user_id_action( def get_get_chatbot_by_user_id_action(
get_chatbot_by_user_id_svc: GetChatbotByUserIdSvc, get_chatbot_by_user_id_svc: GetChatbotByUserIdSvc,
): ):
return GetChatbotByUserIdAction( return GetChatbotByUserIdAction(
@ -159,10 +158,6 @@ async def get_authenticate_action(
) )
async def get_bot_manager(s: Settings):
return BotsManager(s=s)
async def chatbot( async def chatbot(
get_chatbot_by_user_id_action: GetChatbotByUserIdAction, get_chatbot_by_user_id_action: GetChatbotByUserIdAction,
create_or_update_chatbot_action: CreateOrUpdateChatbotAction, create_or_update_chatbot_action: CreateOrUpdateChatbotAction,

View file

@ -11,7 +11,6 @@ from apps.httpapi.litestar.dependencies import (
authenticate, authenticate,
get_authenticate_action, get_authenticate_action,
get_authenticator, get_authenticator,
get_bot_manager,
get_chatbot_repo, get_chatbot_repo,
get_create_chatbot_svc, get_create_chatbot_svc,
get_create_or_update_chatbot_action, get_create_or_update_chatbot_action,
@ -44,11 +43,12 @@ from apps.httpapi.litestar.routes.api import (
save_bot_settings, save_bot_settings,
) )
from apps.httpapi.litestar.routes.auth import get_code, login from apps.httpapi.litestar.routes.auth import get_code, login
from huesoporro.bot import BotsManager
from huesoporro.settings import Settings from huesoporro.settings import Settings
@get("/healthz") @get("/healthz")
async def get_health() -> dict: def get_health() -> dict:
return {"status": "ok"} return {"status": "ok"}
@ -89,7 +89,7 @@ def create_app():
"s": Provide(get_settings, use_cache=True), "s": Provide(get_settings, use_cache=True),
"a": Provide(get_authenticator, use_cache=True), "a": Provide(get_authenticator, use_cache=True),
"user": Provide(authenticate), "user": Provide(authenticate),
"bm": Provide(get_bot_manager, use_cache=True), "bm": Provide(BotsManager, use_cache=True),
"sss": Provide(get_sentences_storer_svc), "sss": Provide(get_sentences_storer_svc),
"twitch_authenticator": Provide(get_authenticator), "twitch_authenticator": Provide(get_authenticator),
"authenticate_action": Provide(get_authenticate_action), "authenticate_action": Provide(get_authenticate_action),
@ -115,8 +115,9 @@ def create_app():
app = create_app() app = create_app()
if __name__ == "__main__": if __name__ == "__main__":
s = Settings.get() s = Settings.get()
config = uvicorn.Config("main:app", host=s.host, port=s.port, log_level="info") config = uvicorn.Config("main:app", host=s.port, port=s.port, log_level="info")
server = uvicorn.Server(config) server = uvicorn.Server(config)
server.run() server.run()

View file

@ -151,7 +151,6 @@ class MessageType(StrEnum):
YES = "YES" YES = "YES"
WHAT = "WHAT" WHAT = "WHAT"
LAUGH = "LAUGH" LAUGH = "LAUGH"
ANO_SUFFIX = "ANO_SUFFIX"
OTHER = "OTHER" OTHER = "OTHER"
@ -169,8 +168,6 @@ class MessageHandler:
"keking", "keking",
"KEKW", "KEKW",
"OMEGADANCEBUTFAST", "OMEGADANCEBUTFAST",
"xdd",
"xdding",
] ]
self.send = channel_send_func self.send = channel_send_func
@ -178,17 +175,16 @@ class MessageHandler:
"""Determines the type of message based on its content""" """Determines the type of message based on its content"""
if content.startswith("!"): if content.startswith("!"):
return MessageType.COMMAND return MessageType.COMMAND
if content in ["Yes", "yes"]: if content == "Yes":
return MessageType.YES return MessageType.YES
if content.startswith("WHAT"): if content.startswith("WHAT"):
return MessageType.WHAT return MessageType.WHAT
if content.endswith("ano") and len(content) > 3: # noqa: PLR2004
return MessageType.ANO_SUFFIX
if content in self.laugh_patterns: if content in self.laugh_patterns:
return MessageType.LAUGH return MessageType.LAUGH
return MessageType.OTHER return MessageType.OTHER
async def handle_laugh(self) -> str: async def handle_laugh(self) -> str:
"""Handles laugh messages"""
return random.choice(self.laugh_patterns) # noqa: S311 return random.choice(self.laugh_patterns) # noqa: S311
@ -204,7 +200,6 @@ class SaveMessagesCog(commands.Cog):
MessageType.YES: self._create_typed_send("yes"), MessageType.YES: self._create_typed_send("yes"),
MessageType.WHAT: self._create_typed_send("what"), MessageType.WHAT: self._create_typed_send("what"),
MessageType.LAUGH: self._create_typed_send("laugh"), MessageType.LAUGH: self._create_typed_send("laugh"),
MessageType.ANO_SUFFIX: self._create_typed_send("ano_suffix"),
} }
for func in self.send_functions.values(): for func in self.send_functions.values():
@ -232,10 +227,13 @@ class SaveMessagesCog(commands.Cog):
if not message.author: if not message.author:
return return
# Store reference to current message for send functions
self.current_message = message self.current_message = message
# Store the message content
await self.store_svc.run(message.content) await self.store_svc.run(message.content)
# Determine message type and handle accordingly
msg_type = self.message_handler.get_message_type(message.content) msg_type = self.message_handler.get_message_type(message.content)
response = None response = None
@ -249,14 +247,11 @@ class SaveMessagesCog(commands.Cog):
response = "WHAT Ramon" response = "WHAT Ramon"
case MessageType.LAUGH: case MessageType.LAUGH:
response = await self.message_handler.handle_laugh() response = await self.message_handler.handle_laugh()
case MessageType.ANO_SUFFIX:
response = (
f"@{message.author.name} me la agarras con la mano. venga, tira"
)
case MessageType.OTHER: case MessageType.OTHER:
return return
if response and msg_type in self.send_functions: if response and msg_type in self.send_functions:
# Use the type-specific send function
await self.backoff_svc.call_async(self.send_functions[msg_type], response) await self.backoff_svc.call_async(self.send_functions[msg_type], response)

View file

@ -42,7 +42,9 @@ class IRepo(BaseModel, ABC, Generic[T]):
pass # pragma: no cover pass # pragma: no cover
@abstractmethod @abstractmethod
async def list(self, offset: int = 0, limit: int = 10, auto_commit=True) -> list[T]: async def list(
self, obj: T, offset: int = 0, limit: int = 10, auto_commit=True
) -> list[T]:
pass # pragma: no cover pass # pragma: no cover
@ -151,7 +153,7 @@ class UserRepo(IRepo[User]):
) )
async def list( # type: ignore[empty-body] async def list( # type: ignore[empty-body]
self, offset: int = 0, limit: int = 10, auto_commit=True self, obj: User, offset: int = 0, limit: int = 10, auto_commit=True
) -> list[User]: ) -> list[User]:
pass # pragma: no cover pass # pragma: no cover
@ -172,8 +174,6 @@ class QuoteRepo(IRepo[Quote]):
) )
async def create(self, obj: Quote, auto_commit=True) -> Quote: async def create(self, obj: Quote, auto_commit=True) -> Quote:
if await self.get_by_quote(obj.quote):
raise ValueError(f"Quote {obj.quote} already exists")
async with ( async with (
self.get_client(auto_commit=auto_commit) as db, self.get_client(auto_commit=auto_commit) as db,
await db.execute( await db.execute(
@ -196,81 +196,18 @@ class QuoteRepo(IRepo[Quote]):
return self._deserialize(data) return self._deserialize(data)
async def update(self, obj: Quote, auto_commit=True) -> Quote: # type: ignore[empty-body] async def update(self, obj: Quote, auto_commit=True) -> Quote: # type: ignore[empty-body]
if not await self.get_by_id(obj.id): pass # pragma: no cover
raise ValueError(f"Quote {obj.id} does not exist")
async with (
self.get_client(auto_commit=auto_commit) as db,
await db.execute(
"""
UPDATE quotes
SET quote = ?,
author = ?,
channel = ?,
last_updated_at = ?
WHERE id = ?
RETURNING *
""",
(
obj.quote,
obj.author,
obj.channel_name,
utils.get_utc_now(),
obj.id.hex,
),
) as cursor,
):
data = await cursor.fetchone()
return self._deserialize(data)
async def delete(self, obj: Quote, auto_commit=True): async def delete(self, obj: Quote, auto_commit=True):
async with self.get_client(auto_commit=auto_commit) as db: pass # pragma: no cover
await db.execute(
"""
DELETE FROM quotes WHERE id = ?
""",
(obj.id.hex,),
)
async def get_by_id(self, obj_id: UUID, auto_commit=True) -> Quote | None: # type: ignore[empty-body] async def get_by_id(self, obj_id: UUID, auto_commit=True) -> Quote | None: # type: ignore[empty-body]
async with ( pass # pragma: no cover
self.get_client(auto_commit=auto_commit) as db,
db.execute(
"""
SELECT * FROM quotes WHERE id = ?
""",
(obj_id.hex,),
) as cursor,
):
data = await cursor.fetchone()
if not data:
return None
return self._deserialize(data)
async def get_by_quote(self, quote: str, auto_commit=True) -> Quote | None:
async with (
self.get_client(auto_commit=auto_commit) as db,
db.execute(
"""
SELECT * FROM quotes WHERE quote = ?
""",
(quote,),
) as cursor,
):
data = await cursor.fetchone()
if not data:
return None
return self._deserialize(data)
async def list( # type: ignore[empty-body] async def list( # type: ignore[empty-body]
self, offset: int = 0, limit: int = 10, auto_commit=True self, obj: T, offset: int = 0, limit: int = 10, auto_commit=True
) -> list[Quote]: ) -> list[T]:
async with self.get_client() as db: pass # pragma: no cover
db.row_factory = aiosqlite.Row
async with db.execute(
"SELECT * FROM quotes LIMIT ? OFFSET ?", (limit, offset)
) as cursor:
results = await cursor.fetchall()
return [self._deserialize(result) for result in results]
async def get_random(self, channel_name: str, auto_commit=True) -> Quote | None: async def get_random(self, channel_name: str, auto_commit=True) -> Quote | None:
async with ( async with (
@ -361,22 +298,11 @@ class ChatbotRepo(IRepo[Chatbot]):
data = await cursor.fetchone() data = await cursor.fetchone()
return self._deserialize(data) return self._deserialize(data)
async def delete(self, obj: Chatbot, auto_commit=True): async def delete(self, obj: T, auto_commit=True):
if not await self.get_by_id(obj.id): pass # pragma: no cover
raise ValueError(f"Chatbot {obj.id} does not exist")
async with self.get_client() as db:
await db.execute("DELETE FROM chatbot WHERE id = ?", (obj.id.hex,))
async def get_by_id(self, obj_id: UUID, auto_commit=True) -> Chatbot | None: # type: ignore[empty-body] async def get_by_id(self, obj_id: UUID, auto_commit=True) -> Chatbot | None: # type: ignore[empty-body]
async with self.get_client() as db: pass # pragma: no cover
db.row_factory = aiosqlite.Row
async with db.execute(
"SELECT * FROM chatbot WHERE id = ?", (obj_id.hex,)
) as cursor:
result = await cursor.fetchone()
if not result:
return None
return self._deserialize(result)
async def get_by_user_id(self, user_id: UUID) -> Chatbot | None: async def get_by_user_id(self, user_id: UUID) -> Chatbot | None:
async with self.get_client() as db: async with self.get_client() as db:
@ -390,12 +316,6 @@ class ChatbotRepo(IRepo[Chatbot]):
return self._deserialize(result) return self._deserialize(result)
async def list( # type: ignore[empty-body] async def list( # type: ignore[empty-body]
self, offset: int = 0, limit: int = 10, auto_commit=True self, obj: T, offset: int = 0, limit: int = 10, auto_commit=True
) -> list[Chatbot]: ) -> list[T]:
async with self.get_client() as db: pass # pragma: no cover
db.row_factory = aiosqlite.Row
async with db.execute(
"SELECT * FROM chatbot LIMIT ? OFFSET ?", (limit, offset)
) as cursor:
results = await cursor.fetchall()
return [self._deserialize(result) for result in results]

View file

@ -65,7 +65,7 @@ def db(s, cdb):
pass pass
async def list( # type: ignore[empty-body] async def list( # type: ignore[empty-body]
self, offset: int = 0, limit: int = 10, auto_commit=True self, obj: BaseModel, offset: int = 0, limit: int = 10, auto_commit=True
) -> list[BaseModel]: ) -> list[BaseModel]:
pass pass
@ -169,16 +169,6 @@ async def chatbot(chatbot_factory, user):
return chatbot_factory.build(user_id=user.id) return chatbot_factory.build(user_id=user.id)
@pytest.fixture
async def five_chatbots(chatbot_factory, user):
return [chatbot_factory.build() for _ in range(5)]
@pytest.fixture
async def persisted_five_chatbots(five_chatbots, chatbot_repo):
return [await chatbot_repo.create(chatbot) for chat in five_chatbots]
@pytest.fixture @pytest.fixture
async def persisted_chatbot(chatbot_repo, chatbot, persisted_user): async def persisted_chatbot(chatbot_repo, chatbot, persisted_user):
return await chatbot_repo.create(chatbot) return await chatbot_repo.create(chatbot)
@ -214,21 +204,11 @@ async def quote(quote_factory):
return quote_factory.build() return quote_factory.build()
@pytest.fixture
async def five_quotes(quote_factory):
return [quote_factory.build() for _ in range(5)]
@pytest.fixture @pytest.fixture
async def persisted_quote(quote_repo, quote): async def persisted_quote(quote_repo, quote):
return await quote_repo.create(quote) return await quote_repo.create(quote)
@pytest.fixture
async def persisted_five_quotes(five_quotes, quote_repo):
return [await quote_repo.create(quote) for quote in five_quotes]
@pytest.fixture @pytest.fixture
async def create_quote_svc(quote_repo): async def create_quote_svc(quote_repo):
return CreateQuoteSvc(repo=quote_repo) return CreateQuoteSvc(repo=quote_repo)

View file

@ -88,79 +88,8 @@ async def test_update_chatbot_raises_value_error_on_non_existing_chatbot(
await chatbot_repo.update(chatbot) await chatbot_repo.update(chatbot)
async def test_delete_chatbot_raises_value_error_on_non_existing_chatbot(
chatbot_repo, chatbot
):
with pytest.raises(ValueError, match=f"Chatbot {chatbot.id} does not exist"):
await chatbot_repo.delete(chatbot)
async def test_delete_chatbot(chatbot_repo, persisted_chatbot):
assert await chatbot_repo.delete(persisted_chatbot) is None
assert await chatbot_repo.get_by_id(persisted_chatbot.id) is None
async def test_get_by_id(chatbot_repo, persisted_chatbot):
chatbot = await chatbot_repo.get_by_id(persisted_chatbot.id)
assert chatbot == persisted_chatbot
async def test_list(chatbot_repo, persisted_five_chatbots):
chatbots = await chatbot_repo.list()
assert len(chatbots) == 5 # noqa: PLR2004
async def test_list_offset_limit(chatbot_repo, persisted_five_chatbots):
chatbots = await chatbot_repo.list(offset=1, limit=2)
assert len(chatbots) == 2 # noqa: PLR2004
async def test_get_random_quote(quote_repo: QuoteRepo, persisted_quote): async def test_get_random_quote(quote_repo: QuoteRepo, persisted_quote):
quote = await quote_repo.get_random(persisted_quote.channel_name) quote = await quote_repo.get_random(persisted_quote.channel_name)
assert quote assert quote
assert quote.author == persisted_quote.author assert quote.author == persisted_quote.author
assert quote.channel_name == persisted_quote.channel_name assert quote.channel_name == persisted_quote.channel_name
async def test_create_quote_raises_value_error_for_existing_quote(
quote_repo: QuoteRepo, persisted_quote
):
with pytest.raises(
ValueError, match=f"Quote {persisted_quote.quote} already exists"
):
await quote_repo.create(persisted_quote)
async def test_create_quote(quote_repo: QuoteRepo, quote_factory):
quote = quote_factory.build()
created_quote = await quote_repo.create(quote)
assert created_quote == quote
async def test_update_quote_raises_value_error_on_non_existing_quote(
quote_repo: QuoteRepo, quote
):
with pytest.raises(ValueError, match=f"Quote {quote.id} does not exist"):
await quote_repo.update(quote)
async def test_update_quote(quote_repo: QuoteRepo, persisted_quote):
persisted_quote.quote = "new quote"
updated_quote = await quote_repo.update(persisted_quote)
persisted_quote.last_updated_at = updated_quote.last_updated_at
assert updated_quote == persisted_quote
async def test_delete_quote(quote_repo: QuoteRepo, persisted_quote):
assert await quote_repo.delete(persisted_quote) is None
assert await quote_repo.get_by_id(persisted_quote.id) is None
async def test_list_quotes(quote_repo, persisted_five_quotes):
quotes = await quote_repo.list()
assert len(quotes) == 5 # noqa: PLR2004
async def test_list_quotes_offset_limit(quote_repo, persisted_five_quotes):
quotes = await quote_repo.list(offset=1, limit=2)
assert len(quotes) == 2 # noqa: PLR2004

1296
uv.lock generated

File diff suppressed because it is too large Load diff