Skip to content

Commit 8959517

Browse files
author
Jakub Miazek
committed
add uni tests for user auth
1 parent 32346cd commit 8959517

File tree

5 files changed

+88
-10
lines changed

5 files changed

+88
-10
lines changed

app/api/user.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from fastapi import APIRouter, Depends, status, Request, HTTPException
2+
from sqlalchemy.ext.asyncio import AsyncSession
3+
4+
from app.database import get_db
5+
from app.models.user import User
6+
from app.schemas.user import UserSchema, UserResponse, UserLogin, TokenResponse
7+
from app.services.auth import create_access_token
8+
9+
router = APIRouter(prefix="/v1/user")
10+
11+
12+
@router.post("/", status_code=status.HTTP_201_CREATED, response_model=UserResponse)
13+
async def create_user(payload: UserSchema, request: Request, db_session: AsyncSession = Depends(get_db)):
14+
_user: User = User(**payload.model_dump())
15+
await _user.save(db_session)
16+
17+
# TODO: add refresh token
18+
_user.access_token = await create_access_token(_user, request)
19+
return _user
20+
21+
22+
@router.post("/token", status_code=status.HTTP_201_CREATED, response_model=TokenResponse)
23+
async def get_token_for_user(user: UserLogin, request: Request, db_session: AsyncSession = Depends(get_db)):
24+
_user: User = await User.find(db_session, [User.email == user.email])
25+
26+
# TODO: out exception handling to external module
27+
if not _user:
28+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
29+
if not _user.check_password(user.password):
30+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Password is incorrect")
31+
32+
# TODO: add refresh token
33+
_token = await create_access_token(_user, request)
34+
return {"access_token": _token, "token_type": "bearer"}

app/schemas/user.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,3 @@ class UserLogin(BaseModel):
3131
model_config = config
3232
email: EmailStr = Field(title="User’s email", description="User’s email")
3333
password: str = Field(title="User’s password", description="User’s password")
34-

tests/api/test_auth.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import pytest
2+
from httpx import AsyncClient
3+
from starlette import status
4+
import jwt
5+
6+
pytestmark = pytest.mark.anyio
7+
8+
# TODO: parametrize test with diff urls
9+
async def test_add_user(client: AsyncClient):
10+
payload = {
11+
"email": "rancher@grassroots.com",
12+
"first_name": "Joe",
13+
"last_name": "Garcia",
14+
"password": "s1lly"
15+
}
16+
response = await client.post("/user/", json=payload)
17+
assert response.status_code == status.HTTP_201_CREATED
18+
claimset = jwt.decode(response.json()["access_token"], options={"verify_signature": False})
19+
assert claimset["email"] == payload["email"]
20+
assert claimset["expiry"] > 0
21+
assert claimset["platform"] == "python-httpx/0.24.1"
22+
23+
24+
# TODO: parametrize test with diff urls including 404 and 401
25+
async def test_get_token(client: AsyncClient):
26+
payload = {"email": "rancher@grassroots.com", "password": "s1lly"}
27+
response = await client.post("/user/token", json=payload)
28+
assert response.status_code == status.HTTP_201_CREATED
29+
claimset = jwt.decode(response.json()["access_token"], options={"verify_signature": False})
30+
assert claimset["email"] == payload["email"]
31+
assert claimset["expiry"] > 0
32+
assert claimset["platform"] == "python-httpx/0.24.1"
33+
34+
35+
# TODO: baerer token test > get token > test endpoint auth with token > expire token on redis > test endpoint auth with token

tests/api/test_health.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import pytest
2+
from fastapi import status
3+
from httpx import AsyncClient
4+
5+
pytestmark = pytest.mark.anyio
6+
7+
async def test_redis_health(client: AsyncClient):
8+
response = await client.get(f"/public/health/redis")
9+
assert response.status_code == status.HTTP_200_OK
10+
# assert payload["name"] == response.json()["name"]
11+
# assert UUID(response.json()["id"])

tests/conftest.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import pytest
2-
import pytest_asyncio
32
from httpx import AsyncClient
43

54
from app.database import engine
65
from app.main import app
76
from app.models.base import Base
7+
from app.redis import get_redis
88

99

1010
@pytest.fixture(
11+
scope="session",
1112
params=[
1213
pytest.param(("asyncio", {"use_uvloop": True}), id="asyncio+uvloop"),
1314
]
@@ -16,6 +17,7 @@ def anyio_backend(request):
1617
return request.param
1718

1819

20+
@pytest.fixture(scope="session")
1921
async def start_db():
2022
async with engine.begin() as conn:
2123
await conn.run_sync(Base.metadata.drop_all)
@@ -25,15 +27,12 @@ async def start_db():
2527
await engine.dispose()
2628

2729

28-
@pytest_asyncio.fixture
29-
async def client() -> AsyncClient:
30+
@pytest.fixture(scope="session")
31+
async def client(start_db) -> AsyncClient:
3032
async with AsyncClient(
3133
app=app,
3234
base_url="http://testserver/v1",
3335
headers={"Content-Type": "application/json"},
34-
) as client:
35-
await start_db()
36-
yield client
37-
# for AsyncEngine created in function scope, close and
38-
# clean-up pooled connections
39-
await engine.dispose()
36+
) as test_client:
37+
app.state.redis = await get_redis()
38+
yield test_client

0 commit comments

Comments
 (0)