649 lines
22 KiB
Python
649 lines
22 KiB
Python
import pytest
|
|
from fastapi import status
|
|
from httpx import AsyncClient
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from typing import Callable, Dict, Any
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
from app.models import User as UserModel, Group as GroupModel, List as ListModel
|
|
from app.schemas.expense import ExpenseCreate, ExpensePublic, ExpenseUpdate
|
|
# from app.config import settings # Comment out the original import
|
|
|
|
# Helper to create a URL for an endpoint
|
|
# API_V1_STR = settings.API_V1_STR # Comment out the original assignment
|
|
|
|
@pytest.fixture(scope="module")
|
|
def mock_settings_financials():
|
|
mock_settings = MagicMock()
|
|
mock_settings.API_V1_STR = "/api/v1"
|
|
return mock_settings
|
|
|
|
# Patch the settings in the test module
|
|
@pytest.fixture(autouse=True)
|
|
def patch_settings_financials(mock_settings_financials):
|
|
with patch("app.config.settings", mock_settings_financials):
|
|
yield
|
|
|
|
def expense_url(endpoint: str = "") -> str:
|
|
# Use the mocked API_V1_STR via the patched settings object
|
|
from app.config import settings # Import settings here to use the patched version
|
|
return f"{settings.API_V1_STR}/financials/expenses{endpoint}"
|
|
|
|
def settlement_url(endpoint: str = "") -> str:
|
|
# Use the mocked API_V1_STR via the patched settings object
|
|
from app.config import settings # Import settings here to use the patched version
|
|
return f"{settings.API_V1_STR}/financials/settlements{endpoint}"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_new_expense_success_list_context(
|
|
client: AsyncClient,
|
|
db_session: AsyncSession,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
test_list_user_is_member: ListModel,
|
|
) -> None:
|
|
expense_data = ExpenseCreate(
|
|
description="Test Expense for List",
|
|
amount=100.00,
|
|
currency="USD",
|
|
paid_by_user_id=test_user.id,
|
|
list_id=test_list_user_is_member.id,
|
|
group_id=None,
|
|
)
|
|
|
|
response = await client.post(
|
|
expense_url(),
|
|
headers=normal_user_token_headers,
|
|
json=expense_data.model_dump(exclude_unset=True)
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_201_CREATED
|
|
content = response.json()
|
|
assert content["description"] == expense_data.description
|
|
assert content["amount"] == expense_data.amount
|
|
assert content["currency"] == expense_data.currency
|
|
assert content["paid_by_user_id"] == test_user.id
|
|
assert content["list_id"] == test_list_user_is_member.id
|
|
if test_list_user_is_member.group_id:
|
|
assert content["group_id"] == test_list_user_is_member.group_id
|
|
else:
|
|
assert content["group_id"] is None
|
|
assert "id" in content
|
|
assert "created_at" in content
|
|
assert "updated_at" in content
|
|
assert "version" in content
|
|
assert content["version"] == 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_new_expense_success_group_context(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
test_group_user_is_member: GroupModel,
|
|
) -> None:
|
|
expense_data = ExpenseCreate(
|
|
description="Test Expense for Group",
|
|
amount=50.00,
|
|
currency="EUR",
|
|
paid_by_user_id=test_user.id,
|
|
group_id=test_group_user_is_member.id,
|
|
list_id=None,
|
|
)
|
|
|
|
response = await client.post(
|
|
expense_url(),
|
|
headers=normal_user_token_headers,
|
|
json=expense_data.model_dump(exclude_unset=True)
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_201_CREATED
|
|
content = response.json()
|
|
assert content["description"] == expense_data.description
|
|
assert content["paid_by_user_id"] == test_user.id
|
|
assert content["group_id"] == test_group_user_is_member.id
|
|
assert content["list_id"] is None
|
|
assert content["version"] == 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_new_expense_fail_no_list_or_group(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
) -> None:
|
|
expense_data = ExpenseCreate(
|
|
description="Test Invalid Expense",
|
|
amount=10.00,
|
|
currency="USD",
|
|
paid_by_user_id=test_user.id,
|
|
list_id=None,
|
|
group_id=None,
|
|
)
|
|
|
|
response = await client.post(
|
|
expense_url(),
|
|
headers=normal_user_token_headers,
|
|
json=expense_data.model_dump(exclude_unset=True)
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
content = response.json()
|
|
assert "Expense must be linked to a list_id or group_id" in content["detail"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_new_expense_fail_paid_by_other_not_owner(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
test_group_user_is_member: GroupModel,
|
|
another_user_in_group: UserModel,
|
|
) -> None:
|
|
expense_data = ExpenseCreate(
|
|
description="Expense paid by other",
|
|
amount=75.00,
|
|
currency="GBP",
|
|
paid_by_user_id=another_user_in_group.id,
|
|
group_id=test_group_user_is_member.id,
|
|
list_id=None,
|
|
)
|
|
|
|
response = await client.post(
|
|
expense_url(),
|
|
headers=normal_user_token_headers,
|
|
json=expense_data.model_dump(exclude_unset=True)
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
content = response.json()
|
|
assert "Only group owners can create expenses paid by others" in content["detail"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_expense_success(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
created_expense: ExpensePublic,
|
|
) -> None:
|
|
response = await client.get(
|
|
expense_url(f"/{created_expense.id}"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
content = response.json()
|
|
assert content["id"] == created_expense.id
|
|
assert content["description"] == created_expense.description
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_expense_not_found(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
) -> None:
|
|
response = await client.get(
|
|
expense_url("/999"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_404_NOT_FOUND
|
|
content = response.json()
|
|
assert "Expense not found" in content["detail"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_expense_forbidden_personal_expense_other_user(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
personal_expense_of_another_user: ExpensePublic,
|
|
) -> None:
|
|
response = await client.get(
|
|
expense_url(f"/{personal_expense_of_another_user.id}"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
content = response.json()
|
|
assert "You do not have permission to access this expense" in content["detail"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_expense_forbidden_not_member_of_list_or_group(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
another_user: UserModel,
|
|
expense_in_inaccessible_list_or_group: ExpensePublic,
|
|
) -> None:
|
|
response = await client.get(
|
|
expense_url(f"/{expense_in_inaccessible_list_or_group.id}"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
content = response.json()
|
|
assert "You do not have permission to access this expense" in content["detail"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_expense_success_in_list_user_has_access(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
expense_in_accessible_list: ExpensePublic,
|
|
test_list_user_is_member: ListModel,
|
|
) -> None:
|
|
response = await client.get(
|
|
expense_url(f"/{expense_in_accessible_list.id}"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
content = response.json()
|
|
assert content["id"] == expense_in_accessible_list.id
|
|
assert content["list_id"] == test_list_user_is_member.id
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_expense_success_in_group_user_has_access(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
expense_in_accessible_group: ExpensePublic,
|
|
test_group_user_is_member: GroupModel,
|
|
) -> None:
|
|
response = await client.get(
|
|
expense_url(f"/{expense_in_accessible_group.id}"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
content = response.json()
|
|
assert content["id"] == expense_in_accessible_group.id
|
|
assert content["group_id"] == test_group_user_is_member.id
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_list_expenses_success(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
test_list_user_is_member: ListModel,
|
|
) -> None:
|
|
response = await client.get(
|
|
expense_url(f"?list_id={test_list_user_is_member.id}"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
content = response.json()
|
|
assert isinstance(content, list)
|
|
for expense in content:
|
|
assert expense["list_id"] == test_list_user_is_member.id
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_list_expenses_list_not_found(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
) -> None:
|
|
response = await client.get(
|
|
expense_url("?list_id=999"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_404_NOT_FOUND
|
|
content = response.json()
|
|
assert "List not found" in content["detail"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_list_expenses_no_access(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_list_user_not_member: ListModel,
|
|
) -> None:
|
|
response = await client.get(
|
|
expense_url(f"?list_id={test_list_user_not_member.id}"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
content = response.json()
|
|
assert "You do not have permission to access this list" in content["detail"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_list_expenses_empty(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_list_user_is_member_no_expenses: ListModel,
|
|
) -> None:
|
|
response = await client.get(
|
|
expense_url(f"?list_id={test_list_user_is_member_no_expenses.id}"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
content = response.json()
|
|
assert isinstance(content, list)
|
|
assert len(content) == 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_list_expenses_pagination(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
test_list_with_multiple_expenses: ListModel,
|
|
created_expenses_for_list: list[ExpensePublic],
|
|
) -> None:
|
|
# Test first page
|
|
response = await client.get(
|
|
expense_url(f"?list_id={test_list_with_multiple_expenses.id}&skip=0&limit=2"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
content = response.json()
|
|
assert isinstance(content, list)
|
|
assert len(content) == 2
|
|
assert content[0]["id"] == created_expenses_for_list[0].id
|
|
assert content[1]["id"] == created_expenses_for_list[1].id
|
|
|
|
# Test second page
|
|
response = await client.get(
|
|
expense_url(f"?list_id={test_list_with_multiple_expenses.id}&skip=2&limit=2"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
content = response.json()
|
|
assert isinstance(content, list)
|
|
assert len(content) == 2
|
|
assert content[0]["id"] == created_expenses_for_list[2].id
|
|
assert content[1]["id"] == created_expenses_for_list[3].id
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_group_expenses_success(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
test_group_user_is_member: GroupModel,
|
|
) -> None:
|
|
response = await client.get(
|
|
expense_url(f"?group_id={test_group_user_is_member.id}"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
content = response.json()
|
|
assert isinstance(content, list)
|
|
for expense in content:
|
|
assert expense["group_id"] == test_group_user_is_member.id
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_group_expenses_group_not_found(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
) -> None:
|
|
response = await client.get(
|
|
expense_url("?group_id=999"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_404_NOT_FOUND
|
|
content = response.json()
|
|
assert "Group not found" in content["detail"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_group_expenses_no_access(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_group_user_not_member: GroupModel,
|
|
) -> None:
|
|
response = await client.get(
|
|
expense_url(f"?group_id={test_group_user_not_member.id}"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
content = response.json()
|
|
assert "You do not have permission to access this group" in content["detail"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_group_expenses_empty(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_group_user_is_member_no_expenses: GroupModel,
|
|
) -> None:
|
|
response = await client.get(
|
|
expense_url(f"?group_id={test_group_user_is_member_no_expenses.id}"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
content = response.json()
|
|
assert isinstance(content, list)
|
|
assert len(content) == 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_group_expenses_pagination(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
test_group_with_multiple_expenses: GroupModel,
|
|
created_expenses_for_group: list[ExpensePublic],
|
|
) -> None:
|
|
# Test first page
|
|
response = await client.get(
|
|
expense_url(f"?group_id={test_group_with_multiple_expenses.id}&skip=0&limit=2"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
content = response.json()
|
|
assert isinstance(content, list)
|
|
assert len(content) == 2
|
|
assert content[0]["id"] == created_expenses_for_group[0].id
|
|
assert content[1]["id"] == created_expenses_for_group[1].id
|
|
|
|
# Test second page
|
|
response = await client.get(
|
|
expense_url(f"?group_id={test_group_with_multiple_expenses.id}&skip=2&limit=2"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_200_OK
|
|
content = response.json()
|
|
assert isinstance(content, list)
|
|
assert len(content) == 2
|
|
assert content[0]["id"] == created_expenses_for_group[2].id
|
|
assert content[1]["id"] == created_expenses_for_group[3].id
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_expense_success_payer_updates_details(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
expense_paid_by_test_user: ExpensePublic,
|
|
) -> None:
|
|
update_data = ExpenseUpdate(
|
|
description="Updated expense description",
|
|
version=expense_paid_by_test_user.version,
|
|
)
|
|
|
|
response = await client.put(
|
|
expense_url(f"/{expense_paid_by_test_user.id}"),
|
|
headers=normal_user_token_headers,
|
|
json=update_data.model_dump(exclude_unset=True)
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
content = response.json()
|
|
assert content["description"] == update_data.description
|
|
assert content["version"] == expense_paid_by_test_user.version + 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_expense_success_group_owner_updates_others_expense(
|
|
client: AsyncClient,
|
|
group_owner_token_headers: Dict[str, str],
|
|
group_owner: UserModel,
|
|
expense_paid_by_another_in_group_where_test_user_is_owner: ExpensePublic,
|
|
another_user_in_group: UserModel,
|
|
) -> None:
|
|
update_data = ExpenseUpdate(
|
|
description="Updated by group owner",
|
|
version=expense_paid_by_another_in_group_where_test_user_is_owner.version,
|
|
)
|
|
|
|
response = await client.put(
|
|
expense_url(f"/{expense_paid_by_another_in_group_where_test_user_is_owner.id}"),
|
|
headers=group_owner_token_headers,
|
|
json=update_data.model_dump(exclude_unset=True)
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
content = response.json()
|
|
assert content["description"] == update_data.description
|
|
assert content["version"] == expense_paid_by_another_in_group_where_test_user_is_owner.version + 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_expense_fail_not_payer_nor_group_owner(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
expense_paid_by_another_in_group_where_test_user_is_member: ExpensePublic,
|
|
another_user_in_group: UserModel,
|
|
) -> None:
|
|
update_data = ExpenseUpdate(
|
|
description="Attempted update by non-owner",
|
|
version=expense_paid_by_another_in_group_where_test_user_is_member.version,
|
|
)
|
|
|
|
response = await client.put(
|
|
expense_url(f"/{expense_paid_by_another_in_group_where_test_user_is_member.id}"),
|
|
headers=normal_user_token_headers,
|
|
json=update_data.model_dump(exclude_unset=True)
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
content = response.json()
|
|
assert "You do not have permission to update this expense" in content["detail"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_expense_fail_not_found(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
) -> None:
|
|
update_data = ExpenseUpdate(
|
|
description="Update attempt on non-existent expense",
|
|
version=1,
|
|
)
|
|
|
|
response = await client.put(
|
|
expense_url("/999"),
|
|
headers=normal_user_token_headers,
|
|
json=update_data.model_dump(exclude_unset=True)
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_404_NOT_FOUND
|
|
content = response.json()
|
|
assert "Expense not found" in content["detail"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_expense_fail_change_paid_by_user_not_owner(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
expense_paid_by_test_user_in_group: ExpensePublic,
|
|
another_user_in_same_group: UserModel,
|
|
) -> None:
|
|
update_data = ExpenseUpdate(
|
|
paid_by_user_id=another_user_in_same_group.id,
|
|
version=expense_paid_by_test_user_in_group.version,
|
|
)
|
|
|
|
response = await client.put(
|
|
expense_url(f"/{expense_paid_by_test_user_in_group.id}"),
|
|
headers=normal_user_token_headers,
|
|
json=update_data.model_dump(exclude_unset=True)
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
content = response.json()
|
|
assert "Only group owners can change the payer of an expense" in content["detail"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_expense_success_owner_changes_paid_by_user(
|
|
client: AsyncClient,
|
|
group_owner_token_headers: Dict[str, str],
|
|
group_owner: UserModel,
|
|
expense_in_group_owner_group: ExpensePublic,
|
|
another_user_in_same_group: UserModel,
|
|
) -> None:
|
|
update_data = ExpenseUpdate(
|
|
paid_by_user_id=another_user_in_same_group.id,
|
|
version=expense_in_group_owner_group.version,
|
|
)
|
|
|
|
response = await client.put(
|
|
expense_url(f"/{expense_in_group_owner_group.id}"),
|
|
headers=group_owner_token_headers,
|
|
json=update_data.model_dump(exclude_unset=True)
|
|
)
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
content = response.json()
|
|
assert content["paid_by_user_id"] == another_user_in_same_group.id
|
|
assert content["version"] == expense_in_group_owner_group.version + 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_expense_success_payer(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
expense_paid_by_test_user: ExpensePublic,
|
|
) -> None:
|
|
response = await client.delete(
|
|
expense_url(f"/{expense_paid_by_test_user.id}"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_204_NO_CONTENT
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_expense_success_group_owner(
|
|
client: AsyncClient,
|
|
group_owner_token_headers: Dict[str, str],
|
|
group_owner: UserModel,
|
|
expense_paid_by_another_in_group_where_test_user_is_owner: ExpensePublic,
|
|
) -> None:
|
|
response = await client.delete(
|
|
expense_url(f"/{expense_paid_by_another_in_group_where_test_user_is_owner.id}"),
|
|
headers=group_owner_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_204_NO_CONTENT
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_expense_fail_not_payer_nor_group_owner(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
test_user: UserModel,
|
|
expense_paid_by_another_in_group_where_test_user_is_member: ExpensePublic,
|
|
) -> None:
|
|
response = await client.delete(
|
|
expense_url(f"/{expense_paid_by_another_in_group_where_test_user_is_member.id}"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
content = response.json()
|
|
assert "You do not have permission to delete this expense" in content["detail"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_expense_fail_not_found(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
) -> None:
|
|
response = await client.delete(
|
|
expense_url("/999"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_404_NOT_FOUND
|
|
content = response.json()
|
|
assert "Expense not found" in content["detail"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_expense_idempotency(
|
|
client: AsyncClient,
|
|
normal_user_token_headers: Dict[str, str],
|
|
expense_paid_by_test_user: ExpensePublic,
|
|
) -> None:
|
|
# First delete
|
|
response = await client.delete(
|
|
expense_url(f"/{expense_paid_by_test_user.id}"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_204_NO_CONTENT
|
|
|
|
# Second delete should also succeed
|
|
response = await client.delete(
|
|
expense_url(f"/{expense_paid_by_test_user.id}"),
|
|
headers=normal_user_token_headers
|
|
)
|
|
assert response.status_code == status.HTTP_204_NO_CONTENT
|
|
|
|
# GET /settlements/{settlement_id}
|
|
# POST /settlements
|
|
# GET /groups/{group_id}/settlements
|
|
# PUT /settlements/{settlement_id}
|
|
# DELETE /settlements/{settlement_id} |