CI / ruff · mypy · pytest (pull_request) Failing after 3m40s
- app/pipeline: протокол CIProvider, клиент Gitea Actions (прогон по head_sha, упавший шаг, лог джоба, ретраи), PipelineWatcher — опрос только при незавершённых push, сообщение о push обновляется CI ⏳ → ✅/❌ → CD 🚀/⛔, автору — отдельные уведомления - деплой сервисов — только после зелёного CI; старый push не откатывает сервис, который развернёт более новый; после рестарта бот продолжает с того же места - CI_PROVIDER=none — прежнее поведение (деплой сразу после push) - шаблон tgit-uploads: .gitea/workflows/check.yml; .gitea/ защищён от правок из бота - миграция 0002: старые push помечены skipped, чтобы не слать им «таймаут CI» - ADR-016, ARCHITECTURE §2.5/§5.2, README, DEPLOY, .env.example Refs #11 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
93 lines
3.8 KiB
Python
93 lines
3.8 KiB
Python
from pathlib import Path
|
|
|
|
import pytest
|
|
from aiogram.methods import EditMessageText, SendMessage
|
|
from aiogram.types import InlineKeyboardMarkup
|
|
|
|
from app.access.service import AccessService
|
|
from app.bot.routers.commit import Act, CommitCb
|
|
from app.db.session import SessionMaker
|
|
from app.main import build_dispatcher
|
|
from app.repo.git import UploadsRepo
|
|
from app.texts import Texts
|
|
from tests.conftest import ADMIN, git
|
|
from tests.fakes import TgClient
|
|
|
|
|
|
@pytest.fixture
|
|
def tg(texts: Texts, sessions: SessionMaker, access: AccessService, repo: UploadsRepo) -> TgClient:
|
|
return TgClient(build_dispatcher(texts, sessions, access, repo, "https://example.test/commit/{sha}"))
|
|
|
|
|
|
def cb(action: Act, i: int = 0) -> str:
|
|
return CommitCb(a=action, i=i).pack()
|
|
|
|
|
|
def labels(markup: object) -> list[str]:
|
|
assert isinstance(markup, InlineKeyboardMarkup)
|
|
return [b.text for row in markup.inline_keyboard for b in row]
|
|
|
|
|
|
async def test_message_then_push_lands_in_remote(tg: TgClient, texts: Texts, remote: Path, tmp_path: Path) -> None:
|
|
t = texts.commit
|
|
await tg.send(ADMIN, texts.menu.commit)
|
|
await tg.press(ADMIN, cb(Act.NEW_FILE))
|
|
await tg.send(ADMIN, "notes.txt")
|
|
await tg.send(ADMIN, "привет из телеграма")
|
|
await tg.press(ADMIN, cb(Act.DRAFT))
|
|
draft = tg.session.sent(EditMessageText)[-1]
|
|
assert t.message_missing in draft.text and t.push_button not in labels(draft.reply_markup)
|
|
|
|
await tg.press(ADMIN, cb(Act.MESSAGE))
|
|
await tg.send(ADMIN, "x" * 80) # first line too long
|
|
assert tg.session.sent(SendMessage)[-1].text == t.bad_message
|
|
await tg.send(ADMIN, "docs: заметки")
|
|
with_message = tg.session.sent(SendMessage)[-1]
|
|
assert t.message_current.format(message="docs: заметки") in with_message.text
|
|
assert t.push_button in labels(with_message.reply_markup)
|
|
|
|
await tg.press(ADMIN, cb(Act.PUSH))
|
|
assert tg.session.sent(EditMessageText)[-1].text.startswith("Запушить в main?")
|
|
await tg.press(ADMIN, cb(Act.PUSH_GO))
|
|
|
|
pushing, done = tg.session.sent(EditMessageText)[-2:]
|
|
assert pushing.text == t.pushing
|
|
assert done.text.startswith("✅ Запушено: ") and "docs: заметки" in done.text
|
|
assert "https://example.test/commit/" in done.text and "CI" not in done.text # no CI configured
|
|
|
|
clone = tmp_path / "verify"
|
|
git(tmp_path, "clone", "-q", str(remote), str(clone))
|
|
assert (clone / "notes.txt").read_text(encoding="utf-8") == "привет из телеграма"
|
|
|
|
# the draft is gone after a push
|
|
tg.session.requests.clear()
|
|
await tg.send(ADMIN, texts.menu.commit)
|
|
assert not any(label.startswith("🧾") for label in labels(tg.session.sent(SendMessage)[-1].reply_markup))
|
|
|
|
|
|
async def test_commit_button_in_folder_asks_message_then_confirms(
|
|
tg: TgClient, texts: Texts, remote: Path, tmp_path: Path
|
|
) -> None:
|
|
t = texts.commit
|
|
await tg.send(ADMIN, texts.menu.commit)
|
|
await tg.press(ADMIN, cb(Act.NEW_FILE))
|
|
await tg.send(ADMIN, "quick.txt")
|
|
await tg.send(ADMIN, "быстро")
|
|
|
|
folder = tg.session.sent(SendMessage)[-1]
|
|
assert t.commit_button.format(count=1) in labels(folder.reply_markup)
|
|
|
|
await tg.press(ADMIN, cb(Act.COMMIT))
|
|
assert tg.session.sent(EditMessageText)[-1].text == t.ask_message
|
|
await tg.send(ADMIN, "feat: быстрый коммит")
|
|
|
|
confirm = tg.session.sent(SendMessage)[-1]
|
|
assert confirm.text.startswith("Запушить в main?") and "feat: быстрый коммит" in confirm.text
|
|
assert labels(confirm.reply_markup) == [t.push_go, t.edit_message, texts.menu.back]
|
|
|
|
await tg.press(ADMIN, cb(Act.PUSH_GO))
|
|
assert tg.session.sent(EditMessageText)[-1].text.startswith("✅ Запушено: ")
|
|
clone = tmp_path / "verify2"
|
|
git(tmp_path, "clone", "-q", str(remote), str(clone))
|
|
assert (clone / "quick.txt").read_text(encoding="utf-8") == "быстро"
|