Skip to content

Commit a8812dc

Browse files
committed
Rework tours plugin
Also replace tests using a ps server for team validation with the ps cli interface
1 parent 230296a commit a8812dc

File tree

11 files changed

+286
-457
lines changed

11 files changed

+286
-457
lines changed

Diff for: .env-example

-7
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,3 @@ COMMAND_CHARACTER=.
3030
## WEBHOOKS: Dictionary containing room names associated with Discord webhook URLs.
3131
## Used to send notifications about room events such as tournaments.
3232
# WEBHOOKS='{"room1": "https://discord.com/api/webhooks/123/abc", "room2": "https://discord.com/api/webhooks/456/def"}'
33-
34-
## TESTS_MOD_USERNAME: PS username of a dummy user used to run tests against a real
35-
## showdown instance.
36-
# TESTS_MOD_USERNAME=mymod
37-
38-
## TESTS_MOD_PASSWORD: PS password of the dummy tests user.
39-
# TESTS_MOD_PASSWORD=mypassword

Diff for: .github/workflows/ci-cd.yml

+2-17
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,9 @@ jobs:
5454
- name: Save coverage report
5555
run: cat coverage/coverage.md >> $GITHUB_STEP_SUMMARY
5656

57-
secrets-check:
58-
runs-on: ubuntu-24.04
59-
outputs:
60-
has-secrets: ${{ steps.secrets-check.outputs.has-secrets }}
61-
steps:
62-
- name: Check if secrets are available
63-
id: secrets-check
64-
run: echo "has-secrets=${{ secrets.TESTS_BOT_USERNAME != '' }}" >> $GITHUB_OUTPUT
65-
6657
integration:
6758
runs-on: ubuntu-24.04
68-
needs: secrets-check
69-
if: needs.secrets-check.outputs.has-secrets == 'true'
59+
if: github.event_name == 'schedule'
7060
steps:
7161
- uses: actions/checkout@v4
7262
- name: Cache veekun database
@@ -91,12 +81,7 @@ jobs:
9181
- name: Create folders
9282
run: mkdir -p data pokemon-showdown
9383
- name: Run integration tests
94-
run: podman build --env USERNAME --env PASSWORD --env TESTS_MOD_USERNAME --env TESTS_MOD_PASSWORD --volume "$PWD"/pokemon-showdown:/pokemon-showdown --volume "$PWD"/data:/data --target integration .
95-
env:
96-
USERNAME: ${{ secrets.TESTS_BOT_USERNAME }}
97-
PASSWORD: ${{ secrets.TESTS_BOT_PASSWORD }}
98-
TESTS_MOD_USERNAME: ${{ secrets.TESTS_MOD_USERNAME }}
99-
TESTS_MOD_PASSWORD: ${{ secrets.TESTS_MOD_PASSWORD }}
84+
run: podman build --volume "$PWD"/pokemon-showdown:/pokemon-showdown --volume "$PWD"/data:/data --target integration .
10085

10186
coverage-badge:
10287
runs-on: ubuntu-24.04

Diff for: Containerfile

+5-8
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@ FROM builder as test-base
3131

3232
RUN apk add --no-cache gcc musl-dev linux-headers
3333
RUN apk add --no-cache make
34+
RUN apk add --no-cache nodejs npm
35+
36+
ENV CERBOTTANA_SHOWDOWN_PATH=/pokemon-showdown
3437

35-
RUN mkdir -p /data
38+
RUN mkdir -p /data /pokemon-showdown
3639

3740
RUN --mount=type=cache,target=/root/.cache/uv \
3841
uv sync --no-editable
@@ -49,13 +52,7 @@ RUN uv run coverage report --format=markdown > /coverage/coverage.md
4952

5053
FROM test-base as integration
5154

52-
ENV CERBOTTANA_SHOWDOWN_PATH=/pokemon-showdown
53-
54-
RUN apk add --no-cache nodejs npm
55-
56-
RUN mkdir -p /pokemon-showdown
57-
58-
RUN make pytest-integration
55+
RUN uv run pytest -m integration
5956

6057

6158
FROM base as final

Diff for: Makefile

+2-6
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,12 @@ ruff:
2121

2222
.PHONY: pytest
2323
pytest:
24-
@uv run pytest -m 'not integration' --cov
25-
26-
.PHONY: pytest-integration
27-
pytest-integration:
28-
@uv run pytest -m 'integration'
24+
@uv run pytest --cov
2925

3026
.PHONY: lint
3127
lint: format-check darglint mypy ruff
3228

3329
.PHONY: all
34-
all: format darglint mypy ruff pytest pytest-integration
30+
all: format darglint mypy ruff pytest
3531

3632
.DEFAULT_GOAL := all

Diff for: pyproject.toml

-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ dev = [
2424
"pytest-asyncio==0.25.3",
2525
"pytest-cov==6.0.0",
2626
"pytest-mock==3.14.0",
27-
"pytest-xprocess==1.0.2",
2827
"python-dotenv[cli]==1.0.1",
2928
"ruff==0.9.9",
3029
"types-python-dateutil==2.9.0.20241206",

Diff for: src/cerbottana/plugins/__init__.py

+25-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from collections.abc import Callable, Coroutine
77
from functools import wraps
88
from pathlib import Path
9-
from typing import TYPE_CHECKING, ClassVar
9+
from typing import TYPE_CHECKING, ClassVar, Protocol, runtime_checkable
1010

1111
from domify.base_element import BaseElement
1212
from sqlalchemy import select
@@ -25,6 +25,12 @@
2525
HTMLPageFunc = Callable[[User, Room, int], BaseElement | None]
2626

2727

28+
@runtime_checkable
29+
class CommandClass(Protocol):
30+
@classmethod
31+
async def cmd_func(cls, msg: Message) -> None: ...
32+
33+
2834
# --- Command logic and complementary decorators ---
2935

3036

@@ -33,24 +39,27 @@ class Command:
3339

3440
def __init__(
3541
self,
42+
func_name: str,
3643
func: CommandFunc,
44+
cls: CommandClass | None,
3745
aliases: tuple[str, ...],
3846
helpstr: str,
3947
is_unlisted: bool,
4048
required_rank: Role,
4149
required_rank_editable: bool | str,
4250
allow_pm: bool | Role,
4351
) -> None:
44-
self.name = func.__name__
52+
self.name = func_name
4553
self.module = func.__module__
4654
self.callback = func
55+
self.cls = cls
4756
self.aliases = (self.name, *aliases)
4857
self.helpstr = helpstr
4958
self.is_unlisted = is_unlisted
5059
self.required_rank = required_rank
5160
self.required_rank_editable = required_rank_editable
5261
self.allow_pm = allow_pm
53-
self._instances[func.__name__] = self
62+
self._instances[func_name] = self
5463

5564
@property
5665
def splitted_aliases(self) -> dict[str, Command]:
@@ -107,6 +116,7 @@ def get_rank_editable_commands(cls) -> set[str]:
107116

108117

109118
def command_check_permission(
119+
func_name: str,
110120
func: CommandFunc,
111121
allow_pm: bool | Role,
112122
main_room_only: bool,
@@ -120,7 +130,7 @@ async def wrapper(msg: Message) -> None:
120130
elif msg.room:
121131
roomid = msg.room.roomid
122132
is_pm = msg.room is None
123-
req_rank = msg.conn.commands[func.__name__].get_required_rank(roomid, is_pm)
133+
req_rank = msg.conn.commands[func_name].get_required_rank(roomid, is_pm)
124134

125135
if main_room_only and not msg.user.has_role(req_rank, msg.conn.main_room):
126136
return
@@ -151,7 +161,7 @@ def command_wrapper(
151161
required_rank_editable: bool | str = False,
152162
main_room_only: bool = False,
153163
parametrize_room: bool = False,
154-
) -> Callable[[CommandFunc], Command]:
164+
) -> Callable[[CommandFunc | CommandClass], Command]:
155165
"""Decorates a function to generate a Command instance.
156166
157167
Args:
@@ -174,11 +184,17 @@ def command_wrapper(
174184
the docstring of `parametrize_room_wrapper`. Defaults to False.
175185
176186
Returns:
177-
Callable[[CommandFunc], Command]: Wrapper.
187+
Callable[[CommandFunc | CommandClass], Command]: Wrapper.
178188
"""
179189

180-
def cls_wrapper(func: CommandFunc) -> Command:
190+
def cls_wrapper(func: CommandFunc | CommandClass) -> Command:
191+
func_name = func.__name__.lower() # type: ignore[union-attr]
192+
cls = None
193+
if isinstance(func, CommandClass):
194+
cls = func
195+
func = cls.cmd_func
181196
func = command_check_permission(
197+
func_name,
182198
func,
183199
allow_pm,
184200
main_room_only,
@@ -187,7 +203,9 @@ def cls_wrapper(func: CommandFunc) -> Command:
187203
if parametrize_room:
188204
func = parametrize_room_wrapper(func)
189205
return Command(
206+
func_name,
190207
func,
208+
cls,
191209
aliases,
192210
helpstr,
193211
is_unlisted,

0 commit comments

Comments
 (0)