From 7f8c78ab93a8b8d8db2578340143e98a5695bbb6 Mon Sep 17 00:00:00 2001 From: Simone Locci Date: Wed, 27 Aug 2025 18:28:04 +0200 Subject: [PATCH 1/4] Add tests --- tests/api/__init__.py | 0 tests/api/test_users.py | 24 ++++++++++++++++++++++++ tests/services/__init__.py | 0 tests/services/test_user_service.py | 18 ++++++++++++++++++ 4 files changed, 42 insertions(+) create mode 100644 tests/api/__init__.py create mode 100644 tests/api/test_users.py create mode 100644 tests/services/__init__.py create mode 100644 tests/services/test_user_service.py diff --git a/tests/api/__init__.py b/tests/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/api/test_users.py b/tests/api/test_users.py new file mode 100644 index 0000000..3f2719b --- /dev/null +++ b/tests/api/test_users.py @@ -0,0 +1,24 @@ +import httpx +import pytest + +from app.main import app + + +@pytest.mark.asyncio +async def test_create_user(): + transport = httpx.ASGITransport(app=app) + async with httpx.AsyncClient(transport=transport, base_url="http://test") as ac: + response = await ac.post("/api/v1/users/", json={"name": "Simone", "email": "simone@example.com"}) + assert response.status_code == 200 + data = response.json() + assert data["name"] == "Simone" + assert data["email"] == "simone@example.com" + +@pytest.mark.asyncio +async def test_get_users(): + transport = httpx.ASGITransport(app=app) + async with httpx.AsyncClient(transport=transport, base_url="http://test") as ac: + response = await ac.get("/api/v1/users/") + assert response.status_code == 200 + data = response.json() + assert isinstance(data, list) diff --git a/tests/services/__init__.py b/tests/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/services/test_user_service.py b/tests/services/test_user_service.py new file mode 100644 index 0000000..96575bd --- /dev/null +++ b/tests/services/test_user_service.py @@ -0,0 +1,18 @@ +import pytest +from app.models.user import UserCreate +from app.services.user_service import save_user, get_all_users + + +@pytest.mark.asyncio +async def test_save_user(): + user = UserCreate(name="Simone", email="simone@example.com") + saved_user = await save_user(user) + assert saved_user.name == "Simone" + assert saved_user.email == "simone@example.com" + + +@pytest.mark.asyncio +async def test_get_all_users_empty(): + users = await get_all_users() + assert isinstance(users, list) + assert len(users) > 0 From f29e629d68e4f1822eab6f366973b4966cab8f12 Mon Sep 17 00:00:00 2001 From: Simone Locci Date: Wed, 27 Aug 2025 18:33:52 +0200 Subject: [PATCH 2/4] Add Dockerfile --- .github/workflows/python.yml | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/python.yml diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 0000000..14b749b --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,42 @@ +name: Python CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + name: Test and Lint + runs-on: ubuntu-latest + + steps: + - name: ๐Ÿงพ Checkout repository + uses: actions/checkout@v4 + + - name: ๐Ÿ Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: ๐Ÿ“ฆ Install Poetry + run: | + pip install poetry + poetry config virtualenvs.create false + + - name: ๐Ÿ“„ Install dependencies + run: poetry install --no-interaction --no-root + + - name: โœ… Run tests + run: poetry run pytest -v + + - name: ๐Ÿงผ Check formatting + run: | + pip install black + black --check . + + - name: ๐Ÿ” Lint with ruff + run: | + pip install ruff + ruff check . From 2cadddee931033deb8f443420007d7c382ceed8c Mon Sep 17 00:00:00 2001 From: Simone Locci Date: Wed, 27 Aug 2025 18:36:40 +0200 Subject: [PATCH 3/4] Fix formatting --- app/api/users.py | 3 ++- app/main.py | 3 ++- tests/api/test_users.py | 5 ++++- tests/services/test_user_service.py | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/api/users.py b/app/api/users.py index 47053e6..a0e7bde 100644 --- a/app/api/users.py +++ b/app/api/users.py @@ -1,4 +1,5 @@ -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter + from app.models.user import User, UserCreate from app.services.user_service import save_user, get_all_users diff --git a/app/main.py b/app/main.py index bc014b6..39431a8 100644 --- a/app/main.py +++ b/app/main.py @@ -2,9 +2,10 @@ import sys from fastapi import FastAPI + from app.api import users -log_format = '%(asctime)s | %(levelname)-8s | %(name)-12s - %(message)s' +log_format = "%(asctime)s | %(levelname)-8s | %(name)-12s - %(message)s" logging.basicConfig(stream=sys.stdout, level=logging.DEBUG, format=log_format) app = FastAPI(title="Python FastAPI Template", version="0.1.0") diff --git a/tests/api/test_users.py b/tests/api/test_users.py index 3f2719b..f2f95e1 100644 --- a/tests/api/test_users.py +++ b/tests/api/test_users.py @@ -8,12 +8,15 @@ async def test_create_user(): transport = httpx.ASGITransport(app=app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as ac: - response = await ac.post("/api/v1/users/", json={"name": "Simone", "email": "simone@example.com"}) + response = await ac.post( + "/api/v1/users/", json={"name": "Simone", "email": "simone@example.com"} + ) assert response.status_code == 200 data = response.json() assert data["name"] == "Simone" assert data["email"] == "simone@example.com" + @pytest.mark.asyncio async def test_get_users(): transport = httpx.ASGITransport(app=app) diff --git a/tests/services/test_user_service.py b/tests/services/test_user_service.py index 96575bd..36c4afc 100644 --- a/tests/services/test_user_service.py +++ b/tests/services/test_user_service.py @@ -1,4 +1,5 @@ import pytest + from app.models.user import UserCreate from app.services.user_service import save_user, get_all_users From f9b4f44786a1e511921d143c28cd5908dbbeb426 Mon Sep 17 00:00:00 2001 From: Simone Locci Date: Wed, 27 Aug 2025 18:39:58 +0200 Subject: [PATCH 4/4] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ef5ec57..a8194f6 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Built to showcase best practices in Python backend development and serve as a so - ๐Ÿ”€ Routing with versioned endpoints - ๐Ÿ“„ OpenAPI docs auto-generated (`/docs`, `/redoc`) - โœ… Data validation and typing with Pydantic v2 -- ๐Ÿงช Async-ready test setup with `pytest`, `httpx`, `pytest-asyncio` (in progress) +- ๐Ÿงช Async-ready test setup with `pytest`, `httpx`, `pytest-asyncio` - ๐Ÿณ Minimal Docker support - ๐Ÿงฐ Designed as a reusable starter template