Added unit tests for user api
This commit is contained in:
167
tests/api/test_users.py
Normal file
167
tests/api/test_users.py
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# File: tests/api/test_users.py
|
||||||
|
from datetime import datetime
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from fastapi import status
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from app.api.dependencies import get_admin_user, get_user_service
|
||||||
|
from app.main import app
|
||||||
|
from app.models.auth import UserRole
|
||||||
|
from app.models.types import PyObjectId
|
||||||
|
from app.models.user import UserInDB, UserCreate
|
||||||
|
from app.services.user_service import UserService
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------
|
||||||
|
# Fixtures
|
||||||
|
# -----------------------
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fake_user_admin():
|
||||||
|
return UserInDB(
|
||||||
|
_id=PyObjectId(),
|
||||||
|
username="admin",
|
||||||
|
email="admin@example.com",
|
||||||
|
role=UserRole.ADMIN,
|
||||||
|
is_active=True,
|
||||||
|
hashed_password="hashed-secret",
|
||||||
|
created_at=datetime(2025, 1, 1),
|
||||||
|
updated_at=datetime(2025, 1, 2),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fake_user_response():
|
||||||
|
return UserInDB(
|
||||||
|
_id=PyObjectId(),
|
||||||
|
username="other",
|
||||||
|
email="other@example.com",
|
||||||
|
role=UserRole.USER,
|
||||||
|
is_active=True,
|
||||||
|
hashed_password="hashed-secret-2",
|
||||||
|
created_at=datetime(2025, 1, 1),
|
||||||
|
updated_at=datetime(2025, 1, 2),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client(fake_user_admin):
|
||||||
|
# Fake admin dependency
|
||||||
|
def get_admin_user_override():
|
||||||
|
return fake_user_admin
|
||||||
|
|
||||||
|
# Fake user service
|
||||||
|
user_service_mock = MagicMock(spec=UserService)
|
||||||
|
|
||||||
|
def get_user_service_override():
|
||||||
|
return user_service_mock
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
client.app.dependency_overrides = {
|
||||||
|
get_admin_user: get_admin_user_override,
|
||||||
|
get_user_service: get_user_service_override
|
||||||
|
}
|
||||||
|
|
||||||
|
client.user_service_mock = user_service_mock
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------
|
||||||
|
# Tests
|
||||||
|
# -----------------------
|
||||||
|
|
||||||
|
class TestListUsers:
|
||||||
|
|
||||||
|
def test_i_can_list_users(self, client, fake_user_admin, fake_user_response):
|
||||||
|
client.user_service_mock.list_users.return_value = [fake_user_admin, fake_user_response]
|
||||||
|
response = client.get("/users")
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
data = response.json()
|
||||||
|
assert len(data) == 2
|
||||||
|
assert data[0]["username"] == "admin"
|
||||||
|
|
||||||
|
def test_i_can_list_users_when_empty(self, client):
|
||||||
|
client.user_service_mock.list_users.return_value = []
|
||||||
|
response = client.get("/users")
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
assert response.json() == []
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetUserById:
|
||||||
|
|
||||||
|
def test_i_can_get_user_by_id(self, client, fake_user_response):
|
||||||
|
client.user_service_mock.get_user_by_id.return_value = fake_user_response
|
||||||
|
response = client.get(f"/users/{fake_user_response.id}")
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
data = response.json()
|
||||||
|
assert data["username"] == fake_user_response.username
|
||||||
|
|
||||||
|
def test_i_cannot_get_user_by_id_not_found(self, client):
|
||||||
|
client.user_service_mock.get_user_by_id.return_value = None
|
||||||
|
response = client.get("/users/64f0c9f4b0d1c8b7b8e1f0a2")
|
||||||
|
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||||
|
assert response.json()["detail"] == "User not found"
|
||||||
|
|
||||||
|
|
||||||
|
class TestCreateUser:
|
||||||
|
|
||||||
|
def test_i_can_create_user(self, client, fake_user_response):
|
||||||
|
user_data = UserCreate(username="newuser",
|
||||||
|
email="new@example.com",
|
||||||
|
password="#Passw0rd!",
|
||||||
|
role=UserRole.USER)
|
||||||
|
|
||||||
|
client.user_service_mock.create_user.return_value = fake_user_response
|
||||||
|
response = client.post("/users", json=user_data.model_dump(mode="json"))
|
||||||
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
|
data = response.json()
|
||||||
|
assert data["username"] == fake_user_response.username
|
||||||
|
|
||||||
|
def test_i_cannot_create_user_when_service_raises_value_error(self, client):
|
||||||
|
user_data = {"username": "baduser", "email": "bad@example.com", "role": "user", "password": "password"}
|
||||||
|
client.user_service_mock.create_user.side_effect = ValueError("Invalid data")
|
||||||
|
response = client.post("/users", json=user_data)
|
||||||
|
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||||
|
|
||||||
|
|
||||||
|
class TestUpdateUser:
|
||||||
|
|
||||||
|
def test_i_can_update_user(self, client, fake_user_response):
|
||||||
|
user_data = {"username": "updateduser", "email": "updated@example.com"}
|
||||||
|
client.user_service_mock.update_user.return_value = fake_user_response
|
||||||
|
response = client.put(f"/users/{fake_user_response.id}", json=user_data)
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
data = response.json()
|
||||||
|
assert data["username"] == fake_user_response.username
|
||||||
|
|
||||||
|
def test_i_cannot_update_user_not_found(self, client):
|
||||||
|
client.user_service_mock.update_user.return_value = None
|
||||||
|
user_data = {"username": "updateduser"}
|
||||||
|
response = client.put("/users/64f0c9f4b0d1c8b7b8e1f0a2", json=user_data)
|
||||||
|
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||||
|
assert response.json()["detail"] == "User not found"
|
||||||
|
|
||||||
|
def test_i_cannot_update_user_when_service_raises_value_error(self, client):
|
||||||
|
client.user_service_mock.update_user.side_effect = ValueError("Invalid update")
|
||||||
|
user_data = {"username": "badupdate"}
|
||||||
|
response = client.put("/users/64f0c9f4b0d1c8b7b8e1f0a2", json=user_data)
|
||||||
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||||
|
assert response.json()["detail"] == "Invalid update"
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeleteUser:
|
||||||
|
|
||||||
|
def test_i_can_delete_user(self, client):
|
||||||
|
client.user_service_mock.delete_user.return_value = True
|
||||||
|
response = client.delete("/users/64f0c9f4b0d1c8b7b8e1f0a1")
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
data = response.json()
|
||||||
|
assert data["message"] == "User successfully deleted"
|
||||||
|
|
||||||
|
def test_i_cannot_delete_user_not_found(self, client):
|
||||||
|
client.user_service_mock.delete_user.return_value = False
|
||||||
|
response = client.delete("/users/64f0c9f4b0d1c8b7b8e1f0a2")
|
||||||
|
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||||
|
assert response.json()["detail"] == "User not found"
|
||||||
Reference in New Issue
Block a user