Доработки контуров, тесты конфига и CI
This commit is contained in:
parent
05169b9b39
commit
aafb9c9e3b
|
|
@ -26,6 +26,8 @@ deploy
|
|||
.gitea
|
||||
tests
|
||||
docs
|
||||
scripts
|
||||
Makefile
|
||||
*.md
|
||||
Dockerfile
|
||||
docker-compose*.yml
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@ on:
|
|||
branches: [master]
|
||||
workflow_dispatch:
|
||||
|
||||
# Отменяем предыдущий незавершённый прогон при новом пуше.
|
||||
concurrency:
|
||||
group: ci
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
REGISTRY: git.mpakosrv.ru
|
||||
IMAGE_NAME: mpakobecex/dispelevilbot-bot
|
||||
|
|
@ -20,12 +25,10 @@ jobs:
|
|||
python-version: "3.12"
|
||||
- name: Установка зависимостей
|
||||
run: pip install -r requirements-dev.txt
|
||||
# В Forgejo 1.21 `continue-on-error` игнорируется, поэтому неблокирующий
|
||||
# режим для black сделан через shell. ruff обязателен.
|
||||
- name: Ruff
|
||||
run: ruff check .
|
||||
- name: Black
|
||||
run: black --check . || echo "::warning::black нашёл отличия в форматировании (пока не блокирует)"
|
||||
run: black --check .
|
||||
- name: Pytest
|
||||
run: pytest -q
|
||||
|
||||
|
|
@ -43,7 +46,9 @@ jobs:
|
|||
IMAGE="$REGISTRY/$IMAGE_NAME"
|
||||
SHA="${GITHUB_SHA:-unknown}"
|
||||
SHORT_SHA="$(echo "$SHA" | cut -c1-12)"
|
||||
VERSION="$(sed -n 's/^__version__ = "\(.*\)"/\1/p' common/version.py)"
|
||||
TAGS="$IMAGE:$SHORT_SHA $IMAGE:latest"
|
||||
if [ -n "$VERSION" ]; then TAGS="$TAGS $IMAGE:$VERSION"; fi
|
||||
ARGS=""
|
||||
for t in $TAGS; do ARGS="$ARGS -t $t"; done
|
||||
docker build $ARGS .
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
# Управление контурами test/prod.
|
||||
# Пример: make up E=test
|
||||
#
|
||||
# Используем .RECIPEPREFIX, чтобы не зависеть от табов (рецепты начинаются с ">").
|
||||
E ?= test
|
||||
FILE := env/$(E).env
|
||||
COMPOSE := docker compose -p $(E) --env-file $(FILE) -f docker-compose.prod.yml
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
.RECIPEPREFIX := >
|
||||
|
||||
.PHONY: help up pull down restart logs ps images check psql
|
||||
|
||||
help:
|
||||
> @echo "Контур задаётся через E=test|prod (по умолчанию test):"
|
||||
> @echo " make up E=test поднять контур"
|
||||
> @echo " make pull E=test подтянуть образ из registry"
|
||||
> @echo " make down E=test остановить (данные сохраняются)"
|
||||
> @echo " make restart E=test пересоздать контейнеры"
|
||||
> @echo " make logs E=test стримить логи"
|
||||
> @echo " make ps E=test статус сервисов"
|
||||
> @echo " make images E=test образы контура"
|
||||
> @echo " make check E=test проверка контура (скрипт)"
|
||||
> @echo " make psql E=test psql в БД контура"
|
||||
|
||||
up:
|
||||
> $(COMPOSE) up -d
|
||||
|
||||
pull:
|
||||
> $(COMPOSE) pull
|
||||
|
||||
down:
|
||||
> $(COMPOSE) down
|
||||
|
||||
restart:
|
||||
> $(COMPOSE) up -d --force-recreate
|
||||
|
||||
logs:
|
||||
> $(COMPOSE) logs -f
|
||||
|
||||
ps:
|
||||
> $(COMPOSE) ps
|
||||
|
||||
images:
|
||||
> $(COMPOSE) images
|
||||
|
||||
check:
|
||||
> ./scripts/check_contour.sh $(E)
|
||||
|
||||
psql:
|
||||
> $(COMPOSE) exec postgres sh -lc 'psql -U "$$POSTGRES_USER" -d "$$POSTGRES_DB"'
|
||||
|
|
@ -73,7 +73,7 @@ pytest
|
|||
|
||||
## Версионирование
|
||||
|
||||
- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.2`.
|
||||
- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.2.5`.
|
||||
- Схема — SemVer-подобная (`MAJOR.MINOR.PATCH`); git-тег = `v` + версия (`v0.1`).
|
||||
- Docker-образы тегируются версией (Блок 2), события ClickHouse получают `bot_version` (Блок 3).
|
||||
- Выпуск: изменить `common/version.py` → коммит → аннотированный тег `vX.Y` → `git push --tags`.
|
||||
|
|
|
|||
|
|
@ -32,9 +32,7 @@ async def set_nick_handler(message: Message) -> None:
|
|||
nick = parts[1]
|
||||
try:
|
||||
await users_service.set_nick(message.from_user.id, nick)
|
||||
await message.answer(
|
||||
render(system.MSG_NICK_SET, {"username": message.from_user.username, "nick": nick})
|
||||
)
|
||||
await message.answer(render(system.MSG_NICK_SET, {"username": message.from_user.username, "nick": nick}))
|
||||
await message.delete()
|
||||
except Exception:
|
||||
logger.exception("Не удалось установить ник")
|
||||
|
|
|
|||
|
|
@ -54,9 +54,7 @@ SCHEDULES: tuple[ScheduledMessage, ...] = (
|
|||
|
||||
|
||||
async def _send_broadcast(bot: Bot, template: str, subscribers: list[dict]) -> None:
|
||||
chunks = [
|
||||
subscribers[i : i + BATCH_SIZE] for i in range(0, len(subscribers), BATCH_SIZE)
|
||||
] or [[]]
|
||||
chunks = [subscribers[i : i + BATCH_SIZE] for i in range(0, len(subscribers), BATCH_SIZE)] or [[]]
|
||||
for chunk in chunks:
|
||||
text = render(template, {"usernames": format_usernames(chunk)})
|
||||
await bot.send_message(chat_id=get_settings().chat_id, text=text)
|
||||
|
|
|
|||
|
|
@ -8,18 +8,12 @@ from common.repositories import shop_repo
|
|||
|
||||
async def list_titles() -> list[Title]:
|
||||
rows = await shop_repo.get_titles_list()
|
||||
return [
|
||||
Title(id=int(row["i"]), title=row["n"] or "", price=int(row["p"] or 0))
|
||||
for row in rows
|
||||
]
|
||||
return [Title(id=int(row["i"]), title=row["n"] or "", price=int(row["p"] or 0)) for row in rows]
|
||||
|
||||
|
||||
async def list_donats() -> list[Donat]:
|
||||
rows = await shop_repo.get_donats_list()
|
||||
return [
|
||||
Donat(id=int(row["i"]), title=row["n"] or "", price=int(row["p"] or 0))
|
||||
for row in rows
|
||||
]
|
||||
return [Donat(id=int(row["i"]), title=row["n"] or "", price=int(row["p"] or 0)) for row in rows]
|
||||
|
||||
|
||||
async def buy_title(id_user: int, id_title: int) -> bool:
|
||||
|
|
|
|||
|
|
@ -9,6 +9,5 @@ from common.repositories import stats_repo
|
|||
async def top_users() -> list[TopEntry]:
|
||||
rows = await stats_repo.get_top_list()
|
||||
return [
|
||||
TopEntry(username=row.get("username"), nick=row.get("nick"), count=int(row.get("count") or 0))
|
||||
for row in rows
|
||||
TopEntry(username=row.get("username"), nick=row.get("nick"), count=int(row.get("count") or 0)) for row in rows
|
||||
]
|
||||
|
|
|
|||
|
|
@ -87,10 +87,7 @@ class Settings(BaseSettings):
|
|||
"""Строка подключения psycopg для async-пула (креды URL-экранируются)."""
|
||||
user = quote_plus(self.db_user)
|
||||
password = quote_plus(self.db_password)
|
||||
return (
|
||||
f"postgresql://{user}:{password}"
|
||||
f"@{self.db_host}:{self.db_port}/{self.db_name}"
|
||||
)
|
||||
return f"postgresql://{user}:{password}" f"@{self.db_host}:{self.db_port}/{self.db_name}"
|
||||
|
||||
|
||||
@lru_cache
|
||||
|
|
|
|||
|
|
@ -9,15 +9,13 @@ from common.db.pool import get_pool
|
|||
|
||||
async def money_action(id_user: int, amount: int, id_action: int = 1) -> None:
|
||||
"""Начислить/списать деньги (запись в журнал money)."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("CALL bot.money_action(%s, %s, %s)", (id_user, amount, id_action))
|
||||
|
||||
|
||||
async def get_money(id_user: int) -> int:
|
||||
"""Текущий баланс пользователя (SUM по журналу)."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("SELECT bot.get_money(%s) AS money", (id_user,))
|
||||
row = await cur.fetchone()
|
||||
return int(row["money"] or 0) if row else 0
|
||||
|
|
@ -25,8 +23,7 @@ async def get_money(id_user: int) -> int:
|
|||
|
||||
async def get_eggs(id_user: int) -> int:
|
||||
"""Количество яичек пользователя."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("SELECT bot.get_eggs(%s) AS eggs", (id_user,))
|
||||
row = await cur.fetchone()
|
||||
return int(row["eggs"] or 0) if row else 0
|
||||
|
|
@ -34,15 +31,13 @@ async def get_eggs(id_user: int) -> int:
|
|||
|
||||
async def purification(id_user: int) -> dict[str, Any] | None:
|
||||
"""Выполнить ежедневное очищение."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("SELECT * FROM bot.purification(%s)", (id_user,))
|
||||
return await cur.fetchone()
|
||||
|
||||
|
||||
async def add_eggs(id_user: int) -> dict[str, Any] | None:
|
||||
"""Получить яички (раз в сутки)."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("SELECT * FROM bot.add_eggs(%s)", (id_user,))
|
||||
return await cur.fetchone()
|
||||
|
|
|
|||
|
|
@ -9,24 +9,21 @@ from common.db.pool import get_pool
|
|||
|
||||
async def get_titles_list() -> list[dict[str, Any]]:
|
||||
"""Доступные к покупке звания (id_title, title, price)."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("SELECT * FROM bot.get_titles_list()")
|
||||
return await cur.fetchall()
|
||||
|
||||
|
||||
async def get_donats_list() -> list[dict[str, Any]]:
|
||||
"""Доступные к покупке донаты (id_donat, title, price)."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("SELECT * FROM bot.get_donats_list()")
|
||||
return await cur.fetchall()
|
||||
|
||||
|
||||
async def buy_title(id_user: int, id_title: int) -> int:
|
||||
"""Купить звание. Возвращает 1 при успехе, -1 если не хватает денег."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("SELECT bot.buy_title(%s, %s) AS res", (id_user, id_title))
|
||||
row = await cur.fetchone()
|
||||
return int(row["res"]) if row else -1
|
||||
|
|
@ -34,8 +31,7 @@ async def buy_title(id_user: int, id_title: int) -> int:
|
|||
|
||||
async def buy_donat(id_user: int, id_donat: int) -> str:
|
||||
"""Купить донат. Возвращает промокод или '-1' при неуспехе."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("SELECT bot.buy_donat(%s, %s) AS res", (id_user, id_donat))
|
||||
row = await cur.fetchone()
|
||||
return str(row["res"]) if row else "-1"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ from common.db.pool import get_pool
|
|||
|
||||
async def get_top_list() -> list[dict[str, Any]]:
|
||||
"""Топ-10 пользователей по количеству сообщений (username, nick, count)."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("SELECT u AS username, n AS nick, c AS count FROM bot.get_top_list()")
|
||||
return await cur.fetchall()
|
||||
|
|
|
|||
|
|
@ -7,13 +7,11 @@ from common.db.pool import get_pool
|
|||
|
||||
async def subscribe(id_user: int) -> None:
|
||||
"""Подписать пользователя на рассылки."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("CALL bot.subscribe(%s)", (id_user,))
|
||||
|
||||
|
||||
async def unsubscribe(id_user: int) -> None:
|
||||
"""Отписать пользователя от рассылок."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("CALL bot.unsubscribe(%s)", (id_user,))
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ from common.db.pool import get_pool
|
|||
|
||||
async def add_user(id_user: int, username: str, first_name: str, last_name: str) -> None:
|
||||
"""Создать/обновить пользователя и добавить запись о сообщении."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute(
|
||||
"CALL bot.add_user(%s, %s, %s, %s)",
|
||||
(id_user, username, first_name, last_name),
|
||||
|
|
@ -19,37 +18,32 @@ async def add_user(id_user: int, username: str, first_name: str, last_name: str)
|
|||
|
||||
async def add_nick(id_user: int, nick: str) -> None:
|
||||
"""Установить ник пользователя."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("CALL bot.add_nick(%s, %s)", (id_user, nick))
|
||||
|
||||
|
||||
async def get_nick_list() -> list[dict[str, Any]]:
|
||||
"""Список всех игроков (username, nick), отсортированный по нику."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("SELECT username, nick FROM bot.get_nick_list()")
|
||||
return await cur.fetchall()
|
||||
|
||||
|
||||
async def get_nick_list_subscribe() -> list[dict[str, Any]]:
|
||||
"""Список подписанных на рассылки (username, nick)."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("SELECT username, nick FROM bot.get_nick_list_subscribe()")
|
||||
return await cur.fetchall()
|
||||
|
||||
|
||||
async def get_info_by_id(id_user: int) -> dict[str, Any] | None:
|
||||
"""Сводная информация о пользователе."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("SELECT * FROM bot.get_info_by_id(%s)", (id_user,))
|
||||
return await cur.fetchone()
|
||||
|
||||
|
||||
async def del_user_by_id(id_user: int) -> None:
|
||||
"""Удалить пользователя (при выходе из чата)."""
|
||||
async with get_pool().connection() as conn:
|
||||
async with conn.cursor() as cur:
|
||||
async with get_pool().connection() as conn, conn.cursor() as cur:
|
||||
await cur.execute("CALL bot.del_user_by_id(%s)", (id_user,))
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@ from typing import Any
|
|||
_TAG_RE = re.compile(r"\{([a-z_][a-z0-9_]*)\}")
|
||||
|
||||
#: Теги, чьи значения подставляются «как есть» (формируются helpers-функциями ниже).
|
||||
RAW_TAGS: frozenset[str] = frozenset(
|
||||
{"usernames", "users_list", "users_list_count", "tops"}
|
||||
)
|
||||
RAW_TAGS: frozenset[str] = frozenset({"usernames", "users_list", "users_list_count", "tops"})
|
||||
|
||||
#: Полный реестр поддерживаемых тегов: имя -> короткое описание.
|
||||
TAG_REGISTRY: dict[str, str] = {
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
__version__ = "0.2"
|
||||
__version__ = "0.2.5"
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ ENV_FILE=env/prod.env
|
|||
|
||||
APP_ENV=prod
|
||||
|
||||
# Образ в реестре
|
||||
# Образ в реестре (latest = последняя сборка из master; можно закрепить <sha> или версию, напр. 0.2.5)
|
||||
REGISTRY=git.mpakosrv.ru
|
||||
IMAGE_TAG=v0.2
|
||||
IMAGE_TAG=latest
|
||||
|
||||
BOT_TOKEN=
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ ENV_FILE=env/test.env
|
|||
|
||||
APP_ENV=dev
|
||||
|
||||
# Образ в реестре
|
||||
# Образ в реестре (latest = последняя сборка из master; можно закрепить <sha> или версию, напр. 0.2.5)
|
||||
REGISTRY=git.mpakosrv.ru
|
||||
IMAGE_TAG=v0.2
|
||||
IMAGE_TAG=latest
|
||||
|
||||
BOT_TOKEN=
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ line-length = 120
|
|||
target-version = "py312"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "I", "UP", "B", "W"]
|
||||
select = ["E", "F", "I", "UP", "B", "W", "C4", "SIM", "PIE"]
|
||||
ignore = ["E501"]
|
||||
|
||||
[tool.black]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/env bash
|
||||
# Проверка контура: сервисы запущены, образ бота — ожидаемый, миграции применены.
|
||||
# Использование: ./scripts/check_contour.sh [test|prod]
|
||||
set -euo pipefail
|
||||
|
||||
E="${1:-test}"
|
||||
FILE="env/${E}.env"
|
||||
|
||||
if [ ! -f "$FILE" ]; then
|
||||
echo "Нет файла окружения: $FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
COMPOSE="docker compose -p ${E} --env-file ${FILE} -f docker-compose.prod.yml"
|
||||
|
||||
echo "== контур: ${E} =="
|
||||
$COMPOSE ps
|
||||
|
||||
echo
|
||||
echo "== образы =="
|
||||
$COMPOSE images
|
||||
|
||||
echo
|
||||
echo "== проверки =="
|
||||
|
||||
running="$($COMPOSE ps --services --status running || true)"
|
||||
fail=0
|
||||
for svc in postgres bot; do
|
||||
if echo "$running" | grep -qx "$svc"; then
|
||||
echo "OK ${svc}: running"
|
||||
else
|
||||
echo "FAIL ${svc}: не запущен" >&2
|
||||
fail=1
|
||||
fi
|
||||
done
|
||||
|
||||
# миграции: сервис migrate должен завершиться успешно (Exited (0))
|
||||
if $COMPOSE ps -a --format '{{.Service}} {{.Status}}' | grep -q '^migrate Exited (0)'; then
|
||||
echo "OK migrate: завершён успешно"
|
||||
else
|
||||
echo "WARN migrate: нет подтверждённого успешного завершения (см. 'logs migrate')" >&2
|
||||
fi
|
||||
|
||||
# БД отвечает
|
||||
if $COMPOSE exec -T postgres sh -lc 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"' >/dev/null 2>&1; then
|
||||
echo "OK postgres: принимает соединения"
|
||||
else
|
||||
echo "FAIL postgres: не отвечает" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
exit $fail
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
"""Тесты разбора переменных окружения (без БД и сети).
|
||||
|
||||
Проверяем граничные случаи, которые уже ломали запуск:
|
||||
- ``ADMIN_IDS`` пустой / через запятую / как JSON-массив;
|
||||
- пустые целочисленные поля ``CHAT_ID``/``ALERT_CHAT_ID``;
|
||||
- обязательный ``BOT_TOKEN``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from common.config import Settings
|
||||
|
||||
#: Переменные, которые чистим перед каждым тестом, чтобы окружение не влияло.
|
||||
_ENV_KEYS = ("BOT_TOKEN", "APP_ENV", "ADMIN_IDS", "CHAT_ID", "ALERT_CHAT_ID")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clean_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
for key in _ENV_KEYS:
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
|
||||
|
||||
def _settings(monkeypatch: pytest.MonkeyPatch, **env: str) -> Settings:
|
||||
for key, value in env.items():
|
||||
monkeypatch.setenv(key, value)
|
||||
return Settings(_env_file=None)
|
||||
|
||||
|
||||
def test_admin_ids_empty(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
assert _settings(monkeypatch, BOT_TOKEN="1:a", ADMIN_IDS="").admin_ids == []
|
||||
|
||||
|
||||
def test_admin_ids_not_set(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
assert _settings(monkeypatch, BOT_TOKEN="1:a").admin_ids == []
|
||||
|
||||
|
||||
def test_admin_ids_comma(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
assert _settings(monkeypatch, BOT_TOKEN="1:a", ADMIN_IDS="1,2, 3").admin_ids == [1, 2, 3]
|
||||
|
||||
|
||||
def test_admin_ids_json_like(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
assert _settings(monkeypatch, BOT_TOKEN="1:a", ADMIN_IDS="[7, 8]").admin_ids == [7, 8]
|
||||
|
||||
|
||||
def test_chat_ids_empty_are_zero(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
settings = _settings(monkeypatch, BOT_TOKEN="1:a", CHAT_ID="", ALERT_CHAT_ID="")
|
||||
assert settings.chat_id == 0
|
||||
assert settings.alert_chat_id == 0
|
||||
|
||||
|
||||
def test_chat_id_value(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
assert _settings(monkeypatch, BOT_TOKEN="1:a", CHAT_ID="-1001234567890").chat_id == -1001234567890
|
||||
|
||||
|
||||
def test_bot_token_required(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
Settings(_env_file=None)
|
||||
|
|
@ -7,4 +7,4 @@ from common import __version__
|
|||
|
||||
def test_version_is_set():
|
||||
assert __version__
|
||||
assert __version__ == "0.2"
|
||||
assert __version__ == "0.2.5"
|
||||
|
|
|
|||
Loading…
Reference in New Issue