182 lines
5.4 KiB
Python
182 lines
5.4 KiB
Python
"""Tests for auth endpoints: login, refresh, me."""
|
|
|
|
import pytest
|
|
from httpx import AsyncClient
|
|
|
|
from app.models.user import User
|
|
from app.utils.jwt import create_access_token, create_refresh_token
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_login_valid_credentials(client: AsyncClient, admin_user: User):
|
|
"""POST /api/v1/auth/login with valid credentials returns 200 + JWT."""
|
|
response = await client.post(
|
|
"/api/v1/auth/login",
|
|
json={
|
|
"email": "admin@test.com",
|
|
"password": "Admin12345!",
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "access_token" in data
|
|
assert "refresh_token" in data
|
|
assert data["token_type"] == "bearer"
|
|
assert data["expires_in"] > 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_login_invalid_password(client: AsyncClient, admin_user: User):
|
|
"""POST /api/v1/auth/login with wrong password returns 401."""
|
|
response = await client.post(
|
|
"/api/v1/auth/login",
|
|
json={
|
|
"email": "admin@test.com",
|
|
"password": "WrongPassword!",
|
|
},
|
|
)
|
|
assert response.status_code == 401
|
|
data = response.json()
|
|
assert "error" in data["detail"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_login_nonexistent_user(client: AsyncClient):
|
|
"""POST /api/v1/auth/login with unknown email returns 401."""
|
|
response = await client.post(
|
|
"/api/v1/auth/login",
|
|
json={
|
|
"email": "nobody@test.com",
|
|
"password": "SomePassword!",
|
|
},
|
|
)
|
|
assert response.status_code == 401
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_login_inactive_user(client: AsyncClient, inactive_user: User):
|
|
"""POST /api/v1/auth/login with deactivated account returns 401."""
|
|
response = await client.post(
|
|
"/api/v1/auth/login",
|
|
json={
|
|
"email": "inactive@test.com",
|
|
"password": "Inactive123!",
|
|
},
|
|
)
|
|
assert response.status_code == 401
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_login_invalid_email_format(client: AsyncClient):
|
|
"""POST /api/v1/auth/login with invalid email format returns 422."""
|
|
response = await client.post(
|
|
"/api/v1/auth/login",
|
|
json={
|
|
"email": "not-an-email",
|
|
"password": "SomePassword!",
|
|
},
|
|
)
|
|
assert response.status_code == 422
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_refresh_valid_token(client: AsyncClient, admin_user: User):
|
|
"""POST /api/v1/auth/refresh with valid refresh token returns new tokens."""
|
|
refresh_token = create_refresh_token(
|
|
user_id=str(admin_user.id),
|
|
role="admin",
|
|
email=admin_user.email,
|
|
lang=admin_user.language,
|
|
)
|
|
response = await client.post(
|
|
"/api/v1/auth/refresh",
|
|
json={
|
|
"refresh_token": refresh_token,
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "access_token" in data
|
|
assert "refresh_token" in data
|
|
assert data["token_type"] == "bearer"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_refresh_invalid_token(client: AsyncClient):
|
|
"""POST /api/v1/auth/refresh with invalid token returns 401."""
|
|
response = await client.post(
|
|
"/api/v1/auth/refresh",
|
|
json={
|
|
"refresh_token": "invalid.token.here",
|
|
},
|
|
)
|
|
assert response.status_code == 401
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_refresh_access_token_rejected(client: AsyncClient, admin_user: User):
|
|
"""POST /api/v1/auth/refresh with an access token (not refresh) returns 401."""
|
|
access_token = create_access_token(
|
|
user_id=str(admin_user.id),
|
|
role="admin",
|
|
email=admin_user.email,
|
|
lang=admin_user.language,
|
|
)
|
|
response = await client.post(
|
|
"/api/v1/auth/refresh",
|
|
json={
|
|
"refresh_token": access_token,
|
|
},
|
|
)
|
|
assert response.status_code == 401
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_me_with_valid_token(
|
|
client: AsyncClient, admin_user: User, admin_token: str
|
|
):
|
|
"""GET /api/v1/auth/me with valid JWT returns 200 + user object."""
|
|
response = await client.get(
|
|
"/api/v1/auth/me",
|
|
headers={"Authorization": f"Bearer {admin_token}"},
|
|
)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["email"] == "admin@test.com"
|
|
assert data["role"] == "admin"
|
|
assert data["is_active"] is True
|
|
assert "password_hash" not in data
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_me_without_token(client: AsyncClient):
|
|
"""GET /api/v1/auth/me without token returns 401."""
|
|
response = await client.get("/api/v1/auth/me")
|
|
assert response.status_code == 401
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_me_with_invalid_token(client: AsyncClient):
|
|
"""GET /api/v1/auth/me with invalid token returns 401."""
|
|
response = await client.get(
|
|
"/api/v1/auth/me",
|
|
headers={"Authorization": "Bearer invalid.token.here"},
|
|
)
|
|
assert response.status_code == 401
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_me_with_refresh_token(client: AsyncClient, admin_user: User):
|
|
"""GET /api/v1/auth/me with a refresh token (not access) returns 401."""
|
|
refresh_token = create_refresh_token(
|
|
user_id=str(admin_user.id),
|
|
role="admin",
|
|
email=admin_user.email,
|
|
lang=admin_user.language,
|
|
)
|
|
response = await client.get(
|
|
"/api/v1/auth/me",
|
|
headers={"Authorization": f"Bearer {refresh_token}"},
|
|
)
|
|
assert response.status_code == 401
|