Files
syndarix/backend/tests/api/test_admin_error_handlers.py
Felipe Cardoso c589b565f0 Add pyproject.toml for consolidated project configuration and replace Black, isort, and Flake8 with Ruff
- Introduced `pyproject.toml` to centralize backend tool configurations (e.g., Ruff, mypy, coverage, pytest).
- Replaced Black, isort, and Flake8 with Ruff for linting, formatting, and import sorting.
- Updated `requirements.txt` to include Ruff and remove replaced tools.
- Added `Makefile` to streamline development workflows with commands for linting, formatting, type-checking, testing, and cleanup.
2025-11-10 11:55:15 +01:00

1167 lines
42 KiB
Python

# tests/api/test_admin_error_handlers.py
"""
Tests for admin route exception handlers, error paths, and success paths.
Focus on code coverage of both error handling and normal operation branches.
"""
from unittest.mock import patch
from uuid import uuid4
import pytest
import pytest_asyncio
from fastapi import status
@pytest_asyncio.fixture
async def superuser_token(client, async_test_superuser):
"""Get access token for superuser."""
response = await client.post(
"/api/v1/auth/login",
json={"email": "superuser@example.com", "password": "SuperPassword123!"},
)
assert response.status_code == 200
return response.json()["access_token"]
# ===== USER MANAGEMENT ERROR TESTS =====
class TestAdminListUsersFilters:
"""Test admin list users with various filters."""
@pytest.mark.asyncio
async def test_list_users_with_is_superuser_filter(self, client, superuser_token):
"""Test listing users with is_superuser filter (covers line 96)."""
response = await client.get(
"/api/v1/admin/users?is_superuser=true",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert "data" in data
@pytest.mark.asyncio
async def test_list_users_database_error_propagates(self, client, superuser_token):
"""Test that database errors propagate correctly (covers line 118-120)."""
with patch(
"app.api.routes.admin.user_crud.get_multi_with_total",
side_effect=Exception("DB error"),
):
with pytest.raises(Exception):
await client.get(
"/api/v1/admin/users",
headers={"Authorization": f"Bearer {superuser_token}"},
)
class TestAdminCreateUserErrors:
"""Test admin create user error handling."""
@pytest.mark.asyncio
async def test_create_user_duplicate_email(
self, client, async_test_user, superuser_token
):
"""Test creating user with duplicate email (covers line 145-150)."""
response = await client.post(
"/api/v1/admin/users",
headers={"Authorization": f"Bearer {superuser_token}"},
json={
"email": async_test_user.email,
"password": "NewPassword123!",
"first_name": "Duplicate",
"last_name": "User",
},
)
# Should get error for duplicate email
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_create_user_unexpected_error_propagates(
self, client, superuser_token
):
"""Test unexpected errors during user creation (covers line 151-153)."""
with patch(
"app.api.routes.admin.user_crud.create",
side_effect=RuntimeError("Unexpected error"),
):
with pytest.raises(RuntimeError):
await client.post(
"/api/v1/admin/users",
headers={"Authorization": f"Bearer {superuser_token}"},
json={
"email": "newerror@example.com",
"password": "NewPassword123!",
"first_name": "New",
"last_name": "User",
},
)
class TestAdminGetUserErrors:
"""Test admin get user error handling."""
@pytest.mark.asyncio
async def test_get_nonexistent_user(self, client, superuser_token):
"""Test getting a user that doesn't exist (covers line 170-175)."""
fake_id = uuid4()
response = await client.get(
f"/api/v1/admin/users/{fake_id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_404_NOT_FOUND
class TestAdminUpdateUserErrors:
"""Test admin update user error handling."""
@pytest.mark.asyncio
async def test_update_nonexistent_user(self, client, superuser_token):
"""Test updating a user that doesn't exist (covers line 194-198)."""
fake_id = uuid4()
response = await client.put(
f"/api/v1/admin/users/{fake_id}",
headers={"Authorization": f"Bearer {superuser_token}"},
json={"first_name": "Updated"},
)
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_update_user_unexpected_error(
self, client, async_test_user, superuser_token
):
"""Test unexpected errors during user update (covers line 206-208)."""
with patch(
"app.api.routes.admin.user_crud.update",
side_effect=RuntimeError("Update failed"),
):
with pytest.raises(RuntimeError):
await client.put(
f"/api/v1/admin/users/{async_test_user.id}",
headers={"Authorization": f"Bearer {superuser_token}"},
json={"first_name": "Updated"},
)
class TestAdminDeleteUserErrors:
"""Test admin delete user error handling."""
@pytest.mark.asyncio
async def test_delete_nonexistent_user(self, client, superuser_token):
"""Test deleting a user that doesn't exist (covers line 226-230)."""
fake_id = uuid4()
response = await client.delete(
f"/api/v1/admin/users/{fake_id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_delete_user_unexpected_error(
self, client, async_test_user, superuser_token
):
"""Test unexpected errors during user deletion (covers line 238-240)."""
with patch(
"app.api.routes.admin.user_crud.soft_delete",
side_effect=Exception("Delete failed"),
):
with pytest.raises(Exception):
await client.delete(
f"/api/v1/admin/users/{async_test_user.id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
class TestAdminActivateUserErrors:
"""Test admin activate user error handling."""
@pytest.mark.asyncio
async def test_activate_nonexistent_user(self, client, superuser_token):
"""Test activating a user that doesn't exist (covers line 270-274)."""
fake_id = uuid4()
response = await client.post(
f"/api/v1/admin/users/{fake_id}/activate",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_activate_user_unexpected_error(
self, client, async_test_user, superuser_token
):
"""Test unexpected errors during user activation (covers line 282-284)."""
with patch(
"app.api.routes.admin.user_crud.update",
side_effect=Exception("Activation failed"),
):
with pytest.raises(Exception):
await client.post(
f"/api/v1/admin/users/{async_test_user.id}/activate",
headers={"Authorization": f"Bearer {superuser_token}"},
)
class TestAdminDeactivateUserErrors:
"""Test admin deactivate user error handling."""
@pytest.mark.asyncio
async def test_deactivate_nonexistent_user(self, client, superuser_token):
"""Test deactivating a user that doesn't exist (covers line 306-310)."""
fake_id = uuid4()
response = await client.post(
f"/api/v1/admin/users/{fake_id}/deactivate",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_deactivate_self_forbidden(
self, client, async_test_superuser, superuser_token
):
"""Test that admin cannot deactivate themselves (covers line 319-323)."""
response = await client.post(
f"/api/v1/admin/users/{async_test_superuser.id}/deactivate",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_403_FORBIDDEN
@pytest.mark.asyncio
async def test_deactivate_user_unexpected_error(
self, client, async_test_user, superuser_token
):
"""Test unexpected errors during user deactivation (covers line 326-328)."""
with patch(
"app.api.routes.admin.user_crud.update",
side_effect=Exception("Deactivation failed"),
):
with pytest.raises(Exception):
await client.post(
f"/api/v1/admin/users/{async_test_user.id}/deactivate",
headers={"Authorization": f"Bearer {superuser_token}"},
)
# ===== ORGANIZATION MANAGEMENT ERROR TESTS =====
class TestAdminListOrganizationsErrors:
"""Test admin list organizations error handling."""
@pytest.mark.asyncio
async def test_list_organizations_database_error(self, client, superuser_token):
"""Test list organizations with database error (covers line 427-456)."""
with patch(
"app.api.routes.admin.organization_crud.get_multi_with_member_counts",
side_effect=Exception("DB error"),
):
with pytest.raises(Exception):
await client.get(
"/api/v1/admin/organizations",
headers={"Authorization": f"Bearer {superuser_token}"},
)
class TestAdminCreateOrganizationErrors:
"""Test admin create organization error handling."""
@pytest.mark.asyncio
async def test_create_organization_duplicate_slug(
self, client, async_test_db, superuser_token
):
"""Test creating organization with duplicate slug (covers line 480-483)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create an organization first
async with AsyncTestingSessionLocal() as session:
from app.models.organization import Organization
org = Organization(
name="Existing Org", slug="existing-org", description="Test org"
)
session.add(org)
await session.commit()
# Try to create another with same slug
response = await client.post(
"/api/v1/admin/organizations",
headers={"Authorization": f"Bearer {superuser_token}"},
json={
"name": "New Org",
"slug": "existing-org",
"description": "Duplicate slug",
},
)
# Should get error for duplicate slug
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_create_organization_unexpected_error(self, client, superuser_token):
"""Test unexpected errors during organization creation (covers line 484-485)."""
with patch(
"app.api.routes.admin.organization_crud.create",
side_effect=RuntimeError("Creation failed"),
):
with pytest.raises(RuntimeError):
await client.post(
"/api/v1/admin/organizations",
headers={"Authorization": f"Bearer {superuser_token}"},
json={"name": "New Org", "slug": "new-org", "description": "Test"},
)
class TestAdminGetOrganizationErrors:
"""Test admin get organization error handling."""
@pytest.mark.asyncio
async def test_get_nonexistent_organization(self, client, superuser_token):
"""Test getting an organization that doesn't exist (covers line 516-520)."""
fake_id = uuid4()
response = await client.get(
f"/api/v1/admin/organizations/{fake_id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_404_NOT_FOUND
class TestAdminUpdateOrganizationErrors:
"""Test admin update organization error handling."""
@pytest.mark.asyncio
async def test_update_nonexistent_organization(self, client, superuser_token):
"""Test updating an organization that doesn't exist (covers line 552-556)."""
fake_id = uuid4()
response = await client.put(
f"/api/v1/admin/organizations/{fake_id}",
headers={"Authorization": f"Bearer {superuser_token}"},
json={"name": "Updated Org"},
)
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_update_organization_unexpected_error(
self, client, async_test_db, superuser_token
):
"""Test unexpected errors during organization update (covers line 573-575)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create an organization
async with AsyncTestingSessionLocal() as session:
from app.models.organization import Organization
org = Organization(
name="Test Org", slug="test-org-update-error", description="Test"
)
session.add(org)
await session.commit()
await session.refresh(org)
org_id = org.id
with patch(
"app.api.routes.admin.organization_crud.update",
side_effect=Exception("Update failed"),
):
with pytest.raises(Exception):
await client.put(
f"/api/v1/admin/organizations/{org_id}",
headers={"Authorization": f"Bearer {superuser_token}"},
json={"name": "Updated"},
)
class TestAdminDeleteOrganizationErrors:
"""Test admin delete organization error handling."""
@pytest.mark.asyncio
async def test_delete_nonexistent_organization(self, client, superuser_token):
"""Test deleting an organization that doesn't exist (covers line 596-600)."""
fake_id = uuid4()
response = await client.delete(
f"/api/v1/admin/organizations/{fake_id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_delete_organization_unexpected_error(
self, client, async_test_db, superuser_token
):
"""Test unexpected errors during organization deletion (covers line 611-613)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create organization
async with AsyncTestingSessionLocal() as session:
from app.models.organization import Organization
org = Organization(
name="Error Org", slug="error-org-delete", description="Test"
)
session.add(org)
await session.commit()
await session.refresh(org)
org_id = org.id
with patch(
"app.api.routes.admin.organization_crud.remove",
side_effect=Exception("Delete failed"),
):
with pytest.raises(Exception):
await client.delete(
f"/api/v1/admin/organizations/{org_id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
class TestAdminListOrganizationMembersErrors:
"""Test admin list organization members error handling."""
@pytest.mark.asyncio
async def test_list_members_nonexistent_organization(self, client, superuser_token):
"""Test listing members of non-existent organization (covers line 634-638)."""
fake_id = uuid4()
response = await client.get(
f"/api/v1/admin/organizations/{fake_id}/members",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_list_members_database_error(
self, client, async_test_db, superuser_token
):
"""Test database errors during member listing (covers line 660-662)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create organization
async with AsyncTestingSessionLocal() as session:
from app.models.organization import Organization
org = Organization(
name="Members Error Org", slug="members-error-org", description="Test"
)
session.add(org)
await session.commit()
await session.refresh(org)
org_id = org.id
with patch(
"app.api.routes.admin.organization_crud.get_organization_members",
side_effect=Exception("DB error"),
):
with pytest.raises(Exception):
await client.get(
f"/api/v1/admin/organizations/{org_id}/members",
headers={"Authorization": f"Bearer {superuser_token}"},
)
class TestAdminAddOrganizationMemberErrors:
"""Test admin add organization member error handling."""
@pytest.mark.asyncio
async def test_add_member_nonexistent_organization(
self, client, async_test_user, superuser_token
):
"""Test adding member to non-existent organization (covers line 689-693)."""
fake_id = uuid4()
response = await client.post(
f"/api/v1/admin/organizations/{fake_id}/members",
headers={"Authorization": f"Bearer {superuser_token}"},
json={"user_id": str(async_test_user.id), "role": "member"},
)
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_add_nonexistent_user_to_organization(
self, client, async_test_db, superuser_token
):
"""Test adding non-existent user to organization (covers line 696-700)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create organization
async with AsyncTestingSessionLocal() as session:
from app.models.organization import Organization
org = Organization(
name="Add Member Org", slug="add-member-org", description="Test"
)
session.add(org)
await session.commit()
await session.refresh(org)
org_id = org.id
fake_user_id = uuid4()
response = await client.post(
f"/api/v1/admin/organizations/{org_id}/members",
headers={"Authorization": f"Bearer {superuser_token}"},
json={"user_id": str(fake_user_id), "role": "member"},
)
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_add_member_unexpected_error(
self, client, async_test_db, async_test_user, superuser_token
):
"""Test unexpected errors during member addition (covers line 727-729)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create organization
async with AsyncTestingSessionLocal() as session:
from app.models.organization import Organization
org = Organization(
name="Error Add Org", slug="error-add-org", description="Test"
)
session.add(org)
await session.commit()
await session.refresh(org)
org_id = org.id
with patch(
"app.api.routes.admin.organization_crud.add_user",
side_effect=Exception("Add failed"),
):
with pytest.raises(Exception):
await client.post(
f"/api/v1/admin/organizations/{org_id}/members",
headers={"Authorization": f"Bearer {superuser_token}"},
json={"user_id": str(async_test_user.id), "role": "member"},
)
class TestAdminRemoveOrganizationMemberErrors:
"""Test admin remove organization member error handling."""
@pytest.mark.asyncio
async def test_remove_member_nonexistent_organization(
self, client, async_test_user, superuser_token
):
"""Test removing member from non-existent organization (covers line 750-754)."""
fake_id = uuid4()
response = await client.delete(
f"/api/v1/admin/organizations/{fake_id}/members/{async_test_user.id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_remove_member_unexpected_error(
self, client, async_test_db, async_test_user, superuser_token
):
"""Test unexpected errors during member removal (covers line 780-782)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create organization with member
async with AsyncTestingSessionLocal() as session:
from app.models.organization import Organization
from app.models.user_organization import OrganizationRole, UserOrganization
org = Organization(
name="Remove Member Org", slug="remove-member-org", description="Test"
)
session.add(org)
await session.commit()
await session.refresh(org)
member = UserOrganization(
user_id=async_test_user.id,
organization_id=org.id,
role=OrganizationRole.MEMBER,
)
session.add(member)
await session.commit()
org_id = org.id
with patch(
"app.api.routes.admin.organization_crud.remove_user",
side_effect=Exception("Remove failed"),
):
with pytest.raises(Exception):
await client.delete(
f"/api/v1/admin/organizations/{org_id}/members/{async_test_user.id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
# ===== SUCCESS PATH TESTS =====
class TestAdminListUsersSuccess:
"""Test admin list users success paths."""
@pytest.mark.asyncio
async def test_list_users_with_pagination(self, client, superuser_token):
"""Test listing users with pagination (covers lines 109-116)."""
response = await client.get(
"/api/v1/admin/users?page=1&limit=10",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert "data" in data
assert "pagination" in data
class TestAdminCreateUserSuccess:
"""Test admin create user success paths."""
@pytest.mark.asyncio
async def test_create_user_success(self, client, superuser_token):
"""Test creating a user successfully (covers lines 142-144)."""
response = await client.post(
"/api/v1/admin/users",
headers={"Authorization": f"Bearer {superuser_token}"},
json={
"email": f"newuser{uuid4().hex[:8]}@example.com",
"password": "NewPassword123!",
"first_name": "New",
"last_name": "User",
},
)
assert response.status_code == status.HTTP_201_CREATED
data = response.json()
assert "email" in data
assert "id" in data
class TestAdminUpdateUserSuccess:
"""Test admin update user success paths."""
@pytest.mark.asyncio
async def test_update_user_success(self, client, async_test_user, superuser_token):
"""Test updating user successfully (covers lines 194-202)."""
response = await client.put(
f"/api/v1/admin/users/{async_test_user.id}",
headers={"Authorization": f"Bearer {superuser_token}"},
json={"first_name": "Updated"},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["first_name"] == "Updated"
class TestAdminDeleteUserSuccess:
"""Test admin delete user success paths."""
@pytest.mark.asyncio
async def test_delete_user_success(self, client, async_test_db, superuser_token):
"""Test deleting user successfully (covers lines 226-246)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create a user to delete
async with AsyncTestingSessionLocal() as session:
from app.core.auth import get_password_hash
from app.models.user import User
user_to_delete = User(
email=f"delete{uuid4().hex[:8]}@example.com",
password_hash=get_password_hash("Password123!"),
first_name="Delete",
last_name="Me",
)
session.add(user_to_delete)
await session.commit()
await session.refresh(user_to_delete)
user_id = user_to_delete.id
response = await client.delete(
f"/api/v1/admin/users/{user_id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["success"] is True
@pytest.mark.asyncio
async def test_delete_self_fails(
self, client, async_test_superuser, superuser_token
):
"""Test that admin cannot delete themselves (covers lines 233-238)."""
response = await client.delete(
f"/api/v1/admin/users/{async_test_superuser.id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_403_FORBIDDEN
class TestAdminActivateUserSuccess:
"""Test admin activate user success paths."""
@pytest.mark.asyncio
async def test_activate_user_success(self, client, async_test_db, superuser_token):
"""Test activating user successfully (covers lines 270-282)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create inactive user
async with AsyncTestingSessionLocal() as session:
from app.core.auth import get_password_hash
from app.models.user import User
inactive_user = User(
email=f"inactive{uuid4().hex[:8]}@example.com",
password_hash=get_password_hash("Password123!"),
first_name="Inactive",
last_name="User",
is_active=False,
)
session.add(inactive_user)
await session.commit()
await session.refresh(inactive_user)
user_id = inactive_user.id
response = await client.post(
f"/api/v1/admin/users/{user_id}/activate",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["success"] is True
class TestAdminDeactivateUserSuccess:
"""Test admin deactivate user success paths."""
@pytest.mark.asyncio
async def test_deactivate_user_success(
self, client, async_test_user, superuser_token
):
"""Test deactivating user successfully (covers lines 306-326)."""
response = await client.post(
f"/api/v1/admin/users/{async_test_user.id}/deactivate",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["success"] is True
@pytest.mark.asyncio
async def test_deactivate_self_fails(
self, client, async_test_superuser, superuser_token
):
"""Test that admin cannot deactivate themselves (covers lines 313-318)."""
response = await client.post(
f"/api/v1/admin/users/{async_test_superuser.id}/deactivate",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_403_FORBIDDEN
class TestAdminBulkUserActionSuccess:
"""Test admin bulk user action success paths."""
@pytest.mark.asyncio
async def test_bulk_activate_success(self, client, async_test_db, superuser_token):
"""Test bulk activate users (covers lines 355-360, 375-392)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create inactive users
user_ids = []
async with AsyncTestingSessionLocal() as session:
from app.core.auth import get_password_hash
from app.models.user import User
for i in range(2):
user = User(
email=f"bulkuser{i}{uuid4().hex[:8]}@example.com",
password_hash=get_password_hash("Password123!"),
first_name="Bulk",
last_name=f"User{i}",
is_active=False,
)
session.add(user)
await session.commit()
await session.refresh(user)
user_ids.append(str(user.id))
response = await client.post(
"/api/v1/admin/users/bulk-action",
headers={"Authorization": f"Bearer {superuser_token}"},
json={"action": "activate", "user_ids": user_ids},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["affected_count"] >= 0
@pytest.mark.asyncio
async def test_bulk_deactivate_success(
self, client, async_test_db, superuser_token
):
"""Test bulk deactivate users (covers lines 361-366)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create active users
user_ids = []
async with AsyncTestingSessionLocal() as session:
from app.core.auth import get_password_hash
from app.models.user import User
for i in range(2):
user = User(
email=f"bulkdeact{i}{uuid4().hex[:8]}@example.com",
password_hash=get_password_hash("Password123!"),
first_name="Bulk",
last_name=f"Deactivate{i}",
is_active=True,
)
session.add(user)
await session.commit()
await session.refresh(user)
user_ids.append(str(user.id))
response = await client.post(
"/api/v1/admin/users/bulk-action",
headers={"Authorization": f"Bearer {superuser_token}"},
json={"action": "deactivate", "user_ids": user_ids},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["affected_count"] >= 0
@pytest.mark.asyncio
async def test_bulk_delete_success(self, client, async_test_db, superuser_token):
"""Test bulk delete users (covers lines 367-373)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create users to delete
user_ids = []
async with AsyncTestingSessionLocal() as session:
from app.core.auth import get_password_hash
from app.models.user import User
for i in range(2):
user = User(
email=f"bulkdel{i}{uuid4().hex[:8]}@example.com",
password_hash=get_password_hash("Password123!"),
first_name="Bulk",
last_name=f"Delete{i}",
)
session.add(user)
await session.commit()
await session.refresh(user)
user_ids.append(str(user.id))
response = await client.post(
"/api/v1/admin/users/bulk-action",
headers={"Authorization": f"Bearer {superuser_token}"},
json={"action": "delete", "user_ids": user_ids},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["affected_count"] >= 0
class TestAdminListOrganizationsSuccess:
"""Test admin list organizations success paths."""
@pytest.mark.asyncio
async def test_list_organizations_with_pagination(self, client, superuser_token):
"""Test listing organizations with pagination (covers lines 427-452)."""
response = await client.get(
"/api/v1/admin/organizations?page=1&limit=10",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert "data" in data
assert "pagination" in data
class TestAdminCreateOrganizationSuccess:
"""Test admin create organization success paths."""
@pytest.mark.asyncio
async def test_create_organization_success(self, client, superuser_token):
"""Test creating organization successfully (covers lines 475-489)."""
unique_slug = f"neworg{uuid4().hex[:8]}"
response = await client.post(
"/api/v1/admin/organizations",
headers={"Authorization": f"Bearer {superuser_token}"},
json={
"name": "New Organization",
"slug": unique_slug,
"description": "Test org",
},
)
assert response.status_code == status.HTTP_201_CREATED
data = response.json()
assert "id" in data
assert data["slug"] == unique_slug
class TestAdminGetOrganizationSuccess:
"""Test admin get organization success paths."""
@pytest.mark.asyncio
async def test_get_organization_success(
self, client, async_test_db, superuser_token
):
"""Test getting organization successfully (covers lines 516-533)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create organization
async with AsyncTestingSessionLocal() as session:
from app.models.organization import Organization
org = Organization(
name="Get Org", slug=f"getorg{uuid4().hex[:8]}", description="Test"
)
session.add(org)
await session.commit()
await session.refresh(org)
org_id = org.id
response = await client.get(
f"/api/v1/admin/organizations/{org_id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["id"] == str(org_id)
class TestAdminUpdateOrganizationSuccess:
"""Test admin update organization success paths."""
@pytest.mark.asyncio
async def test_update_organization_success(
self, client, async_test_db, superuser_token
):
"""Test updating organization successfully (covers lines 552-572)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create organization
async with AsyncTestingSessionLocal() as session:
from app.models.organization import Organization
org = Organization(
name="Update Org",
slug=f"updateorg{uuid4().hex[:8]}",
description="Test",
)
session.add(org)
await session.commit()
await session.refresh(org)
org_id = org.id
response = await client.put(
f"/api/v1/admin/organizations/{org_id}",
headers={"Authorization": f"Bearer {superuser_token}"},
json={"name": "Updated Org Name"},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["name"] == "Updated Org Name"
class TestAdminDeleteOrganizationSuccess:
"""Test admin delete organization success paths."""
@pytest.mark.asyncio
async def test_delete_organization_success(
self, client, async_test_db, superuser_token
):
"""Test deleting organization successfully (covers lines 596-608)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create organization
async with AsyncTestingSessionLocal() as session:
from app.models.organization import Organization
org = Organization(
name="Delete Org",
slug=f"deleteorg{uuid4().hex[:8]}",
description="Test",
)
session.add(org)
await session.commit()
await session.refresh(org)
org_id = org.id
response = await client.delete(
f"/api/v1/admin/organizations/{org_id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["success"] is True
class TestAdminListOrganizationMembersSuccess:
"""Test admin list organization members success paths."""
@pytest.mark.asyncio
async def test_list_organization_members_success(
self, client, async_test_db, async_test_user, superuser_token
):
"""Test listing organization members successfully (covers lines 634-658)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create organization with member
async with AsyncTestingSessionLocal() as session:
from app.models.organization import Organization
from app.models.user_organization import OrganizationRole, UserOrganization
org = Organization(
name="Members Org",
slug=f"membersorg{uuid4().hex[:8]}",
description="Test",
)
session.add(org)
await session.commit()
await session.refresh(org)
member = UserOrganization(
user_id=async_test_user.id,
organization_id=org.id,
role=OrganizationRole.MEMBER,
)
session.add(member)
await session.commit()
org_id = org.id
response = await client.get(
f"/api/v1/admin/organizations/{org_id}/members",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert "data" in data
assert "pagination" in data
class TestAdminAddOrganizationMemberSuccess:
"""Test admin add organization member success paths."""
@pytest.mark.asyncio
async def test_add_member_success(
self, client, async_test_db, async_test_user, superuser_token
):
"""Test adding member to organization successfully (covers lines 689-717)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create organization
async with AsyncTestingSessionLocal() as session:
from app.models.organization import Organization
org = Organization(
name="Add Member Org",
slug=f"addmemberorg{uuid4().hex[:8]}",
description="Test",
)
session.add(org)
await session.commit()
await session.refresh(org)
org_id = org.id
response = await client.post(
f"/api/v1/admin/organizations/{org_id}/members",
headers={"Authorization": f"Bearer {superuser_token}"},
json={"user_id": str(async_test_user.id), "role": "member"},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["success"] is True
class TestAdminRemoveOrganizationMemberSuccess:
"""Test admin remove organization member success paths."""
@pytest.mark.asyncio
async def test_remove_member_success(
self, client, async_test_db, async_test_user, superuser_token
):
"""Test removing member from organization successfully (covers lines 750-780)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create organization with member
async with AsyncTestingSessionLocal() as session:
from app.models.organization import Organization
from app.models.user_organization import OrganizationRole, UserOrganization
org = Organization(
name="Remove Member Success Org",
slug=f"removemembersuccess{uuid4().hex[:8]}",
description="Test",
)
session.add(org)
await session.commit()
await session.refresh(org)
member = UserOrganization(
user_id=async_test_user.id,
organization_id=org.id,
role=OrganizationRole.MEMBER,
)
session.add(member)
await session.commit()
org_id = org.id
response = await client.delete(
f"/api/v1/admin/organizations/{org_id}/members/{async_test_user.id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["success"] is True
@pytest.mark.asyncio
async def test_remove_nonmember_fails(
self, client, async_test_db, async_test_user, superuser_token
):
"""Test removing non-member fails (covers lines 769-773)."""
_test_engine, AsyncTestingSessionLocal = async_test_db
# Create organization without member
async with AsyncTestingSessionLocal() as session:
from app.models.organization import Organization
org = Organization(
name="No Member Org",
slug=f"nomemberorg{uuid4().hex[:8]}",
description="Test",
)
session.add(org)
await session.commit()
await session.refresh(org)
org_id = org.id
response = await client.delete(
f"/api/v1/admin/organizations/{org_id}/members/{async_test_user.id}",
headers={"Authorization": f"Bearer {superuser_token}"},
)
assert response.status_code == status.HTTP_404_NOT_FOUND