diff --git a/application.py b/application.py index 0943da89..a38f0ce1 100644 --- a/application.py +++ b/application.py @@ -1,6 +1,5 @@ from flask import Flask from flask_cors import CORS - from controllers import * # Register the application diff --git a/services/optimiser/input_processing.py b/services/optimiser/input_processing.py index 5f515c71..2b2f32f6 100644 --- a/services/optimiser/input_processing.py +++ b/services/optimiser/input_processing.py @@ -349,7 +349,7 @@ def get_position_qualification(session, position_id): return qualification_id -# This can be called by api from postman, to test easily +# This can be called by api from postman, to tests easily def test_vehicle_list(session, request_id): v_l = get_vehicle_list(session, request_id) for v in v_l: diff --git a/services/optimiser/testing/test_shift.py b/services/optimiser/testing/test_shift.py deleted file mode 100644 index e1e88af5..00000000 --- a/services/optimiser/testing/test_shift.py +++ /dev/null @@ -1,64 +0,0 @@ -import pytest as pytest - -from domain.base import Session, Engine -from services.optimiser.input_processing import * - -# Todo: Mock session - -@pytest.fixture -def session(): - new_session = Session(bind=Engine) - yield new_session - - new_session.close() - - -@pytest.fixture -def request_id(): - return '356' - - -# Todo: using the mock database to test. - -# Input: The database and the vehicle id. -# Expected output: A matrix of (A, P, Q). Indicates the qualifications for the vehicle on all shifts. -# A-The number of shifts for this vehicle attend. P-The number of position. Q-The number of qualifications. -def test_get_input_qualrequirements(session, request_id): - print(get_input_qualrequirements(session, request_id)) - - -# Input: The database and the vehicle id. -# Expected output: A matrix of (A, P, R). Indicates the roles for the vehicle on all shifts. -# A-The number of shifts for this vehicle attend. P-The number of position. R-The number of roles. -def test_get_input_rolerequirements(session, request_id): - print(get_input_rolerequirements(session, request_id)) - - -# Input: The database and the vehicle id. -# Expected output: A matrix of (P, A). Indicates the number of positions of the vehicle on all shifts. -# P-The number of position. A-The number of shifts for this vehicle attend. -def test_get_input_posrequirements(session, request_id): - print(get_input_posrequirements(session, request_id)) - - -# Input: The database and the vehicle id. -# Expected output: A matrix of (V, Q). Indicates the qualifications for all volunteers. -# V-The number of volunteers. Q-The number of qualifications. -def test_get_input_qualability(session): - print(get_input_qualability(session)) - - -# Input: The database and the vehicle id. -# Expected output: A matrix of (V, R). Indicates the roles for all volunteers. -# V-The number of volunteers. R-The number of roles. -def test_get_input_roleability(session): - print(get_input_roleability(session)) - - -# Input: The database and the vehicle id. -# Expected output: A matrix of (V, A). Indicates the volunteer's availability during the scheduled shift time. -# V-The number of volunteers. A-The number of shifts for this vehicle attend. -def test_get_input_availability(session, request_id): - print(get_input_availability(session, request_id)) - - diff --git a/services/optimiser/testing_for_scheduler.py b/services/optimiser/testing_for_scheduler.py deleted file mode 100644 index 2827bc53..00000000 --- a/services/optimiser/testing_for_scheduler.py +++ /dev/null @@ -1,61 +0,0 @@ -import pytest -from domain.base import Session, Engine -from services.optimiser.input_processing import ( - get_input_clashes, - get_vehicle_time, - get_vehicle_list, - get_position_list, - get_position_list_all, -) - -@pytest.fixture -def session(): - new_session = Session(bind=Engine) - yield new_session - - new_session.close() - -@pytest.fixture -def request_id(): - return '322' - -@pytest.fixture -def vehicle_id(): - return '369' - -# Input: The database and the request id. -# Output: A 2-dimensional matrix of size [A, A] that contains boolean values. -# A- represent whether each asset shift clashes with other asset shifts -def test_get_input_clashes(session,request_id): - print(get_input_clashes(session,request_id)) - -# Input: The database and the vehicle id. -# Output: A 6-dimensional matrix of size [year,Month,Day,Hour,Minute,Second] means the time of vehicle . -def test_get_vehicle_time(session,vehicle_id): - print(get_vehicle_time(session,vehicle_id)) - -# Input: The database and the request id. -# Output: A 2-dimensional matrix of size [vehicle_id, vehicle_id] that contains vehicle_id that request_id order. -# output: The dimensionality of the output matrix depends on how many vehicles are required for this request id -# Tips: This data is stored in table[asset_request_vehicle] and the "id" filed represent the vehicle_id -def test_get_vehicle_list(session,request_id): - print(get_vehicle_list(session,request_id)) - -# Input: The database and the vehicle id. -# Output: A 3-dimensional matrix of size [position id,position id,position id] -# output: The dimensionality of the output matrix depends on how many position are related to this vehicle id -def test_get_position_list(session,vehicle_id): - print(get_position_list(session,vehicle_id)) - -# Input: The database and the vehicle id. -# Output: A 3-dimensional matrix of size [position id,position id,position id]. -# This matrix will be sorted based on the size of the numbers -def test_get_position_list_all(session,request_id): - print(get_position_list_all(session,request_id)) - - - - - - - diff --git a/services/optimiser/testing/__init__.py b/tests/__init__.py similarity index 100% rename from services/optimiser/testing/__init__.py rename to tests/__init__.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..13828e27 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,39 @@ +import os + +os.environ.setdefault('username', 'user') +os.environ.setdefault('password', 'password') +os.environ.setdefault('host', '127.0.0.1') +os.environ.setdefault('port', '3306') +os.environ.setdefault('dbname', 'db') +import pytest +from application import app + + +@pytest.fixture(scope='module') +def test_client(): + # Set the Testing configuration prior to creating the Flask application + + with app.test_client() as testing_client: + # Establish an application context + with app.app_context(): + yield testing_client + + +@pytest.fixture(scope='module') +def auth_token(test_client): + # Login with predefined admin credentials + login_payload = { # admin account details + 'email': 'admin', + 'password': 'admin' + } + response = test_client.post('/authentication/login', json=login_payload) + assert response.status_code == 200, "Failed to log in with the given credential" + print(response.json) + token = response.json['access_token'] + return token + + +# append jwt token to header of all the testing requests +@pytest.fixture(autouse=True) +def set_auth_header(test_client, auth_token): + test_client.environ_base['HTTP_AUTHORIZATION'] = f'Bearer {auth_token}' diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/functional/test_unavailability.py b/tests/functional/test_unavailability.py new file mode 100644 index 00000000..060ac788 --- /dev/null +++ b/tests/functional/test_unavailability.py @@ -0,0 +1,137 @@ +def test_create_unavailability_consecutive_event_id(test_client): + user_id = 49 # admin id + payload_1 = { + "title": "All Day Event", + "periodicity": 0, + "start": "2024-03-02T00:00:00Z", + "end": "2024-03-02T23:59:59Z" + } + payload_2 = { + "title": "All Day Event", + "periodicity": 0, + "start": "2024-03-03T00:00:00Z", + "end": "2024-03-03T23:59:59Z" + } + response = 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.status_code == 200 + assert response_2.status_code == 200 + assert response_2.json["eventId"] - response.json["eventId"] == 1 + + +def test_create_unavailability_same_time_interval(test_client): + user_id = 49 + payload = { + "title": "All Day Event", + "periodicity": 0, + "start": "2024-03-02T00:00:00Z", + "end": "2024-03-02T23:59:59Z" + } + response = test_client.post(f"/v2/volunteers/{user_id}/unavailability", + json=payload + ) + response_2 = test_client.post(f"/v2/volunteers/{user_id}/unavailability", + json=payload + ) + assert response.status_code == 200 + assert response_2.status_code == 400 + + +def test_create_unavailability_nonexistent_user_id(test_client): + user_id = -1 + payload = { + "title": "All Day Event", + "periodicity": 0, + "start": "2024-03-02T00:00:00Z", + "end": "2024-03-02T23:59:59Z" + } + response = test_client.post(f"/v2/volunteers/{user_id}/unavailability", + json=payload + ) + response.status_code = 404 + + +def test_create_unavailability_end_before_start(test_client): + user_id = 49 + payload = { + "title": "All Day Event", + "periodicity": 0, + "start": "2024-03-02T00:00:00Z", + "end": "2024-03-01T23:59:59Z" + } + response = test_client.post(f"/v2/volunteers/{user_id}/unavailability", + json=payload + ) + assert response.status_code == 400 + + +def test_create_unavailability_overlapped_time(test_client): + user_id = 49 + 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): + user_id = 49 + payload_1 = { + "title": "Morning Event", + "periodicity": 0, + "start": "2024-03-05T08:00:00Z", + "end": "2024-03-05T12:00:00Z" + } + payload_2 = { + "title": "Afternoon Event", + "periodicity": 0, + "start": "2024-03-05T11:00:00Z", + "end": "2024-03-05T15:00:00Z" + } + test_client.post(f"/v2/volunteers/{user_id}/unavailability", json=payload_1) + response = test_client.post(f"/v2/volunteers/{user_id}/unavailability", json=payload_2) + assert response.status_code == 200 + assert len(response.json["mergedIntervals"]) == 1 # json response must have mergedIntervals field if it is merged + assert response.json["mergedIntervals"][0]["start"] == "2024-03-05T08:00:00Z" + assert response.json["mergedIntervals"][0]["end"] == "2024-03-05T15:00:00Z" + + +def test_merge_adjacent_unavailability_intervals(test_client): + user_id = 49 + payload_1 = { + "title": "Morning Shift", + "periodicity": 0, + "start": "2024-03-06T08:00:00Z", + "end": "2024-03-06T12:00:00Z" + } + payload_2 = { + "title": "Afternoon Shift", + "periodicity": 0, + "start": "2024-03-06T12:00:00Z", + "end": "2024-03-06T16:00:00Z" + } + test_client.post(f"/v2/volunteers/{user_id}/unavailability", json=payload_1) + response = test_client.post(f"/v2/volunteers/{user_id}/unavailability", json=payload_2) + assert response.status_code == 200 + assert len(response.json["mergedIntervals"]) == 1 # json response must have mergedIntervals field if it is merged + assert response.json["mergedIntervals"][0]["start"] == "2024-03-06T08:00:00Z" + assert response.json["mergedIntervals"][0]["end"] == "2024-03-06T16:00:00Z" diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 00000000..e69de29b