feat(api+testing): select member by email

- Reorganized folder structure so routes live in the same folder
- Set up pytest fixtures for future use
This commit is contained in:
minhtrannhat 2024-03-01 20:14:20 -05:00
parent b64030617c
commit 49c83fa287
Signed by: minhtrannhat
GPG Key ID: E13CFA85C53F8062
10 changed files with 72 additions and 14 deletions

View File

@ -46,6 +46,10 @@ target-version = ["py311"]
addopts = "--showlocals" addopts = "--showlocals"
asyncio_mode = "auto" asyncio_mode = "auto"
pythonpath = ["src"] pythonpath = ["src"]
filterwarnings = [
"error",
'ignore::DeprecationWarning',
]
[tool.pdm.scripts] [tool.pdm.scripts]

View File

@ -4,8 +4,7 @@ import redis.asyncio as redis
from fastapi import FastAPI from fastapi import FastAPI
from fastapi_limiter import FastAPILimiter from fastapi_limiter import FastAPILimiter
from src.neo_neo_todo.control import control from src.neo_neo_todo.routes import control, sessions
from src.neo_neo_todo.sessions import sessions
from src.neo_neo_todo.utils.database import pool from src.neo_neo_todo.utils.database import pool

View File

@ -1,6 +1,10 @@
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from psycopg import sql
from psycopg.rows import class_row
from psycopg_pool import AsyncConnectionPool
@dataclass @dataclass
class Member: class Member:
@ -9,3 +13,26 @@ class Member:
password_hash: str password_hash: str
created: datetime created: datetime
email_verified: datetime | None email_verified: datetime | None
async def select_member_by_email(db: AsyncConnectionPool, email: str) -> Member | None:
"""
Find exactly one email match in users list in PostgreSQL database
"""
async with db.connection() as conn:
async with conn.cursor(row_factory=class_row(Member)) as curr:
query = sql.SQL(
"""
SELECT id, email, password_hash, created, email_verified
FROM members
WHERE LOWER(email) = LOWER(%s);
"""
)
await curr.execute(query, (email,))
print(query.as_string(conn))
member = await curr.fetchone()
return None if member is None else member

View File

@ -1,5 +1,4 @@
from fastapi import APIRouter from fastapi import APIRouter
from starlette.requests import Request
router = APIRouter( router = APIRouter(
prefix="/control", prefix="/control",
@ -8,5 +7,5 @@ router = APIRouter(
@router.get("/ping") @router.get("/ping")
async def ping(request: Request): async def ping():
return {"ping": "pong"} return {"ping": "pong"}

View File

@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from fastapi_limiter.depends import RateLimiter from fastapi_limiter.depends import RateLimiter
from starlette.requests import Request
from src.neo_neo_todo.models.session import LoginData from src.neo_neo_todo.models.session import LoginData
@ -11,7 +10,7 @@ router = APIRouter(
@router.post("", dependencies=[Depends(RateLimiter(times=100, seconds=600))]) @router.post("", dependencies=[Depends(RateLimiter(times=100, seconds=600))])
async def login(request: Request, login_data: LoginData): async def login(login_data: LoginData):
""" """
Login to the todo app Login to the todo app

28
backend/tests/conftest.py Normal file
View File

@ -0,0 +1,28 @@
import os
import pytest
from fastapi.testclient import TestClient
from psycopg_pool import AsyncConnectionPool
from src.neo_neo_todo.main import app
@pytest.fixture
async def client():
yield TestClient(app)
@pytest.fixture
async def db_pool():
try:
postgres_db_url = os.environ["TODO_DB_DATABASE_URL"]
except KeyError:
raise KeyError("Can't find postgres DB URL")
pool = AsyncConnectionPool(postgres_db_url, open=False)
await pool.open()
yield pool
await pool.close()

View File

@ -1,12 +1,7 @@
import pytest from fastapi.testclient import TestClient
from httpx import AsyncClient
from src.neo_neo_todo.main import app
@pytest.mark.anyio async def test_ping(client: TestClient):
async def test_ping(): response = client.get("/control/ping")
async with AsyncClient(app=app, base_url="http://test") as ac:
response = await ac.get("/control/ping")
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == {"ping": "pong"} assert response.json() == {"ping": "pong"}

View File

@ -0,0 +1,7 @@
from src.neo_neo_todo.models.member import select_member_by_email
async def test_model_member(db_pool):
member = await select_member_by_email(db_pool, "member@todo.test")
assert member is not None
assert member.email == "member@todo.test"