From 9d5ea6ac764d7ca965fec4c95cbe3a058f5988ab Mon Sep 17 00:00:00 2001 From: 5oappy <111340376+5oappy@users.noreply.github.com> Date: Wed, 24 Apr 2024 15:27:56 +1000 Subject: [PATCH] 5oappy/feature/delete v2 testing (#211) ## 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 --- controllers/v2/unavailability/api.py | 13 +- repository/volunteer_unavailability_v2.py | 14 ++ tests/functional/test_unavailability.py | 44 +++--- .../functional/test_unavailability_delete.py | 138 ++++++++++++++++++ tests/unit/test_repository.py | 71 +++++++++ 5 files changed, 251 insertions(+), 29 deletions(-) create mode 100644 tests/functional/test_unavailability_delete.py create mode 100644 tests/unit/test_repository.py diff --git a/controllers/v2/unavailability/api.py b/controllers/v2/unavailability/api.py index 093186f7..0145cfa5 100644 --- a/controllers/v2/unavailability/api.py +++ b/controllers/v2/unavailability/api.py @@ -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, diff --git a/repository/volunteer_unavailability_v2.py b/repository/volunteer_unavailability_v2.py index 1f011ed6..b9c80d85 100644 --- a/repository/volunteer_unavailability_v2.py +++ b/repository/volunteer_unavailability_v2.py @@ -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 + diff --git a/tests/functional/test_unavailability.py b/tests/functional/test_unavailability.py index f0e12833..e1cb6845 100644 --- a/tests/functional/test_unavailability.py +++ b/tests/functional/test_unavailability.py @@ -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): diff --git a/tests/functional/test_unavailability_delete.py b/tests/functional/test_unavailability_delete.py new file mode 100644 index 00000000..cb5177e5 --- /dev/null +++ b/tests/functional/test_unavailability_delete.py @@ -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 diff --git a/tests/unit/test_repository.py b/tests/unit/test_repository.py new file mode 100644 index 00000000..a42f3303 --- /dev/null +++ b/tests/unit/test_repository.py @@ -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()