feat(api+testing): select todo by member_id and id
- Added test code coverage with `pytest-cov` - Added comments for member model functions
This commit is contained in:
parent
9abd4c4867
commit
60b6babc15
@ -1,5 +1,16 @@
|
|||||||
# Backend Technical Write Up
|
# Backend Technical Write Up
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
- Install `pdm`
|
||||||
|
- Install dependencies with `pdm sync`
|
||||||
|
- Run development backend with `pdm run dev`
|
||||||
|
- Run tests with `pdm run test`
|
||||||
|
|
||||||
|
### Setup for Developmentk
|
||||||
|
|
||||||
|
- run `eval $(pdm venv activate in-project)` to activate the virtual env.
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
- Use FastAPI's `router` to organize different API routes
|
- Use FastAPI's `router` to organize different API routes
|
||||||
|
80
backend/pdm.lock
generated
80
backend/pdm.lock
generated
@ -5,7 +5,7 @@
|
|||||||
groups = ["default", "dev"]
|
groups = ["default", "dev"]
|
||||||
strategy = ["cross_platform"]
|
strategy = ["cross_platform"]
|
||||||
lock_version = "4.4.1"
|
lock_version = "4.4.1"
|
||||||
content_hash = "sha256:334c1bb9213ebdcc88123f9dbc50d1c3e2548a23775c8b3c5c27bd01e5fcc9ff"
|
content_hash = "sha256:a705bf9c8354b802c6aa86d8d35e46e83b9137cf3663c22e7f1442c07eecc38a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "annotated-types"
|
name = "annotated-types"
|
||||||
@ -134,6 +134,70 @@ files = [
|
|||||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.4.3"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "Code coverage measurement for Python"
|
||||||
|
files = [
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"},
|
||||||
|
{file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"},
|
||||||
|
{file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.4.3"
|
||||||
|
extras = ["toml"]
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "Code coverage measurement for Python"
|
||||||
|
dependencies = [
|
||||||
|
"coverage==7.4.3",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"},
|
||||||
|
{file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"},
|
||||||
|
{file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"},
|
||||||
|
{file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"},
|
||||||
|
{file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dnspython"
|
name = "dnspython"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@ -492,6 +556,20 @@ files = [
|
|||||||
{file = "pytest_asyncio-0.23.5-py3-none-any.whl", hash = "sha256:4e7093259ba018d58ede7d5315131d21923a60f8a6e9ee266ce1589685c89eac"},
|
{file = "pytest_asyncio-0.23.5-py3-none-any.whl", hash = "sha256:4e7093259ba018d58ede7d5315131d21923a60f8a6e9ee266ce1589685c89eac"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-cov"
|
||||||
|
version = "4.1.0"
|
||||||
|
requires_python = ">=3.7"
|
||||||
|
summary = "Pytest plugin for measuring coverage."
|
||||||
|
dependencies = [
|
||||||
|
"coverage[toml]>=5.2.1",
|
||||||
|
"pytest>=4.6",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"},
|
||||||
|
{file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "python-dateutil"
|
name = "python-dateutil"
|
||||||
version = "2.8.2"
|
version = "2.8.2"
|
||||||
|
@ -16,6 +16,7 @@ dependencies = [
|
|||||||
"redis>=5.0.1",
|
"redis>=5.0.1",
|
||||||
"psycopg[pool]>=3.1.18",
|
"psycopg[pool]>=3.1.18",
|
||||||
"fastapi-limiter>=0.1.6",
|
"fastapi-limiter>=0.1.6",
|
||||||
|
"pytest-cov>=4.1.0",
|
||||||
]
|
]
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@ -71,4 +72,4 @@ recreate-db-base = "python3 src/neo_neo_todo/utils/database.py"
|
|||||||
migration-base = "python3 src/neo_neo_todo/migrations/0.py"
|
migration-base = "python3 src/neo_neo_todo/migrations/0.py"
|
||||||
recreate-db = {composite = ["recreate-db-base", "migration-base"], env_file = "development.env"}
|
recreate-db = {composite = ["recreate-db-base", "migration-base"], env_file = "development.env"}
|
||||||
generate-test-data = "python3 src/neo_neo_todo/migrations/test_data.py"
|
generate-test-data = "python3 src/neo_neo_todo/migrations/test_data.py"
|
||||||
test = {composite = ["recreate-db-base", "migration-base", "generate-test-data", "pytest tests/"], env_file = "testing.env"}
|
test = {composite = ["recreate-db-base", "migration-base", "generate-test-data", "pytest tests/ --cov=src.neo_neo_todo"], env_file = "testing.env"}
|
||||||
|
@ -91,6 +91,11 @@ async def insert_member(
|
|||||||
async def update_member_password(
|
async def update_member_password(
|
||||||
db: AsyncConnectionPool, id: int, password_hash: str
|
db: AsyncConnectionPool, id: int, password_hash: str
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Updates a member's password (their password_hash to be exact)
|
||||||
|
|
||||||
|
Returns a bool, True if successful and False if not
|
||||||
|
"""
|
||||||
async with db.connection() as conn:
|
async with db.connection() as conn:
|
||||||
async with conn.cursor(row_factory=class_row(Member)) as curr:
|
async with conn.cursor(row_factory=class_row(Member)) as curr:
|
||||||
query = sql.SQL(
|
query = sql.SQL(
|
||||||
@ -113,6 +118,11 @@ async def update_member_password(
|
|||||||
|
|
||||||
|
|
||||||
async def update_member_email_verified(db: AsyncConnectionPool, id: int) -> bool:
|
async def update_member_email_verified(db: AsyncConnectionPool, id: int) -> bool:
|
||||||
|
"""
|
||||||
|
Updates a member's email verified status
|
||||||
|
|
||||||
|
Returns a bool, True if successful and False if not
|
||||||
|
"""
|
||||||
async with db.connection() as conn:
|
async with db.connection() as conn:
|
||||||
async with conn.cursor(row_factory=class_row(Member)) as curr:
|
async with conn.cursor(row_factory=class_row(Member)) as curr:
|
||||||
query = sql.SQL(
|
query = sql.SQL(
|
||||||
|
@ -18,6 +18,11 @@ class Todo:
|
|||||||
async def select_todos(
|
async def select_todos(
|
||||||
db: AsyncConnectionPool, member_id: int, complete: bool | None = None
|
db: AsyncConnectionPool, member_id: int, complete: bool | None = None
|
||||||
) -> list[Todo]:
|
) -> list[Todo]:
|
||||||
|
"""
|
||||||
|
Select multiple Todo(s) from a member_id
|
||||||
|
|
||||||
|
Return a list of Todos (can be empty list)
|
||||||
|
"""
|
||||||
async with db.connection() as conn:
|
async with db.connection() as conn:
|
||||||
async with conn.cursor(row_factory=class_row(Todo)) as curr:
|
async with conn.cursor(row_factory=class_row(Todo)) as curr:
|
||||||
|
|
||||||
@ -43,3 +48,27 @@ async def select_todos(
|
|||||||
todos = await curr.fetchall()
|
todos = await curr.fetchall()
|
||||||
|
|
||||||
return todos
|
return todos
|
||||||
|
|
||||||
|
|
||||||
|
async def select_todo(db: AsyncConnectionPool, id: int, member_id: int) -> Todo | None:
|
||||||
|
"""
|
||||||
|
Find exactly one todo task that matches id and member_id
|
||||||
|
in todos list in PostgreSQL database
|
||||||
|
|
||||||
|
Return a Todo object or None
|
||||||
|
"""
|
||||||
|
async with db.connection() as conn:
|
||||||
|
async with conn.cursor(row_factory=class_row(Todo)) as curr:
|
||||||
|
query = sql.SQL(
|
||||||
|
"""
|
||||||
|
SELECT id, complete, due, task
|
||||||
|
FROM todos
|
||||||
|
WHERE id = (%s) AND member_id = (%s)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
await curr.execute(query, (id, member_id))
|
||||||
|
|
||||||
|
member = await curr.fetchone()
|
||||||
|
|
||||||
|
return None if member is None else member
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
from src.neo_neo_todo.models.todo import select_todos
|
import asyncio
|
||||||
|
|
||||||
|
from src.neo_neo_todo.models.todo import select_todo, select_todos
|
||||||
|
|
||||||
|
|
||||||
async def test_model_todo_select_todos(db_pool):
|
async def test_model_todo_select_todos(db_pool):
|
||||||
@ -17,3 +19,23 @@ async def test_model_todo_select_todos(db_pool):
|
|||||||
todos_list_non_existent_member_id = await select_todos(db_pool, 12341234)
|
todos_list_non_existent_member_id = await select_todos(db_pool, 12341234)
|
||||||
|
|
||||||
assert len(todos_list_non_existent_member_id) == 0
|
assert len(todos_list_non_existent_member_id) == 0
|
||||||
|
|
||||||
|
|
||||||
|
async def test_model_todo_select_todo(db_pool):
|
||||||
|
todo_queries = [
|
||||||
|
select_todo(db_pool, member_id=1, id=1),
|
||||||
|
select_todo(db_pool, member_id=1, id=2),
|
||||||
|
select_todo(db_pool, member_id=1, id=3),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Now you can await all the select_todo calls
|
||||||
|
todos = await asyncio.gather(*todo_queries)
|
||||||
|
|
||||||
|
for i in range(len(todos)):
|
||||||
|
assert todos[i] is not None
|
||||||
|
assert todos[i].id == (i + 1) # type: ignore
|
||||||
|
assert not todos[i].complete # type: ignore
|
||||||
|
|
||||||
|
non_existent_member_id_todo = await select_todo(db_pool, member_id=1234123, id=1)
|
||||||
|
|
||||||
|
assert non_existent_member_id_todo is None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user