Modified files
storeapi/tasks.py
---
+++
@@ -11,7 +11,7 @@
pass
-async def send_simple_message(to: str, subject: str, body: str):
+async def send_simple_email(to: str, subject: str, body: str):
logger.debug(f"Sending email to '{to[:3]}' with subject '{subject[:20]}'")
async with httpx.AsyncClient() as client:
try:
@@ -37,7 +37,7 @@
async def send_user_registration_email(email: str, confirmation_url: str):
- return await send_simple_message(
+ return await send_simple_email(
email,
"Successfully signed up",
(
storeapi/security.py
---
+++
@@ -16,7 +16,7 @@
pwd_context = CryptContext(schemes=["bcrypt"])
-def create_unauthorized_exception(detail: str) -> HTTPException:
+def create_credentials_exception(detail: str) -> HTTPException:
return HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=detail,
@@ -56,19 +56,19 @@
token: str, type: Literal["access", "confirmation"]
) -> str:
try:
- payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+ payload = jwt.decode(token, key=SECRET_KEY, algorithms=[ALGORITHM])
except ExpiredSignatureError as e:
- raise create_unauthorized_exception("Token has expired") from e
+ raise create_credentials_exception("Token has expired") from e
except JWTError as e:
- raise create_unauthorized_exception("Invalid token") from e
+ raise create_credentials_exception("Invalid token") from e
email = payload.get("sub")
if email is None:
- raise create_unauthorized_exception("Token is missing 'sub' field")
+ raise create_credentials_exception("Token is missing 'sub' field")
token_type = payload.get("type")
if token_type is None or token_type != type:
- raise create_unauthorized_exception(
+ raise create_credentials_exception(
f"Token has incorrect type, expected '{type}'"
)
@@ -95,11 +95,11 @@
logger.debug("Authenticating user", extra={"email": email})
user = await get_user(email)
if not user:
- raise create_unauthorized_exception("Invalid email or password")
+ raise create_credentials_exception("Invalid email or password")
if not verify_password(password, user.password):
- raise create_unauthorized_exception("Invalid email or password")
+ raise create_credentials_exception("Invalid email or password")
if not user.confirmed:
- raise create_unauthorized_exception("User has not confirmed email")
+ raise create_credentials_exception("User has not confirmed email")
return user
@@ -107,5 +107,5 @@
email = get_subject_for_token_type(token, "access")
user = await get_user(email=email)
if user is None:
- raise create_unauthorized_exception("Could not find user for this token")
+ raise create_credentials_exception("Could not find user for this token")
return user
storeapi/routers/user.py
---
+++
@@ -1,6 +1,6 @@
import logging
-from fastapi import APIRouter, HTTPException, Request, status
+from fastapi import APIRouter, BackgroundTasks, HTTPException, Request, status
from storeapi import tasks
from storeapi.database import database, user_table
from storeapi.models.user import UserIn
@@ -18,7 +18,7 @@
@router.post("/register", status_code=201)
-async def register(user: UserIn, request: Request):
+async def register(user: UserIn, background_tasks: BackgroundTasks, request: Request):
if await get_user(user.email):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -30,7 +30,11 @@
logger.debug(query)
await database.execute(query)
- await tasks.send_user_registration_email(
+
+ logger.debug("Submitting background task to send email")
+
+ background_tasks.add_task(
+ tasks.send_user_registration_email,
user.email,
confirmation_url=request.url_for(
"confirm_email", token=create_confirmation_token(user.email)
storeapi/tests/test_security.py
---
+++
@@ -131,4 +131,4 @@
token = security.create_confirmation_token(registered_user["email"])
with pytest.raises(security.HTTPException):
- await security.get_current_user(token)
+ await security.get_current_user(token)
storeapi/tests/test_tasks.py
---
+++
@@ -1,19 +1,20 @@
import httpx
import pytest
-from storeapi.tasks import APIResponseError, send_simple_message
+
+from storeapi.tasks import APIResponseError, send_simple_email
@pytest.mark.anyio
-async def test_send_simple_message(mock_httpx_client):
- await send_simple_message("test@example.net", "Test Subject", "Test Body")
+async def test_send_simple_email(mock_httpx_client):
+ await send_simple_email("test@example.net", "Test Subject", "Test Body")
mock_httpx_client.post.assert_called()
-@pytest.mark.asyncio
-async def test_send_simple_message_api_error(mock_httpx_client):
+@pytest.mark.anyio
+async def test_send_simple_email_api_error(mock_httpx_client):
mock_httpx_client.post.return_value = httpx.Response(
status_code=500, content="", request=httpx.Request("POST", "//")
)
- with pytest.raises(APIResponseError, match="API request failed with status code"):
- await send_simple_message("test@example.com", "Test Subject", "Test Body")
+ with pytest.raises(APIResponseError):
+ await send_simple_email("test@example.net", "Test Subject", "Test Body")
storeapi/tests/routers/test_user.py
---
+++
@@ -1,6 +1,6 @@
import pytest
+from fastapi import BackgroundTasks
from httpx import AsyncClient
-from storeapi import tasks
async def register_user(async_client: AsyncClient, email: str, password: str):
@@ -29,7 +29,7 @@
@pytest.mark.anyio
async def test_confirm_user(async_client: AsyncClient, mocker):
- spy = mocker.spy(tasks, "send_user_registration_email")
+ spy = mocker.spy(BackgroundTasks, "add_task")
await register_user(async_client, "test@example.net", "1234")
confirmation_url = str(spy.call_args[1]["confirmation_url"])
@@ -48,7 +48,7 @@
@pytest.mark.anyio
async def test_confirm_user_expired_token(async_client: AsyncClient, mocker):
mocker.patch("storeapi.security.confirm_token_expire_minutes", return_value=-1)
- spy = mocker.spy(tasks, "send_user_registration_email")
+ spy = mocker.spy(BackgroundTasks, "add_task")
await register_user(async_client, "test@example.net", "1234")
confirmation_url = str(spy.call_args[1]["confirmation_url"])