feat(api+testing): update todo and delete todo

- Update python dependencies
This commit is contained in:
minhtrannhat 2024-03-10 03:33:33 -04:00
parent 60b6babc15
commit 4bafc48078
Signed by: minhtrannhat
GPG Key ID: E13CFA85C53F8062
3 changed files with 194 additions and 36 deletions

70
backend/pdm.lock generated
View File

@ -393,12 +393,12 @@ files = [
[[package]] [[package]]
name = "pluggy" name = "pluggy"
version = "1.3.0" version = "1.4.0"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "plugin and hook calling mechanisms for python" summary = "plugin and hook calling mechanisms for python"
files = [ files = [
{file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
{file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
] ]
[[package]] [[package]]
@ -529,31 +529,31 @@ files = [
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "8.0.2" version = "8.1.1"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "pytest: simple powerful testing with Python" summary = "pytest: simple powerful testing with Python"
dependencies = [ dependencies = [
"colorama; sys_platform == \"win32\"", "colorama; sys_platform == \"win32\"",
"iniconfig", "iniconfig",
"packaging", "packaging",
"pluggy<2.0,>=1.3.0", "pluggy<2.0,>=1.4",
] ]
files = [ files = [
{file = "pytest-8.0.2-py3-none-any.whl", hash = "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"}, {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"},
{file = "pytest-8.0.2.tar.gz", hash = "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd"}, {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"},
] ]
[[package]] [[package]]
name = "pytest-asyncio" name = "pytest-asyncio"
version = "0.23.5" version = "0.23.5.post1"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Pytest support for asyncio" summary = "Pytest support for asyncio"
dependencies = [ dependencies = [
"pytest<9,>=7.0.0", "pytest<9,>=7.0.0",
] ]
files = [ files = [
{file = "pytest-asyncio-0.23.5.tar.gz", hash = "sha256:3a048872a9c4ba14c3e90cc1aa20cbc2def7d01c7c8db3777ec281ba9c057675"}, {file = "pytest-asyncio-0.23.5.post1.tar.gz", hash = "sha256:b9a8806bea78c21276bc34321bbf234ba1b2ea5b30d9f0ce0f2dea45e4685813"},
{file = "pytest_asyncio-0.23.5-py3-none-any.whl", hash = "sha256:4e7093259ba018d58ede7d5315131d21923a60f8a6e9ee266ce1589685c89eac"}, {file = "pytest_asyncio-0.23.5.post1-py3-none-any.whl", hash = "sha256:30f54d27774e79ac409778889880242b0403d09cabd65b727ce90fe92dd5d80e"},
] ]
[[package]] [[package]]
@ -631,27 +631,27 @@ files = [
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.3.0" version = "0.3.2"
requires_python = ">=3.7" requires_python = ">=3.7"
summary = "An extremely fast Python linter and code formatter, written in Rust." summary = "An extremely fast Python linter and code formatter, written in Rust."
files = [ files = [
{file = "ruff-0.3.0-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7deb528029bacf845bdbb3dbb2927d8ef9b4356a5e731b10eef171e3f0a85944"}, {file = "ruff-0.3.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77f2612752e25f730da7421ca5e3147b213dca4f9a0f7e0b534e9562c5441f01"},
{file = "ruff-0.3.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e1e0d4381ca88fb2b73ea0766008e703f33f460295de658f5467f6f229658c19"}, {file = "ruff-0.3.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9966b964b2dd1107797be9ca7195002b874424d1d5472097701ae8f43eadef5d"},
{file = "ruff-0.3.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f7dbba46e2827dfcb0f0cc55fba8e96ba7c8700e0a866eb8cef7d1d66c25dcb"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b83d17ff166aa0659d1e1deaf9f2f14cbe387293a906de09bc4860717eb2e2da"},
{file = "ruff-0.3.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:23dbb808e2f1d68eeadd5f655485e235c102ac6f12ad31505804edced2a5ae77"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb875c6cc87b3703aeda85f01c9aebdce3d217aeaca3c2e52e38077383f7268a"},
{file = "ruff-0.3.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ef655c51f41d5fa879f98e40c90072b567c666a7114fa2d9fe004dffba00932"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be75e468a6a86426430373d81c041b7605137a28f7014a72d2fc749e47f572aa"},
{file = "ruff-0.3.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d0d3d7ef3d4f06433d592e5f7d813314a34601e6c5be8481cccb7fa760aa243e"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:967978ac2d4506255e2f52afe70dda023fc602b283e97685c8447d036863a302"},
{file = "ruff-0.3.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b08b356d06a792e49a12074b62222f9d4ea2a11dca9da9f68163b28c71bf1dd4"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1231eacd4510f73222940727ac927bc5d07667a86b0cbe822024dd00343e77e9"},
{file = "ruff-0.3.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9343690f95710f8cf251bee1013bf43030072b9f8d012fbed6ad702ef70d360a"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c6d613b19e9a8021be2ee1d0e27710208d1603b56f47203d0abbde906929a9b"},
{file = "ruff-0.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1f3ed501a42f60f4dedb7805fa8d4534e78b4e196f536bac926f805f0743d49"}, {file = "ruff-0.3.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8439338a6303585d27b66b4626cbde89bb3e50fa3cae86ce52c1db7449330a7"},
{file = "ruff-0.3.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:cc30a9053ff2f1ffb505a585797c23434d5f6c838bacfe206c0e6cf38c921a1e"}, {file = "ruff-0.3.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:de8b480d8379620cbb5ea466a9e53bb467d2fb07c7eca54a4aa8576483c35d36"},
{file = "ruff-0.3.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5da894a29ec018a8293d3d17c797e73b374773943e8369cfc50495573d396933"}, {file = "ruff-0.3.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b74c3de9103bd35df2bb05d8b2899bf2dbe4efda6474ea9681280648ec4d237d"},
{file = "ruff-0.3.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:755c22536d7f1889be25f2baf6fedd019d0c51d079e8417d4441159f3bcd30c2"}, {file = "ruff-0.3.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f380be9fc15a99765c9cf316b40b9da1f6ad2ab9639e551703e581a5e6da6745"},
{file = "ruff-0.3.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dd73fe7f4c28d317855da6a7bc4aa29a1500320818dd8f27df95f70a01b8171f"}, {file = "ruff-0.3.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0ac06a3759c3ab9ef86bbeca665d31ad3aa9a4b1c17684aadb7e61c10baa0df4"},
{file = "ruff-0.3.0-py3-none-win32.whl", hash = "sha256:19eacceb4c9406f6c41af806418a26fdb23120dfe53583df76d1401c92b7c14b"}, {file = "ruff-0.3.2-py3-none-win32.whl", hash = "sha256:9bd640a8f7dd07a0b6901fcebccedadeb1a705a50350fb86b4003b805c81385a"},
{file = "ruff-0.3.0-py3-none-win_amd64.whl", hash = "sha256:128265876c1d703e5f5e5a4543bd8be47c73a9ba223fd3989d4aa87dd06f312f"}, {file = "ruff-0.3.2-py3-none-win_amd64.whl", hash = "sha256:0c1bdd9920cab5707c26c8b3bf33a064a4ca7842d91a99ec0634fec68f9f4037"},
{file = "ruff-0.3.0-py3-none-win_arm64.whl", hash = "sha256:e3a4a6d46aef0a84b74fcd201a4401ea9a6cd85614f6a9435f2d33dd8cefbf83"}, {file = "ruff-0.3.2-py3-none-win_arm64.whl", hash = "sha256:5f65103b1d76e0d600cabd577b04179ff592064eaa451a70a81085930e907d0b"},
{file = "ruff-0.3.0.tar.gz", hash = "sha256:0886184ba2618d815067cf43e005388967b67ab9c80df52b32ec1152ab49f53a"}, {file = "ruff-0.3.2.tar.gz", hash = "sha256:fa78ec9418eb1ca3db392811df3376b46471ae93792a81af2d1cbb0e5dcb5142"},
] ]
[[package]] [[package]]
@ -709,7 +709,7 @@ files = [
[[package]] [[package]]
name = "uvicorn" name = "uvicorn"
version = "0.27.1" version = "0.28.0"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "The lightning-fast ASGI server." summary = "The lightning-fast ASGI server."
dependencies = [ dependencies = [
@ -717,13 +717,13 @@ dependencies = [
"h11>=0.8", "h11>=0.8",
] ]
files = [ files = [
{file = "uvicorn-0.27.1-py3-none-any.whl", hash = "sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4"}, {file = "uvicorn-0.28.0-py3-none-any.whl", hash = "sha256:6623abbbe6176204a4226e67607b4d52cc60ff62cda0ff177613645cefa2ece1"},
{file = "uvicorn-0.27.1.tar.gz", hash = "sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a"}, {file = "uvicorn-0.28.0.tar.gz", hash = "sha256:cab4473b5d1eaeb5a0f6375ac4bc85007ffc75c3cc1768816d9e5d589857b067"},
] ]
[[package]] [[package]]
name = "uvicorn" name = "uvicorn"
version = "0.27.1" version = "0.28.0"
extras = ["standard"] extras = ["standard"]
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "The lightning-fast ASGI server." summary = "The lightning-fast ASGI server."
@ -732,14 +732,14 @@ dependencies = [
"httptools>=0.5.0", "httptools>=0.5.0",
"python-dotenv>=0.13", "python-dotenv>=0.13",
"pyyaml>=5.1", "pyyaml>=5.1",
"uvicorn==0.27.1", "uvicorn==0.28.0",
"uvloop!=0.15.0,!=0.15.1,>=0.14.0; (sys_platform != \"cygwin\" and sys_platform != \"win32\") and platform_python_implementation != \"PyPy\"", "uvloop!=0.15.0,!=0.15.1,>=0.14.0; (sys_platform != \"cygwin\" and sys_platform != \"win32\") and platform_python_implementation != \"PyPy\"",
"watchfiles>=0.13", "watchfiles>=0.13",
"websockets>=10.4", "websockets>=10.4",
] ]
files = [ files = [
{file = "uvicorn-0.27.1-py3-none-any.whl", hash = "sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4"}, {file = "uvicorn-0.28.0-py3-none-any.whl", hash = "sha256:6623abbbe6176204a4226e67607b4d52cc60ff62cda0ff177613645cefa2ece1"},
{file = "uvicorn-0.27.1.tar.gz", hash = "sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a"}, {file = "uvicorn-0.28.0.tar.gz", hash = "sha256:cab4473b5d1eaeb5a0f6375ac4bc85007ffc75c3cc1768816d9e5d589857b067"},
] ]
[[package]] [[package]]

View File

@ -72,3 +72,103 @@ async def select_todo(db: AsyncConnectionPool, id: int, member_id: int) -> Todo
member = await curr.fetchone() member = await curr.fetchone()
return None if member is None else member return None if member is None else member
async def insert_todo(
db: AsyncConnectionPool,
member_id: int,
task: str,
complete: bool,
due: datetime | None,
) -> Todo | None:
"""
Insert a todo into the database
Return the inserted todo object or None
"""
async with db.connection() as conn:
async with conn.cursor(row_factory=class_row(Todo)) as curr:
query = sql.SQL(
"""
INSERT INTO todos (complete, due, member_id, task)
VALUES ((%s), (%s), (%s), (%s))
RETURNING id, complete, due, task
"""
)
await curr.execute(
query,
(
complete,
due,
member_id,
task,
),
)
todo = await curr.fetchone()
return None if todo is None else todo
async def update_todo(
db: AsyncConnectionPool,
id: int,
member_id: int,
task: str,
complete: bool,
due: datetime | None,
) -> Todo | None:
"""
Update a todo in the database
Return the updated todo object or None
"""
async with db.connection() as conn:
async with conn.cursor(row_factory=class_row(Todo)) as curr:
query = sql.SQL(
"""
UPDATE todos
SET complete = (%s), due = (%s), task = (%s)
WHERE id = (%s) AND member_id = (%s)
RETURNING id, complete, due, task
"""
)
await curr.execute(
query,
(
complete,
due,
task,
id,
member_id,
),
)
todo = await curr.fetchone()
return None if todo is None else todo
async def delete_todo(db: AsyncConnectionPool, id: int, member_id: int) -> bool:
"""
Delete a todo with an id and its member_id. Note that you can not delete other users' todos
Return True if deletion is successful and False otherwise
"""
async with db.connection() as conn:
async with conn.cursor(row_factory=class_row(Todo)) as curr:
query = sql.SQL(
"""
DELETE FROM todos
WHERE id = (%s) AND member_id = (%s)
"""
)
await curr.execute(
query,
(
id,
member_id,
),
)
return curr.rowcount > 0

View File

@ -1,6 +1,13 @@
import asyncio import asyncio
from datetime import datetime
from src.neo_neo_todo.models.todo import select_todo, select_todos from src.neo_neo_todo.models.todo import (
delete_todo,
insert_todo,
select_todo,
select_todos,
update_todo,
)
async def test_model_todo_select_todos(db_pool): async def test_model_todo_select_todos(db_pool):
@ -39,3 +46,54 @@ async def test_model_todo_select_todo(db_pool):
non_existent_member_id_todo = await select_todo(db_pool, member_id=1234123, id=1) non_existent_member_id_todo = await select_todo(db_pool, member_id=1234123, id=1)
assert non_existent_member_id_todo is None assert non_existent_member_id_todo is None
async def test_model_todo_insert_todo(db_pool):
todo_insert_success = await insert_todo(
db_pool, member_id=1, task="Test Task 4", complete=False, due=None
)
assert todo_insert_success
assert not todo_insert_success.complete
assert todo_insert_success.task == "Test Task 4"
async def test_model_todo_update_todo(db_pool):
due = datetime.now()
todo_updated_1 = await update_todo(
db_pool,
id=3,
member_id=1,
task="Updated Task Test",
complete=True,
due=due,
)
assert todo_updated_1
assert todo_updated_1.id == 3
assert todo_updated_1.task == "Updated Task Test"
assert todo_updated_1.complete
assert todo_updated_1.due
# non existent id and member_id
todo_updated_2 = await update_todo(
db_pool,
id=99999,
member_id=2,
task="Updated Task Test",
complete=True,
due=due,
)
assert not todo_updated_2
async def test_model_todo_delete_todo(db_pool):
delete_todo_success = await delete_todo(db_pool, id=1, member_id=1)
assert delete_todo_success
delete_todo_fail = await delete_todo(db_pool, id=1, member_id=2)
assert not delete_todo_fail