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>
76 lines
2.0 KiB
Python
76 lines
2.0 KiB
Python
import pytest
|
|
|
|
from app.repo.validate import (
|
|
ValidationError,
|
|
check_content,
|
|
check_name,
|
|
diff_stat,
|
|
is_protected,
|
|
join,
|
|
python_syntax,
|
|
strip_fence,
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("name", ["main.py", "a-b_c.1", ".gitignore", "X" * 100])
|
|
def test_good_names(name: str) -> None:
|
|
assert check_name(f" {name} ") == name
|
|
|
|
|
|
@pytest.mark.parametrize("name", ["", ".", "..", "a/b", "привет.py", "a b", "X" * 101, "a;rm"])
|
|
def test_bad_names(name: str) -> None:
|
|
with pytest.raises(ValidationError):
|
|
check_name(name)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("path", "protected"),
|
|
[
|
|
(".sourcecraft/ci.yaml", True),
|
|
(".gitea/workflows/check.yml", True),
|
|
(".tgit/x.py", True),
|
|
("ruff.toml", True),
|
|
(".sourcecraft", True),
|
|
("services/a/ruff.toml", False),
|
|
("README.md", False),
|
|
("services/a/main.py", False),
|
|
],
|
|
)
|
|
def test_protected_paths(path: str, protected: bool) -> None:
|
|
assert is_protected(path) is protected
|
|
|
|
|
|
def test_join() -> None:
|
|
assert join("", "a.py") == "a.py" and join("s/x", "a.py") == "s/x/a.py"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("text", "expected"),
|
|
[
|
|
("```python\nprint(1)\n```", "print(1)"),
|
|
("```\na\nb\n```", "a\nb"),
|
|
("print(1)", "print(1)"),
|
|
("x ``` y", "x ``` y"),
|
|
],
|
|
)
|
|
def test_strip_fence(text: str, expected: str) -> None:
|
|
assert strip_fence(text) == expected
|
|
|
|
|
|
def test_content_checks() -> None:
|
|
assert check_content("привет".encode()) == "привет"
|
|
for data, reason in [(b"a\0b", "binary"), (b"\xff\xfe", "encoding"), (b"x" * (100 * 1024 + 1), "too_big")]:
|
|
with pytest.raises(ValidationError, match=reason):
|
|
check_content(data)
|
|
|
|
|
|
def test_python_syntax_only_for_py() -> None:
|
|
issue = python_syntax("a.py", "def f(:\n pass\n")
|
|
assert issue is not None and issue.line == 1
|
|
assert python_syntax("a.py", "x = 1\n") is None
|
|
assert python_syntax("a.txt", "def f(:") is None
|
|
|
|
|
|
def test_diff_stat() -> None:
|
|
assert diff_stat("a\nb\nc", "a\nB\nc\nd") == (2, 1)
|