- новый файл: имя → содержимое текстом (```-обёртка снимается) или документом - новая папка (виртуальная, пока в ней нет файлов), замена с diff +N/−M, удаление - валидация: имя, ≤ 100 КБ, UTF-8, без NUL; .py — ast.parse с выбором «исправить / всё равно»; .sourcecraft/, .tgit/, ruff.toml из бота не меняются - DraftService: черновик в БД, 24 ч, ≤ 50 изменений; удаление нового файла убирает его из черновика - дерево показывает черновик поверх репозитория: 🆕 / ✏️ / 🗑, размер — как будет после пуша - экран черновика (A/M/D) с «↩️ убрать» Refs #26 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
75 lines
2.0 KiB
Python
75 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),
|
|
(".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)
|