Files
tgit/tests/bot/test_commit_push.py
ElebobaandClaude Opus 5.5 95a72437ec
CI / ruff · mypy · pytest (pull_request) Failing after 3m40s
feat(pipeline): уведомления CI/CD — PipelineWatcher, CI загрузок на Gitea Actions
- 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>
2026-10-19 00:24:08 +03:00

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") == "быстро"