fixes for pytest

This commit is contained in:
c-d-p
2025-04-27 02:08:36 +02:00
parent 8c13906f2b
commit 8e2da8c5dc
13 changed files with 189 additions and 37 deletions

View File

@@ -1,7 +1,8 @@
from fastapi import status
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone # Add timezone
from pytest_mock import MockerFixture # Import MockerFixture
from tests.helpers import generators
from modules.calendar.models import CalendarEvent # Assuming model exists
@@ -10,13 +11,14 @@ from tests.conftest import fake
# Helper function to create an event payload
def create_event_payload(start_offset_days=0, end_offset_days=1):
start_time = datetime.utcnow() + timedelta(days=start_offset_days)
end_time = datetime.utcnow() + timedelta(days=end_offset_days)
# Ensure datetimes are timezone-aware (UTC)
start_time = datetime.now(timezone.utc) + timedelta(days=start_offset_days)
end_time = datetime.now(timezone.utc) + timedelta(days=end_offset_days)
return {
"title": fake.sentence(nb_words=3),
"description": fake.text(),
"start": start_time.isoformat(), # Rename start_time to start
"end": end_time.isoformat(), # Rename end_time to end
"start": start_time.isoformat().replace("+00:00", "Z"), # Ensure Z suffix
"end": end_time.isoformat().replace("+00:00", "Z"), # Ensure Z suffix
"all_day": fake.boolean(),
}
@@ -31,13 +33,20 @@ def test_create_event_unauthorized(client: TestClient) -> None:
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_create_event_success(db: Session, client: TestClient) -> None:
def test_create_event_success(
db: Session, client: TestClient, mocker: MockerFixture
) -> None:
"""Test creating a calendar event successfully."""
user, password = generators.create_user(db)
login_rsp = generators.login(db, user.username, password)
access_token = login_rsp["access_token"]
payload = create_event_payload()
# Mock the celery task sending
mock_send_task = mocker.patch(
"core.celery_app.celery_app.send_task"
) # Corrected patch target
response = client.post(
"/api/calendar/events",
headers={"Authorization": f"Bearer {access_token}"},
@@ -49,7 +58,7 @@ def test_create_event_success(db: Session, client: TestClient) -> None:
data = response.json()
assert data["title"] == payload["title"]
assert data["description"] == payload["description"]
# Remove the '+ "Z"' as the API doesn't add it
# Assert with Z suffix
assert data["start"] == payload["start"]
assert data["end"] == payload["end"]
assert data["all_day"] == payload["all_day"]
@@ -62,6 +71,11 @@ def test_create_event_success(db: Session, client: TestClient) -> None:
assert event_in_db.user_id == user.id
assert event_in_db.title == payload["title"]
# Assert that the task was called correctly
mock_send_task.assert_called_once_with(
"modules.calendar.tasks.schedule_event_notifications", args=[data["id"]]
)
# --- Test Get Events ---
@@ -72,36 +86,49 @@ def test_get_events_unauthorized(client: TestClient) -> None:
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_get_events_success(db: Session, client: TestClient) -> None:
def test_get_events_success(
db: Session, client: TestClient, mocker: MockerFixture
) -> None: # Add mocker
"""Test getting all calendar events for a user."""
user, password = generators.create_user(db)
user, password = generators.create_user(
db, username="testuser_get_events"
) # Unique username
login_rsp = generators.login(db, user.username, password)
access_token = login_rsp["access_token"]
# Mock celery task for creation
mocker.patch("core.celery_app.celery_app.send_task")
# Create a couple of events for the user
payload1 = create_event_payload(0, 1)
client.post(
create_rsp1 = client.post(
"/api/calendar/events",
headers={"Authorization": f"Bearer {access_token}"},
json=payload1,
)
assert create_rsp1.status_code == status.HTTP_201_CREATED
payload2 = create_event_payload(2, 3)
client.post(
create_rsp2 = client.post(
"/api/calendar/events",
headers={"Authorization": f"Bearer {access_token}"},
json=payload2,
)
assert create_rsp2.status_code == status.HTTP_201_CREATED
# Create an event for another user (should not be returned)
other_user, other_password = generators.create_user(db)
other_user, other_password = generators.create_user(
db, username="otheruser_get_events"
) # Unique username
other_login_rsp = generators.login(db, other_user.username, other_password)
other_access_token = other_login_rsp["access_token"]
other_payload = create_event_payload(4, 5)
client.post(
create_rsp_other = client.post(
"/api/calendar/events",
headers={"Authorization": f"Bearer {other_access_token}"},
json=other_payload,
)
assert create_rsp_other.status_code == status.HTTP_201_CREATED
response = client.get(
"/api/calendar/events", headers={"Authorization": f"Bearer {access_token}"}
@@ -115,35 +142,51 @@ def test_get_events_success(db: Session, client: TestClient) -> None:
assert data[1]["user_id"] == user.id
def test_get_events_filtered(db: Session, client: TestClient) -> None:
def test_get_events_filtered(
db: Session, client: TestClient, mocker: MockerFixture
) -> None: # Add mocker
"""Test getting filtered calendar events for a user."""
user, password = generators.create_user(db)
user, password = generators.create_user(
db, username="testuser_filter_events"
) # Unique username
login_rsp = generators.login(db, user.username, password)
access_token = login_rsp["access_token"]
# Mock celery task for creation
mocker.patch("core.celery_app.celery_app.send_task")
# Create events
payload1 = create_event_payload(0, 1) # Today -> Tomorrow
client.post(
create_rsp1 = client.post(
"/api/calendar/events",
headers={"Authorization": f"Bearer {access_token}"},
json=payload1,
)
assert create_rsp1.status_code == status.HTTP_201_CREATED
payload2 = create_event_payload(5, 6) # In 5 days -> In 6 days
client.post(
create_rsp2 = client.post(
"/api/calendar/events",
headers={"Authorization": f"Bearer {access_token}"},
json=payload2,
)
assert create_rsp2.status_code == status.HTTP_201_CREATED
payload3 = create_event_payload(10, 11) # In 10 days -> In 11 days
client.post(
create_rsp3 = client.post(
"/api/calendar/events",
headers={"Authorization": f"Bearer {access_token}"},
json=payload3,
)
assert create_rsp3.status_code == status.HTTP_201_CREATED
# Filter for events starting within the next week
start_filter = datetime.utcnow().isoformat()
end_filter = (datetime.utcnow() + timedelta(days=7)).isoformat()
start_filter = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
end_filter = (
(datetime.now(timezone.utc) + timedelta(days=7))
.isoformat()
.replace("+00:00", "Z")
)
response = client.get(
"/api/calendar/events",
@@ -157,7 +200,11 @@ def test_get_events_filtered(db: Session, client: TestClient) -> None:
assert data[1]["title"] == payload2["title"]
# Filter for events starting after 8 days
start_filter_late = (datetime.utcnow() + timedelta(days=8)).isoformat()
start_filter_late = (
(datetime.now(timezone.utc) + timedelta(days=8))
.isoformat()
.replace("+00:00", "Z")
)
response = client.get(
"/api/calendar/events",
headers={"Authorization": f"Bearer {access_token}"},
@@ -172,34 +219,48 @@ def test_get_events_filtered(db: Session, client: TestClient) -> None:
# --- Test Get Event By ID ---
def test_get_event_by_id_unauthorized(db: Session, client: TestClient) -> None:
def test_get_event_by_id_unauthorized(
db: Session, client: TestClient, mocker: MockerFixture
) -> None: # Add mocker
"""Test getting a specific event without authentication."""
user, password = generators.create_user(db)
login_rsp = generators.login(db, user.username, password)
access_token = login_rsp["access_token"]
payload = create_event_payload()
# Mock celery task for creation
mocker.patch("core.celery_app.celery_app.send_task")
create_response = client.post(
"/api/calendar/events",
headers={"Authorization": f"Bearer {access_token}"},
json=payload,
)
assert create_response.status_code == status.HTTP_201_CREATED
event_id = create_response.json()["id"]
response = client.get(f"/api/calendar/events/{event_id}")
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_get_event_by_id_success(db: Session, client: TestClient) -> None:
def test_get_event_by_id_success(
db: Session, client: TestClient, mocker: MockerFixture
) -> None: # Add mocker
"""Test getting a specific event successfully."""
user, password = generators.create_user(db)
login_rsp = generators.login(db, user.username, password)
access_token = login_rsp["access_token"]
payload = create_event_payload()
# Mock celery task for creation
mocker.patch("core.celery_app.celery_app.send_task")
create_response = client.post(
"/api/calendar/events",
headers={"Authorization": f"Bearer {access_token}"},
json=payload,
)
assert create_response.status_code == status.HTTP_201_CREATED
event_id = create_response.json()["id"]
response = client.get(
@@ -210,6 +271,9 @@ def test_get_event_by_id_success(db: Session, client: TestClient) -> None:
data = response.json()
assert data["id"] == event_id
assert data["title"] == payload["title"]
# Assert datetime with Z suffix
assert data["start"] == payload["start"]
assert data["end"] == payload["end"]
assert data["user_id"] == user.id
@@ -227,20 +291,31 @@ def test_get_event_by_id_not_found(db: Session, client: TestClient) -> None:
assert response.status_code == status.HTTP_404_NOT_FOUND
def test_get_event_by_id_forbidden(db: Session, client: TestClient) -> None:
def test_get_event_by_id_forbidden(
db: Session, client: TestClient, mocker: MockerFixture
) -> None: # Add mocker
"""Test getting another user's event."""
user1, password_user1 = generators.create_user(db)
user2, password_user2 = generators.create_user(db)
user1, password_user1 = generators.create_user(
db, username="user1_forbidden_get"
) # Unique username
user2, password_user2 = generators.create_user(
db, username="user2_forbidden_get"
) # Unique username
# Log in as user1 and create an event
login_rsp1 = generators.login(db, user1.username, password_user1)
access_token1 = login_rsp1["access_token"]
payload = create_event_payload()
# Mock celery task for creation
mocker.patch("core.celery_app.celery_app.send_task")
create_response = client.post(
"/api/calendar/events",
headers={"Authorization": f"Bearer {access_token1}"},
json=payload,
)
assert create_response.status_code == status.HTTP_201_CREATED
event_id = create_response.json()["id"]
# Log in as user2 and try to get user1's event
@@ -259,17 +334,24 @@ def test_get_event_by_id_forbidden(db: Session, client: TestClient) -> None:
# --- Test Update Event ---
def test_update_event_unauthorized(db: Session, client: TestClient) -> None:
def test_update_event_unauthorized(
db: Session, client: TestClient, mocker: MockerFixture
) -> None: # Add mocker
"""Test updating an event without authentication."""
user, password = generators.create_user(db)
login_rsp = generators.login(db, user.username, password)
access_token = login_rsp["access_token"]
payload = create_event_payload()
# Mock celery task for creation
mocker.patch("core.celery_app.celery_app.send_task")
create_response = client.post(
"/api/calendar/events",
headers={"Authorization": f"Bearer {access_token}"},
json=payload,
)
assert create_response.status_code == status.HTTP_201_CREATED
event_id = create_response.json()["id"]
update_payload = {"title": "Updated Title"}
@@ -277,12 +359,20 @@ def test_update_event_unauthorized(db: Session, client: TestClient) -> None:
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_update_event_success(db: Session, client: TestClient) -> None:
def test_update_event_success(
db: Session, client: TestClient, mocker: MockerFixture
) -> None: # Add mocker
"""Test updating an event successfully."""
user, password = generators.create_user(db)
login_rsp = generators.login(db, user.username, password)
access_token = login_rsp["access_token"]
payload = create_event_payload()
# Mock celery task for creation
mocker.patch(
"core.celery_app.celery_app.send_task", return_value=None
) # Mock for creation
create_response = client.post(
"/api/calendar/events",
headers={"Authorization": f"Bearer {access_token}"},
@@ -299,6 +389,13 @@ def test_update_event_success(db: Session, client: TestClient) -> None:
"all_day": not payload["all_day"], # Toggle all_day
}
# Mock celery task for update (needs separate mock)
mock_send_task_update = mocker.patch(
"modules.calendar.service.celery_app.send_task"
)
# Mock cancel notifications as well, as it's called synchronously in the service
mocker.patch("modules.calendar.tasks.cancel_event_notifications")
response = client.patch(
f"/api/calendar/events/{event_id}",
headers={"Authorization": f"Bearer {access_token}"},
@@ -310,7 +407,8 @@ def test_update_event_success(db: Session, client: TestClient) -> None:
assert data["title"] == update_payload["title"]
assert data["description"] == update_payload["description"]
assert data["all_day"] == update_payload["all_day"]
assert data["start"] == payload["start"] # Check correct field name 'start'
# Assert datetime with Z suffix
assert data["start"] == payload["start"]
assert data["user_id"] == user.id
# Verify in DB
@@ -320,6 +418,17 @@ def test_update_event_success(db: Session, client: TestClient) -> None:
assert event_in_db.description == update_payload["description"]
assert event_in_db.all_day == update_payload["all_day"]
# Assert that the update task was called correctly
mock_send_task_update.assert_called_once_with(
"modules.calendar.tasks.schedule_event_notifications", args=[event_id]
)
# Assert cancel was NOT called because update doesn't cancel
# mock_cancel_notifications.assert_not_called() # Update: cancel IS called in update path via re-schedule
# Actually, schedule_event_notifications calls cancel_event_notifications first.
# So we need to mock cancel_event_notifications called *within* schedule_event_notifications
# OR mock schedule_event_notifications itself. Let's stick to mocking send_task.
# The cancel mock added earlier handles the direct call in the service layer if any.
def test_update_event_not_found(db: Session, client: TestClient) -> None:
"""Test updating a non-existent event."""
@@ -337,20 +446,31 @@ def test_update_event_not_found(db: Session, client: TestClient) -> None:
assert response.status_code == status.HTTP_404_NOT_FOUND
def test_update_event_forbidden(db: Session, client: TestClient) -> None:
def test_update_event_forbidden(
db: Session, client: TestClient, mocker: MockerFixture
) -> None: # Add mocker
"""Test updating another user's event."""
user1, password_user1 = generators.create_user(db)
user2, password_user2 = generators.create_user(db)
user1, password_user1 = generators.create_user(
db, username="user1_forbidden_update"
) # Unique username
user2, password_user2 = generators.create_user(
db, username="user2_forbidden_update"
) # Unique username
# Log in as user1 and create an event
login_rsp1 = generators.login(db, user1.username, password_user1)
access_token1 = login_rsp1["access_token"]
payload = create_event_payload()
# Mock celery task for creation
mocker.patch("core.celery_app.celery_app.send_task")
create_response = client.post(
"/api/calendar/events",
headers={"Authorization": f"Bearer {access_token1}"},
json=payload,
)
assert create_response.status_code == status.HTTP_201_CREATED
event_id = create_response.json()["id"]
# Log in as user2 and try to update user1's event
@@ -371,29 +491,42 @@ def test_update_event_forbidden(db: Session, client: TestClient) -> None:
# --- Test Delete Event ---
def test_delete_event_unauthorized(db: Session, client: TestClient) -> None:
def test_delete_event_unauthorized(
db: Session, client: TestClient, mocker: MockerFixture
) -> None: # Add mocker
"""Test deleting an event without authentication."""
user, password = generators.create_user(db)
login_rsp = generators.login(db, user.username, password)
access_token = login_rsp["access_token"]
payload = create_event_payload()
# Mock celery task for creation
mocker.patch("core.celery_app.celery_app.send_task")
create_response = client.post(
"/api/calendar/events",
headers={"Authorization": f"Bearer {access_token}"},
json=payload,
)
assert create_response.status_code == status.HTTP_201_CREATED
event_id = create_response.json()["id"]
response = client.delete(f"/api/calendar/events/{event_id}")
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_delete_event_success(db: Session, client: TestClient) -> None:
def test_delete_event_success(
db: Session, client: TestClient, mocker: MockerFixture
) -> None:
"""Test deleting an event successfully."""
user, password = generators.create_user(db)
login_rsp = generators.login(db, user.username, password)
access_token = login_rsp["access_token"]
payload = create_event_payload()
# Mock the celery task sending for creation
mocker.patch("core.celery_app.celery_app.send_task")
create_response = client.post(
"/api/calendar/events",
headers={"Authorization": f"Bearer {access_token}"},
@@ -408,12 +541,20 @@ def test_delete_event_success(db: Session, client: TestClient) -> None:
event_in_db = db.query(CalendarEvent).filter(CalendarEvent.id == event_id).first()
assert event_in_db is not None
# Mock the cancel_event_notifications function to prevent Redis call
mock_cancel_notifications = mocker.patch(
"modules.calendar.service.cancel_event_notifications" # Target the function as used in service.py
)
response = client.delete(
f"/api/calendar/events/{event_id}",
headers={"Authorization": f"Bearer {access_token}"},
)
assert response.status_code == status.HTTP_204_NO_CONTENT
# Assert that cancel_event_notifications was called
mock_cancel_notifications.assert_called_once_with(event_id)
# Verify event is deleted from DB
event_in_db = db.query(CalendarEvent).filter(CalendarEvent.id == event_id).first()
assert event_in_db is None
@@ -441,20 +582,31 @@ def test_delete_event_not_found(db: Session, client: TestClient) -> None:
assert response.status_code == status.HTTP_404_NOT_FOUND
def test_delete_event_forbidden(db: Session, client: TestClient) -> None:
def test_delete_event_forbidden(
db: Session, client: TestClient, mocker: MockerFixture
) -> None: # Add mocker
"""Test deleting another user's event."""
user1, password_user1 = generators.create_user(db)
user2, password_user2 = generators.create_user(db)
user1, password_user1 = generators.create_user(
db, username="user1_forbidden_delete"
) # Unique username
user2, password_user2 = generators.create_user(
db, username="user2_forbidden_delete"
) # Unique username
# Log in as user1 and create an event
login_rsp1 = generators.login(db, user1.username, password_user1)
access_token1 = login_rsp1["access_token"]
payload = create_event_payload()
# Mock celery task for creation
mocker.patch("core.celery_app.celery_app.send_task")
create_response = client.post(
"/api/calendar/events",
headers={"Authorization": f"Bearer {access_token1}"},
json=payload,
)
assert create_response.status_code == status.HTTP_201_CREATED
event_id = create_response.json()["id"]
# Log in as user2 and try to delete user1's event