Skip to content

Commit

Permalink
5oappy/feature/delete v2 testing (#211)
Browse files Browse the repository at this point in the history
## Describe your changes
implemented test criteria for functional testing of delete endpoint,
wrote unit test for event repository, modified create/post endpoint in
api to check for duplicates instead of overlaps so that events can now
overlap but cannot create events that are exactly the same as you can
imagine duplicate events will get increasingly annoying for end user.
Right now it doesn't care about 'event title' but can add if needed.

## Issue ticket number and link

https://fireapp-emergiq-2024.atlassian.net/browse/FE-189?atlOrigin=eyJpIjoiZWQwMjlmY2RmYThkNGM2NzllOGU3NDQyMzRhYmI4MzMiLCJwIjoiaiJ9
  • Loading branch information
5oappy authored Apr 24, 2024
1 parent c570940 commit 9d5ea6a
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 29 deletions.
13 changes: 6 additions & 7 deletions controllers/v2/unavailability/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,15 @@ def get(self, user_id):
def post(self, user_id):
try:
args = edit_parser.parse_args()
# Check if start time is earlier than end time.
# Check if start time is earlier than end time. redundancy may remove
if args['start'] >= args['end']:
return {"message": "Start time must be earlier than end time"}, 400 # HTTP 400 Bad Request

overlapping_events = self.event_repository.check_overlapping_events(user_id, args['start'], args['end'],
args['periodicity'])
if overlapping_events:
return {"message": "Time frames overlap with existing events",
"overlapping events": overlapping_events}, 400

duplicate_event = self.event_repository.check_duplicate_event(user_id, args['start'], args['end'],
args['periodicity'])
# Prevent duplicate events from being created.
if duplicate_event:
return {"message": "Event to be added already exist"}, 400

eventId = self.event_repository.create_event(
user_id,
Expand Down
14 changes: 14 additions & 0 deletions repository/volunteer_unavailability_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,17 @@ def check_overlapping_events(self, userId, startTime, endTime, periodicity):
})

return overlapping_details

def check_duplicate_event(self, userId, startTime, endTime, periodicity):
with session_scope() as session:
# Query the database for events with the same start time, end time, and periodicity
duplicate_events_count = session.query(UnavailabilityTime).filter(
UnavailabilityTime.userId == userId,
UnavailabilityTime.start == startTime,
UnavailabilityTime.end == endTime,
UnavailabilityTime.periodicity == periodicity
).count()

# If the count of duplicate events is greater than 0, duplicates exist, return True
return duplicate_events_count > 0

44 changes: 22 additions & 22 deletions tests/functional/test_unavailability.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,28 +71,28 @@ def test_create_unavailability_end_before_start(test_client, create_user):
assert response.status_code == 400


def test_create_unavailability_overlapped_time(test_client, create_user):
user_id = create_user
payload_1 = {
"title": "All Day Event",
"periodicity": 0,
"start": "2024-03-03T00:00:00Z",
"end": "2024-03-04T23:59:59Z"
}
payload_2 = {
"title": "All Day Event",
"periodicity": 0,
"start": "2024-03-01T00:00:00Z",
"end": "2024-03-05T23:59:59Z"
}
response_1 = test_client.post(f"/v2/volunteers/{user_id}/unavailability",
json=payload_1
)
response_2 = test_client.post(f"/v2/volunteers/{user_id}/unavailability",
json=payload_2
)
assert response_1.status_code == 200
assert response_2.status_code == 400
# def test_create_unavailability_overlapped_time(test_client, create_user):
# user_id = create_user
# payload_1 = {
# "title": "All Day Event",
# "periodicity": 0,
# "start": "2024-03-03T00:00:00Z",
# "end": "2024-03-04T23:59:59Z"
# }
# payload_2 = {
# "title": "All Day Event",
# "periodicity": 0,
# "start": "2024-03-01T00:00:00Z",
# "end": "2024-03-05T23:59:59Z"
# }
# response_1 = test_client.post(f"/v2/volunteers/{user_id}/unavailability",
# json=payload_1
# )
# response_2 = test_client.post(f"/v2/volunteers/{user_id}/unavailability",
# json=payload_2
# )
# assert response_1.status_code == 200
# assert response_2.status_code == 400


# def test_merge_overlapping_unavailability_intervals(test_client, create_user):
Expand Down
138 changes: 138 additions & 0 deletions tests/functional/test_unavailability_delete.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
from datetime import datetime, timezone
from unittest.mock import patch
from services.jwk import JWKService
from repository import volunteer_unavailability_v2

"""
Test Case 6: User with ROOT_ADMIN Role
Input: user_id of a user with ROOT_ADMIN role, event_id of an existing event.
Expected Output: Successful deletion of the event with HTTP status code 200.
Test Case 7: User with other Roles
Input: user_id of a user with a role other than ROOT_ADMIN, event_id of an existing event.
Expected Output: HTTP status code 403 Forbidden with a message indicating that the user does not have permission to delete the event.
Test Case 9: Attempt to Delete Event without Required Permissions
Input: user_id, event_id of an event that the user has permission to view but not delete.
Expected Output: HTTP status code 403 Forbidden with a message indicating the user does not have permission to delete the event.
"""

payload = {
"title": "Test Event",
"periodicity": 0,
"start": "2025-05-02T00:00:00Z",
"end": "2025-05-02T23:59:59Z"
}

"""Test Case 1: Successful Deletion
Input: user_id, event_id of an existing event that the user has permission to delete.
Expected Output: HTTP status code 200 with a success message."""


def test_delete_volunteer_unavailability_success(test_client, create_user):
user_id = create_user
event_response = test_client.post(f"/v2/volunteers/{user_id}/unavailability",
json=payload
)
# checks body was posted correctly
assert event_response.status_code == 200

# extract event_id
event_id = event_response.json["eventId"]

# make delete request
response = test_client.delete(f"/v2/volunteers/{user_id}/unavailability/{event_id}")

assert response.status_code == 200


"""Test Case 2: Event Not Found
Input: user_id, event_id of a non-existing event.
Expected Output: HTTP status code 404 with a message indicating the event was not found."""


def test_delete_event_not_found(test_client, create_user):
user_id = create_user
event_response = test_client.post(f"/v2/volunteers/{user_id}/unavailability",
json=payload
)

assert event_response.status_code == 200
# extract event_id
event_id = event_response.json["eventId"]

response = test_client.delete(f"/v2/volunteers/{user_id}/unavailability/{event_id + 1}")

assert response.status_code == 404


"""Test Case 3: Attempt to Delete Already Deleted Event
Input: user_id, event_id of an event that has already been deleted.
Expected Output: HTTP status code 404 with a message indicating the event was not found."""


def test_delete_event_already_deleted(test_client, create_user):
user_id = create_user
event_response = test_client.post(f"/v2/volunteers/{user_id}/unavailability", json=payload)
assert event_response.status_code == 200
event_id = event_response.json["eventId"]
response = test_client.delete(f"/v2/volunteers/{user_id}/unavailability/{event_id}")
assert response.status_code == 200
response_repeat = test_client.delete(f"/v2/volunteers/{user_id}/unavailability/{event_id}")
assert response_repeat.status_code == 404


# """Test Case 4: Unauthorized User Input: user_id of a non-authorized user, event_id of an existing event.
# Expected Output: HTTP status code 403 Forbidden or 404 Not Found with a message indicating the user is not authorized to
# delete the event."""


# def test_delete_unauthorized(test_client, create_user1, create_user2):
# # Create the first user and post an unavailability event
# user_id1 = create_user1
# event_response = test_client.post(f"/v2/volunteers/{user_id1}/unavailability",
# json=payload)
# assert event_response.status_code == 200
#
# # Extract the event ID
# event_id = event_response.json()["eventId"]
#
# # Create the second user
# user_id2 = create_user2
#
# # Generate a valid JWT token for user_id1
# token = JWKService.generate(user_id1, "user1", "admin",
# datetime.now(), datetime.now())
#
# # Attempt to delete the event created by user_id1 using the credentials of user_id2
# response = test_client.delete(f"/v2/volunteers/{user_id2}/unavailability/{event_id}",
# headers={"Authorization": f"Bearer {token}"})
#
# # Ensure that the unauthorized user receives a 401 Forbidden status code
# assert response.status_code == 401


"""Test Case 4: Internal Server Error
Input: When an unexpected exception occurs during event deletion.
Expected Output: HTTP status code 500 Internal Server Error with a message indicating the server error."""


def test_internal_server_error(test_client, create_user):
user_id = create_user
event_response = test_client.post(f"/v2/volunteers/{user_id}/unavailability", json=payload)
assert event_response.status_code == 200
# extract event_id
event_id = event_response.json["eventId"]

# Simulate an unexpected exception during event deletion
# Patch the remove_event method to raise an exception
with patch("repository.volunteer_unavailability_v2.EventRepository.remove_event") as mock_remove_event:
mock_remove_event.side_effect = Exception("Something went wrong")

# Send a delete request
response = test_client.delete(f"/v2/volunteers/{user_id}/unavailability/{event_id}")

# Ensure that the response status code is 500 Internal Server Error
assert response.status_code == 500
71 changes: 71 additions & 0 deletions tests/unit/test_repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import unittest
from unittest.mock import MagicMock, patch
from datetime import datetime, timedelta

from domain import UnavailabilityTime
from repository import volunteer_unavailability_v2


class TestEventRepository(unittest.TestCase):
def setUp(self):
self.repository = volunteer_unavailability_v2.EventRepository()

def test_create_event(self):
with MagicMock() as session:
session.add.return_value = None
session.flush.return_value = None
result = session.repository.create_event(1, "Title", datetime.now(), datetime.now(), 1)
self.assertIsNotNone(result)

# def test_edit_event(self):
# mock_event = MagicMock()
# with patch('repository.volunteer_unavailability_v2.session_scope') as mock_session_scope:
# with MagicMock() as session:
# session.add.return_value = None
#
# event_id = session.repository.create_event(1, "Title", datetime.now(), datetime.now(), 1)
# self.assertIsNotNone(event_id)
# result = session.repository.edit_event(1, event_id, "New Title", datetime.now(), datetime.now(), 1)
# # Assert that the result is True since an event exists
# session.flush()
# self.assertTrue(result.title == "New Title")

def test_get_event_with_events(self):
event_id = self.repository.create_event(1, "Title", datetime.now(), datetime.now(), 1)
result = self.repository.get_event(event_id)
self.assertIsNotNone(result)

def test_get_past_events(self):
past_datetime = (datetime.now() - timedelta(days=1))
event_id = self.repository.create_event(1, "Title", past_datetime, datetime.now(), 1)
result = self.repository.get_event(event_id)
self.assertTrue([] == result)

def test_remove_event(self):
with MagicMock() as session:

# Create a mock event
event_id = session.repository.create_event(1, "Title", datetime.now(), datetime.now(), 1)
self.assertIsNotNone(event_id)

# Remove the event
result = session.repository.remove_event(1, event_id)

# Check if removal was successful
self.assertTrue(result)

##
def test_check_duplicate_events(self):
# Mock the session to simulate database interaction
with MagicMock() as session:

# Call the method under test
session.repository.create_event(1, "Title", datetime.now(), datetime.now(), 1)
result = session.repository.check_duplicate_event(1, datetime.now(), datetime.now(), 1)

# Check if the method correctly detects no duplicate events
self.assertTrue(result)


if __name__ == "__main__":
unittest.main()

0 comments on commit 9d5ea6a

Please sign in to comment.