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:
parent
b64030617c
commit
49c83fa287
@ -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]
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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"}
|
@ -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
28
backend/tests/conftest.py
Normal 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()
|
@ -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"}
|
||||||
|
7
backend/tests/test_model_member.py
Normal file
7
backend/tests/test_model_member.py
Normal 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"
|
Loading…
x
Reference in New Issue
Block a user