[QG] Add quality gates on main branch
[+] add lint pipeline for ruff isort and black checks [+] add types pipeline for mypy check [+] add tests pipeline for pytest check with coverage less 70% blocker QG [+] add some tests fo QG pass
This commit is contained in:
13
.woodpecker/lints.yaml
Normal file
13
.woodpecker/lints.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
when:
|
||||||
|
- event: [push, pull_request]
|
||||||
|
branch: main
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: lint
|
||||||
|
image: python:3.11
|
||||||
|
commands:
|
||||||
|
- pip install uv
|
||||||
|
- uv sync --no-dev --only-group lints
|
||||||
|
- uv run black --check .
|
||||||
|
- uv run ruff check .
|
||||||
|
- uv run isort --check-only .
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
when:
|
|
||||||
- event: push
|
|
||||||
branch: main
|
|
||||||
- event: pull_request
|
|
||||||
branch: main
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: install-deps
|
|
||||||
image: python:3.11-slim
|
|
||||||
commands:
|
|
||||||
- pip install --upgrade pip
|
|
||||||
- pip install flake8 black isort # можно добавить pylint, mypy и др.
|
|
||||||
|
|
||||||
- name: flake8
|
|
||||||
image: python:3.11-slim
|
|
||||||
commands:
|
|
||||||
- flake8 . --max-line-length=120 --exclude=.venv,venv,__pycache__
|
|
||||||
|
|
||||||
- name: black
|
|
||||||
image: python:3.11-slim
|
|
||||||
commands:
|
|
||||||
- black --check --diff .
|
|
||||||
|
|
||||||
- name: isort
|
|
||||||
image: python:3.11-slim
|
|
||||||
commands:
|
|
||||||
- isort --check-only --diff .
|
|
||||||
|
|
||||||
- name: mypy
|
|
||||||
image: python:3.11-slim
|
|
||||||
commands:
|
|
||||||
- pip install mypy
|
|
||||||
- mypy .
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
when:
|
|
||||||
- event: push
|
|
||||||
branch: main
|
|
||||||
- event: pull_request
|
|
||||||
branch: main
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: install
|
|
||||||
image: python:3.11-slim
|
|
||||||
commands:
|
|
||||||
- pip install --upgrade pip
|
|
||||||
- pip install pytest pytest-cov coverage
|
|
||||||
- if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
|
||||||
|
|
||||||
- name: test
|
|
||||||
image: python:3.11-slim
|
|
||||||
commands:
|
|
||||||
- pytest --cov=src --cov-report=term --cov-report=xml tests/
|
|
||||||
|
|
||||||
12
.woodpecker/tests.yaml
Normal file
12
.woodpecker/tests.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
when:
|
||||||
|
- event: [push, pull_request]
|
||||||
|
branch: main
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: tests
|
||||||
|
image: python:3.11
|
||||||
|
commands:
|
||||||
|
- pip install uv
|
||||||
|
- uv sync --no-dev --group tests
|
||||||
|
- uv run pytest --cov=app --cov-fail-under=70 --cov-report=term-missing
|
||||||
|
|
||||||
11
.woodpecker/types.yaml
Normal file
11
.woodpecker/types.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
when:
|
||||||
|
- event: [push, pull_request]
|
||||||
|
branch: main
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: types
|
||||||
|
image: python:3.11
|
||||||
|
commands:
|
||||||
|
- pip install uv
|
||||||
|
- uv sync --no-dev --only-group types
|
||||||
|
- uv run mypy .
|
||||||
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
BIN
app/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
app/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/__pycache__/main.cpython-313.pyc
Normal file
BIN
app/__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
@@ -1,6 +1,7 @@
|
|||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
@@ -17,6 +18,5 @@ def main():
|
|||||||
uvicorn.run(app_factory, factory=True, host="0.0.0.0", port=8000)
|
uvicorn.run(app_factory, factory=True, host="0.0.0.0", port=8000)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
@@ -11,10 +11,31 @@ dependencies = [
|
|||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"isort>=8.0.1",
|
{include-group = "lints"},
|
||||||
"mypy>=1.20.1",
|
{include-group = "tests"},
|
||||||
|
{include-group = "types"},
|
||||||
"pre-commit>=4.5.1",
|
"pre-commit>=4.5.1",
|
||||||
|
]
|
||||||
|
tests = [
|
||||||
|
"httpx>=0.28.1",
|
||||||
"pytest>=9.0.3",
|
"pytest>=9.0.3",
|
||||||
"pytest-asyncio>=1.3.0",
|
"pytest-asyncio>=1.3.0",
|
||||||
"ruff>=0.15.11",
|
"pytest-cov>=7.1.0",
|
||||||
]
|
]
|
||||||
|
lints = [
|
||||||
|
"black>=23.7.0",
|
||||||
|
"ruff>=0.15.11",
|
||||||
|
"isort>=8.0.1",
|
||||||
|
]
|
||||||
|
types = [
|
||||||
|
"mypy>=1.20.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
asyncio_mode = "auto"
|
||||||
|
asyncio_default_fixture_loop_scope = "function"
|
||||||
|
addopts = "--cov=src --cov-report=term"
|
||||||
|
pythonpath = "."
|
||||||
|
testpaths = "tests"
|
||||||
|
xfail_strict = true
|
||||||
|
|
||||||
|
|||||||
BIN
tests/__pycache__/test_app_run.cpython-313-pytest-9.0.3.pyc
Normal file
BIN
tests/__pycache__/test_app_run.cpython-313-pytest-9.0.3.pyc
Normal file
Binary file not shown.
41
tests/test_app_run.py
Normal file
41
tests/test_app_run.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from contextlib import asynccontextmanager
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
# Предполагаем, что тестируемый модуль называется `myapp`
|
||||||
|
# Импортируем из него нужные объекты
|
||||||
|
from app.main import app_factory, lifespan, main
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_lifespan():
|
||||||
|
"""Проверяет, что lifespan является корректным асинхронным контекстным менеджером."""
|
||||||
|
app = FastAPI()
|
||||||
|
# Проверяем, что lifespan - это asynccontextmanager
|
||||||
|
assert isinstance(lifespan, asynccontextmanager(lifespan).__class__)
|
||||||
|
|
||||||
|
# Проверяем, что контекстный менеджер работает (ничего не ломается)
|
||||||
|
async with lifespan(app):
|
||||||
|
pass # Просто убеждаемся, что yield отрабатывает
|
||||||
|
|
||||||
|
|
||||||
|
def test_app_factory():
|
||||||
|
"""Проверяет, что app_factory создаёт правильное приложение FastAPI с переданным lifespan."""
|
||||||
|
app = app_factory()
|
||||||
|
assert isinstance(app, FastAPI)
|
||||||
|
# Проверяем, что lifespan приложения установлен на функцию lifespan
|
||||||
|
assert app.router.lifespan_context == lifespan
|
||||||
|
|
||||||
|
|
||||||
|
@patch("app.main.uvicorn.run")
|
||||||
|
def test_main(mock_uvicorn_run):
|
||||||
|
"""Проверяет, что main вызывает uvicorn.run с правильными параметрами."""
|
||||||
|
main()
|
||||||
|
mock_uvicorn_run.assert_called_once_with(
|
||||||
|
app_factory,
|
||||||
|
factory=True,
|
||||||
|
host="0.0.0.0",
|
||||||
|
port=8000, # Предполагаемый порт (в коде обрезано, но обычно 8000)
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user