549 lines
16 KiB
Python
549 lines
16 KiB
Python
import pytest
|
|
from fastapi.testclient import TestClient
|
|
from bson import ObjectId
|
|
|
|
from src.models.user import UserModel
|
|
from src.models.team import TeamModel
|
|
from src.db.repositories.user_repository import user_repository
|
|
from src.db.repositories.team_repository import team_repository
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_user(client: TestClient, admin_api_key: tuple, test_team: TeamModel):
|
|
"""Test creating a new user (admin only)"""
|
|
raw_key, _ = admin_api_key
|
|
|
|
# Set up the headers with the admin API key
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Create a new user
|
|
response = client.post(
|
|
"/api/v1/users",
|
|
headers=headers,
|
|
json={
|
|
"email": "newuser@example.com",
|
|
"name": "New User",
|
|
"team_id": str(test_team.id),
|
|
"is_admin": False
|
|
}
|
|
)
|
|
|
|
# Check response
|
|
assert response.status_code == 201
|
|
data = response.json()
|
|
assert "id" in data
|
|
assert data["email"] == "newuser@example.com"
|
|
assert data["name"] == "New User"
|
|
assert data["team_id"] == str(test_team.id)
|
|
assert data["is_admin"] is False
|
|
assert "created_at" in data
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_user_non_admin(client: TestClient, user_api_key: tuple, test_team: TeamModel):
|
|
"""Test that non-admin users cannot create users"""
|
|
raw_key, _ = user_api_key
|
|
|
|
# Set up the headers with a non-admin API key
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Try to create a new user
|
|
response = client.post(
|
|
"/api/v1/users",
|
|
headers=headers,
|
|
json={
|
|
"email": "unauthorized@example.com",
|
|
"name": "Unauthorized User",
|
|
"team_id": str(test_team.id),
|
|
"is_admin": False
|
|
}
|
|
)
|
|
|
|
# Check that the request is forbidden
|
|
assert response.status_code == 403
|
|
assert "detail" in response.json()
|
|
assert "admin" in response.json()["detail"].lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_users_in_team(client: TestClient, admin_api_key: tuple, test_team: TeamModel):
|
|
"""Test listing users in a team"""
|
|
raw_key, _ = admin_api_key
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# List users in the team
|
|
response = client.get(
|
|
f"/api/v1/users?team_id={test_team.id}",
|
|
headers=headers
|
|
)
|
|
|
|
# Check response
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "users" in data
|
|
assert "total" in data
|
|
assert data["total"] >= 1 # Should include at least the admin user
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_all_users_admin_only(client: TestClient, admin_api_key: tuple):
|
|
"""Test that only admins can list all users across teams"""
|
|
raw_key, _ = admin_api_key
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# List all users (no team filter)
|
|
response = client.get(
|
|
"/api/v1/users",
|
|
headers=headers
|
|
)
|
|
|
|
# Check response
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "users" in data
|
|
assert "total" in data
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_users_non_admin_restricted(client: TestClient, user_api_key: tuple):
|
|
"""Test that non-admin users can only see their own team"""
|
|
raw_key, _ = user_api_key
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Try to list all users
|
|
response = client.get(
|
|
"/api/v1/users",
|
|
headers=headers
|
|
)
|
|
|
|
# Should only return users from their own team
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "users" in data
|
|
# All returned users should be from the same team
|
|
if data["users"]:
|
|
user_team_ids = set(user["team_id"] for user in data["users"])
|
|
assert len(user_team_ids) == 1 # Only one team represented
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_user_by_id(client: TestClient, admin_api_key: tuple, admin_user: UserModel):
|
|
"""Test getting a specific user by ID"""
|
|
raw_key, _ = admin_api_key
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Get the user
|
|
response = client.get(
|
|
f"/api/v1/users/{admin_user.id}",
|
|
headers=headers
|
|
)
|
|
|
|
# Check response
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["id"] == str(admin_user.id)
|
|
assert data["email"] == admin_user.email
|
|
assert data["name"] == admin_user.name
|
|
assert data["team_id"] == str(admin_user.team_id)
|
|
assert data["is_admin"] == admin_user.is_admin
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_user_self(client: TestClient, user_api_key: tuple, regular_user: UserModel):
|
|
"""Test that users can get their own information"""
|
|
raw_key, _ = user_api_key
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Get own user information
|
|
response = client.get(
|
|
f"/api/v1/users/{regular_user.id}",
|
|
headers=headers
|
|
)
|
|
|
|
# Check response
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["id"] == str(regular_user.id)
|
|
assert data["email"] == regular_user.email
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_user_other_team_forbidden(client: TestClient, user_api_key: tuple):
|
|
"""Test that users cannot access users from other teams"""
|
|
raw_key, _ = user_api_key
|
|
|
|
# Create a user in another team
|
|
other_team = TeamModel(
|
|
name="Other Team",
|
|
description="Another team for testing"
|
|
)
|
|
created_team = await team_repository.create(other_team)
|
|
|
|
other_user = UserModel(
|
|
email="other@example.com",
|
|
name="Other User",
|
|
team_id=created_team.id,
|
|
is_admin=False
|
|
)
|
|
created_user = await user_repository.create(other_user)
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Try to get the other user
|
|
response = client.get(
|
|
f"/api/v1/users/{created_user.id}",
|
|
headers=headers
|
|
)
|
|
|
|
# Check that the request is forbidden
|
|
assert response.status_code == 403
|
|
assert "detail" in response.json()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_user(client: TestClient, admin_api_key: tuple, regular_user: UserModel):
|
|
"""Test updating a user (admin only)"""
|
|
raw_key, _ = admin_api_key
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Update the user
|
|
response = client.put(
|
|
f"/api/v1/users/{regular_user.id}",
|
|
headers=headers,
|
|
json={
|
|
"name": "Updated User Name",
|
|
"email": "updated@example.com",
|
|
"is_admin": True
|
|
}
|
|
)
|
|
|
|
# Check response
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["id"] == str(regular_user.id)
|
|
assert data["name"] == "Updated User Name"
|
|
assert data["email"] == "updated@example.com"
|
|
assert data["is_admin"] is True
|
|
assert "updated_at" in data
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_user_self(client: TestClient, user_api_key: tuple, regular_user: UserModel):
|
|
"""Test that users can update their own basic information"""
|
|
raw_key, _ = user_api_key
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Update own information (limited fields)
|
|
response = client.put(
|
|
f"/api/v1/users/{regular_user.id}",
|
|
headers=headers,
|
|
json={
|
|
"name": "Self Updated Name",
|
|
"email": "self_updated@example.com"
|
|
# Note: is_admin should not be updatable by self
|
|
}
|
|
)
|
|
|
|
# Check response
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["name"] == "Self Updated Name"
|
|
assert data["email"] == "self_updated@example.com"
|
|
# Admin status should remain unchanged
|
|
assert data["is_admin"] == regular_user.is_admin
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_user_non_admin_forbidden(client: TestClient, user_api_key: tuple, admin_user: UserModel):
|
|
"""Test that non-admin users cannot update other users"""
|
|
raw_key, _ = user_api_key
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Try to update another user
|
|
response = client.put(
|
|
f"/api/v1/users/{admin_user.id}",
|
|
headers=headers,
|
|
json={
|
|
"name": "Unauthorized Update",
|
|
"is_admin": False
|
|
}
|
|
)
|
|
|
|
# Check that the request is forbidden
|
|
assert response.status_code == 403
|
|
assert "detail" in response.json()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_user(client: TestClient, admin_api_key: tuple):
|
|
"""Test deleting a user (admin only)"""
|
|
raw_key, _ = admin_api_key
|
|
|
|
# Create a user to delete
|
|
user_to_delete = UserModel(
|
|
email="delete@example.com",
|
|
name="User to Delete",
|
|
team_id=ObjectId(),
|
|
is_admin=False
|
|
)
|
|
created_user = await user_repository.create(user_to_delete)
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Delete the user
|
|
response = client.delete(
|
|
f"/api/v1/users/{created_user.id}",
|
|
headers=headers
|
|
)
|
|
|
|
# Check response
|
|
assert response.status_code == 204
|
|
|
|
# Verify the user has been deleted
|
|
deleted_user = await user_repository.get_by_id(created_user.id)
|
|
assert deleted_user is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_user_non_admin(client: TestClient, user_api_key: tuple, admin_user: UserModel):
|
|
"""Test that non-admin users cannot delete users"""
|
|
raw_key, _ = user_api_key
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Try to delete a user
|
|
response = client.delete(
|
|
f"/api/v1/users/{admin_user.id}",
|
|
headers=headers
|
|
)
|
|
|
|
# Check that the request is forbidden
|
|
assert response.status_code == 403
|
|
assert "detail" in response.json()
|
|
assert "admin" in response.json()["detail"].lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_user_activity(client: TestClient, admin_api_key: tuple, regular_user: UserModel):
|
|
"""Test getting user activity/statistics"""
|
|
raw_key, _ = admin_api_key
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Get user activity
|
|
response = client.get(
|
|
f"/api/v1/users/{regular_user.id}/activity",
|
|
headers=headers
|
|
)
|
|
|
|
# Check response
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "user_id" in data
|
|
assert "images_uploaded" in data
|
|
assert "last_login" in data
|
|
assert "api_key_usage" in data
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_change_user_team(client: TestClient, admin_api_key: tuple, regular_user: UserModel):
|
|
"""Test moving a user to a different team (admin only)"""
|
|
raw_key, _ = admin_api_key
|
|
|
|
# Create another team
|
|
new_team = TeamModel(
|
|
name="New Team",
|
|
description="A new team for testing"
|
|
)
|
|
created_team = await team_repository.create(new_team)
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Move user to new team
|
|
response = client.put(
|
|
f"/api/v1/users/{regular_user.id}/team",
|
|
headers=headers,
|
|
json={
|
|
"team_id": str(created_team.id)
|
|
}
|
|
)
|
|
|
|
# Check response
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["team_id"] == str(created_team.id)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_user_search(client: TestClient, admin_api_key: tuple):
|
|
"""Test searching for users by email or name"""
|
|
raw_key, _ = admin_api_key
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Search for users
|
|
response = client.get(
|
|
"/api/v1/users/search?query=admin",
|
|
headers=headers
|
|
)
|
|
|
|
# Check response
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "users" in data
|
|
assert "total" in data
|
|
|
|
# Results should contain users matching the query
|
|
for user in data["users"]:
|
|
assert "admin" in user["email"].lower() or "admin" in user["name"].lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_user_pagination(client: TestClient, admin_api_key: tuple):
|
|
"""Test user listing with pagination"""
|
|
raw_key, _ = admin_api_key
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Test pagination
|
|
response = client.get(
|
|
"/api/v1/users?page=1&limit=10",
|
|
headers=headers
|
|
)
|
|
|
|
# Check response
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "users" in data
|
|
assert "pagination" in data
|
|
assert "total" in data["pagination"]
|
|
assert "page" in data["pagination"]
|
|
assert "pages" in data["pagination"]
|
|
assert len(data["users"]) <= 10
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_invalid_user_id(client: TestClient, admin_api_key: tuple):
|
|
"""Test handling of invalid user IDs"""
|
|
raw_key, _ = admin_api_key
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Try to get user with invalid ID
|
|
response = client.get(
|
|
"/api/v1/users/invalid-id",
|
|
headers=headers
|
|
)
|
|
|
|
# Check that the request returns 400 Bad Request
|
|
assert response.status_code == 400
|
|
assert "detail" in response.json()
|
|
assert "invalid" in response.json()["detail"].lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_nonexistent_user(client: TestClient, admin_api_key: tuple):
|
|
"""Test handling of nonexistent user IDs"""
|
|
raw_key, _ = admin_api_key
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Try to get nonexistent user
|
|
nonexistent_id = str(ObjectId())
|
|
response = client.get(
|
|
f"/api/v1/users/{nonexistent_id}",
|
|
headers=headers
|
|
)
|
|
|
|
# Check that the request returns 404 Not Found
|
|
assert response.status_code == 404
|
|
assert "detail" in response.json()
|
|
assert "not found" in response.json()["detail"].lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_duplicate_email_validation(client: TestClient, admin_api_key: tuple, regular_user: UserModel):
|
|
"""Test that duplicate emails are not allowed"""
|
|
raw_key, _ = admin_api_key
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Try to create user with existing email
|
|
response = client.post(
|
|
"/api/v1/users",
|
|
headers=headers,
|
|
json={
|
|
"email": regular_user.email, # Duplicate email
|
|
"name": "Duplicate Email User",
|
|
"team_id": str(regular_user.team_id),
|
|
"is_admin": False
|
|
}
|
|
)
|
|
|
|
# Check that the request is rejected
|
|
assert response.status_code == 400
|
|
assert "detail" in response.json()
|
|
assert "email" in response.json()["detail"].lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_user_role_management(client: TestClient, admin_api_key: tuple, regular_user: UserModel):
|
|
"""Test managing user roles and permissions"""
|
|
raw_key, _ = admin_api_key
|
|
|
|
# Set up the headers
|
|
headers = {"X-API-Key": raw_key}
|
|
|
|
# Promote user to admin
|
|
response = client.put(
|
|
f"/api/v1/users/{regular_user.id}/role",
|
|
headers=headers,
|
|
json={
|
|
"is_admin": True
|
|
}
|
|
)
|
|
|
|
# Check response
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["is_admin"] is True
|
|
|
|
# Demote user back to regular
|
|
response = client.put(
|
|
f"/api/v1/users/{regular_user.id}/role",
|
|
headers=headers,
|
|
json={
|
|
"is_admin": False
|
|
}
|
|
)
|
|
|
|
# Check response
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["is_admin"] is False |