Skip to content

Commit b0bbd0c

Browse files
committed
feat: adding tests
1 parent 253edc7 commit b0bbd0c

File tree

16 files changed

+277
-98
lines changed

16 files changed

+277
-98
lines changed

.dockerignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
# Python
21
__pycache__
32
*.pyc
4-
.mypy_cache
3+
.*_cache
54
.coverage
65
htmlcov
76
.venv

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# Python
22
__pycache__
33
*.pyc
4-
.mypy_cache
5-
.ruff_cache
4+
.*_cache
65
.coverage
76
htmlcov
87
.venv

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
2626
ENV PYTHONPATH=/app
2727

2828
COPY ./pyproject.toml ./uv.lock /app/
29-
COPY ./app /app/app
29+
COPY ./src /app/src/
3030

3131
# Sync the project
3232
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers
@@ -37,5 +37,5 @@ ENV PORT 8080
3737

3838
EXPOSE ${PORT}
3939

40-
CMD ["sh", "-c", "fastapi run --port ${PORT} app/main.py"]
40+
CMD ["sh", "-c", "fastapi run --port ${PORT} src/app/main.py"]
4141

Makefile

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,25 @@ install:
22
@uv sync --all-extras
33

44
dev: install
5-
@fastapi dev app/main.py
5+
@fastapi dev src/app/main.py
66

77
serve: install
8-
@fastapi run app/main.py
8+
@fastapi run src/app/main.py
9+
10+
test:
11+
@pytest -rxP
912

1013
docker:
1114
@docker compose up -d
1215

1316
format: install
14-
@ruff check app --fix
15-
@ruff format app
17+
@ruff check src/app --fix
18+
@ruff format src/app
1619

1720
lint: install
18-
@mypy app
19-
@ruff check app
20-
@ruff format app --check
21+
@mypy src/app
22+
@ruff check src/app
23+
@ruff format src/app --check
2124

2225
lock: install
2326
@uv lock

__test__/conftest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import pytest
2+
3+
from pytest_asyncio import is_async_test
4+
5+
6+
def pytest_collection_modifyitems(items):
7+
pytest_asyncio_tests = (item for item in items if is_async_test(item))
8+
session_scope_marker = pytest.mark.asyncio(loop_scope="session")
9+
for async_test in pytest_asyncio_tests:
10+
async_test.add_marker(session_scope_marker, append=False)

__test__/todos_test.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import asyncio
2+
import pytest
3+
from app.redis import redis
4+
from app.components.todos.store import TodoStatus, TodoStore
5+
6+
todos = TodoStore(redis)
7+
8+
@pytest.fixture(autouse=True)
9+
async def run_around_each():
10+
await todos.initialize()
11+
yield
12+
await todos.delete_all()
13+
await todos.drop_index()
14+
15+
async def test_crud_for_single_todo():
16+
created_todo = await todos.create(None, "Take out the trash")
17+
todo_id = created_todo.id
18+
19+
assert created_todo.value.name == "Take out the trash"
20+
assert created_todo.value.status == "todo"
21+
22+
read_todo = await todos.one(todo_id)
23+
24+
assert created_todo.value.name == read_todo.name
25+
assert created_todo.value.status == read_todo.status
26+
27+
updated_todo = await todos.update(todo_id, TodoStatus.in_progress)
28+
29+
assert updated_todo.name == read_todo.name
30+
assert updated_todo.status == TodoStatus.in_progress
31+
assert updated_todo.updated_date > updated_todo.created_date
32+
assert updated_todo.updated_date > read_todo.updated_date
33+
34+
await todos.delete(todo_id)
35+
36+
37+
async def test_crud_for_multiple_todos():
38+
all_todo_names = [
39+
"Take out the trash",
40+
"Vacuum downstairs",
41+
"Fold the laundry",
42+
]
43+
44+
coros = []
45+
46+
for todo in all_todo_names:
47+
coros.append(todos.create(None, todo))
48+
await asyncio.gather(*coros)
49+
50+
all_todos = await todos.all()
51+
52+
assert all_todos.total == 3
53+
54+
for todo in all_todos.documents:
55+
assert todo.value.name in all_todo_names

pyproject.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ dependencies = [
2424
"redis[hiredis]>=5.2.1",
2525
]
2626

27+
[dependency-groups]
28+
dev = [
29+
"pytest>=8.3.4",
30+
"pytest-asyncio>=0.24.0",
31+
]
32+
2733
[project.urls]
2834
Repository = "https://github.com/redis-developer/redis-starter-python.git"
2935
"Bug Tracker" = "https://github.com/redis-developer/redis-starter-python/issues"
@@ -34,8 +40,14 @@ dev = [
3440
"ruff<1.0.0,>=0.2.2",
3541
]
3642

43+
[tool.pytest.ini_options]
44+
pythonpath = "src"
45+
asyncio_default_fixture_loop_scope = "session"
46+
asyncio_mode = "auto"
47+
3748
[tool.mypy]
3849
strict = true
50+
disallow_untyped_calls = false
3951
exclude = ["venv", ".venv"]
4052

4153
[tool.ruff]
File renamed without changes.
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
from typing import AsyncIterator, Never
2+
23
from fastapi import APIRouter, FastAPI
34
from fastapi.concurrency import asynccontextmanager
45
from pydantic import BaseModel
56

7+
from app.components.todos.store import Todo, TodoDocument, Todos, TodoStatus, TodoStore
68
from app.redis import redis
7-
from app.components.todos.store import Todo, TodoDocument, TodoStatus, TodoStore, Todos
89

910
todos = TodoStore(redis)
1011

12+
1113
@asynccontextmanager
1214
async def lifespan(_: FastAPI) -> AsyncIterator[Never]:
1315
# before
@@ -16,35 +18,44 @@ async def lifespan(_: FastAPI) -> AsyncIterator[Never]:
1618
# after
1719
return
1820

21+
1922
router = APIRouter(lifespan=lifespan)
2023

24+
2125
@router.get("/", tags=["todos"])
2226
async def all() -> Todos:
2327
return await todos.all()
2428

29+
2530
@router.get("/search", tags=["todos"])
2631
async def search(name: str | None = None, status: TodoStatus | None = None) -> Todos:
2732
return await todos.search(name, status)
2833

34+
2935
@router.get("/{id}", tags=["todos"])
3036
async def one(id: str) -> Todo:
3137
return await todos.one(id)
3238

39+
3340
class CreateTodo(BaseModel):
3441
id: str | None = None
3542
name: str
3643

44+
3745
@router.post("/", tags=["todos"])
3846
async def create(todo: CreateTodo) -> TodoDocument:
3947
return await todos.create(todo.id, todo.name)
4048

49+
4150
class UpdateTodo(BaseModel):
4251
status: TodoStatus
4352

53+
4454
@router.patch("/{id}", tags=["todos"])
4555
async def update(id: str, todo: UpdateTodo) -> Todo:
4656
return await todos.update(id, todo.status)
4757

58+
4859
@router.delete("/{id}", tags=["todos"])
4960
async def delete(id: str) -> None:
5061
return await todos.delete(id)

0 commit comments

Comments
 (0)