diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 5c56a0b..8d74527 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,35 +1,41 @@ name: CI on: push: - branches: [feature/**, bugfix/**, hotfix/**] + branches: ["feature/**", "bugfix/**", "hotfix/**"] pull_request: branches: [main] jobs: lint: - runs-on: ubuntu-latest + runs-on: self-hosted steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - run: pip install ruff - - run: ruff check src/api/ + - name: Install dependencies + run: | + python3 -m pip install --user --upgrade pip setuptools wheel + python3 -m pip install --user ".[dev]" + - name: Lint + run: | + export PATH="$HOME/.local/bin:$PATH" + ruff check src/ test: - runs-on: ubuntu-latest + runs-on: self-hosted steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - run: pip install -r src/api/requirements.txt - - run: pip install pytest - - run: pytest tests/ -v + - name: Install dependencies + run: | + python3 -m pip install --user --upgrade pip setuptools wheel + python3 -m pip install --user ".[dev]" + - name: Test + run: | + export PATH="$HOME/.local/bin:$PATH" + pytest tests/ build: - runs-on: ubuntu-latest + runs-on: self-hosted needs: [lint, test] steps: - uses: actions/checkout@v4 - - run: docker build -t enduro-trails:ci . + - name: Build Docker image + run: docker build -t enduro-trails:ci . diff --git a/.task.md b/.task.md new file mode 100644 index 0000000..4782583 --- /dev/null +++ b/.task.md @@ -0,0 +1,36 @@ +Прочитай CLAUDE.md. Твоя задача — bootstrap проекта для CI: + +1. Создай pyproject.toml в корне с секциями: + - [project] name="enduro-trails", version="0.1.0", requires-python=">=3.12" + - [project.optional-dependencies] dev = ["ruff>=0.4.0", "pytest>=8.0", "httpx>=0.27", "pytest-asyncio>=0.23"] + - [tool.ruff] target-version="py312", line-length=120 + - [tool.pytest.ini_options] asyncio_mode="auto", testpaths=["tests"] + +2. Создай tests/unit/test_health.py: + import pytest + from httpx import AsyncClient, ASGITransport + from src.api.main import app + + @pytest.mark.asyncio + async def test_health_endpoint(): + transport = ASGITransport(app=app) + async with AsyncClient(transport=transport, base_url="http://test") as client: + resp = await client.get("/api/health") + assert resp.status_code == 200 + data = resp.json() + assert data["status"] == "ok" + +3. Создай tests/__init__.py и tests/unit/__init__.py (пустые файлы) + +4. Обнови .gitea/workflows/ci.yml: + - Используй образ python:3.12 для всех job + - Установка зависимостей: pip install -e ".[dev]" + - lint: ruff check src/ + - test: pytest tests/ + - build: docker build . + +5. Создай ветку feature/bootstrap, закоммить всё, запуш в origin. + +Коммит message: "feat: add pyproject.toml, dev dependencies, first unit test" +Push в ветку feature/bootstrap (НЕ в main). +Git remote использует http://localhost:3000/admin/enduro-trails.git diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9e1a6c9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,37 @@ +[project] +name = "enduro-trails" +version = "0.1.0" +requires-python = ">=3.10" +description = "Карта эндуро-маршрутов с рельефом, навигацией и слоями terrain/TRI/hillshade" +readme = "README.md" +dependencies = [ + "fastapi==0.111.0", + "uvicorn==0.29.0", + "shapely==2.0.4", + "mapbox-vector-tile==2.2.0", + "httpx==0.27.0", +] + +[project.optional-dependencies] +dev = [ + "ruff>=0.4.0", + "pytest>=8.0", + "httpx>=0.27", + "pytest-asyncio>=0.23", +] + +[build-system] +requires = ["setuptools>=68", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["."] +include = ["src*"] + +[tool.ruff] +target-version = "py310" +line-length = 120 + +[tool.pytest.ini_options] +asyncio_mode = "auto" +testpaths = ["tests"] diff --git a/src/api/main.py b/src/api/main.py index 42160ec..f2149fe 100644 --- a/src/api/main.py +++ b/src/api/main.py @@ -11,13 +11,13 @@ import os import math import struct import sqlite3 -import json + import itertools -from pathlib import Path + from shapely.geometry import LineString from typing import List -from functools import lru_cache + from fastapi import FastAPI, HTTPException, Response from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles @@ -780,7 +780,7 @@ async def post_scenic(req: ScenicRequest): if not scored_pois: # Нет красивых мест — строим просто кольцо через ближайшие грунтовки # Пробуем кольцо: старт → точка на расстоянии ~target_km/3 → старт - angle = 0 + _ = 0 third_dist = target_km / 3.0 mid_lat = lat + (third_dist / 111.0) mid_lon = lon @@ -1137,7 +1137,7 @@ async def post_route(req: RouteRequest): # Открываем БД один раз для всех маршрутов try: conn = get_db() - except Exception as e: + except Exception: conn = None routes_out = [] diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/test_health.py b/tests/unit/test_health.py new file mode 100644 index 0000000..1435507 --- /dev/null +++ b/tests/unit/test_health.py @@ -0,0 +1,13 @@ +import pytest +from httpx import AsyncClient, ASGITransport +from src.api.main import app + + +@pytest.mark.asyncio +async def test_health_endpoint(): + transport = ASGITransport(app=app) + async with AsyncClient(transport=transport, base_url="http://test") as client: + resp = await client.get("/api/health") + assert resp.status_code == 200 + data = resp.json() + assert data["status"] == "ok"