"""Tests for Handlers.""" import json from unittest.mock import ANY, MagicMock, patch import pytest from oto import response from oto import status as http_status from timed_release import handlers from timed_release.api import app from timed_release.constants import authorization, error, success from timed_release.logic import ( product_live_time, scheduled_update, timed_release, timed_release_ws, ) @patch('timed_release.handlers.g') def test_exception_handler(mock_g, app_context): """Verify exception_Handler returns 500 status code and json payload.""" message = ( 'The server encountered an internal error ' 'and was unable to complete your request.') mock_error = MagicMock() server_response = handlers.exception_handler(mock_error) mock_g.log.exception.assert_called_with(mock_error) # assert status code is 500 assert server_response.status_code == 500 # assert json payload response_message = json.loads(server_response.data.decode()) assert response_message['message'] == message assert response_message['code'] == response.error.ERROR_CODE_INTERNAL_ERROR @pytest.mark.parametrize( 'description, product_id, status', [ ('Product id must be greater than zero', '0', 400), ('Product id must be greater than zero', '-1', 400), ('Product id must be integer', 'abc', 400)]) def test_get_product_live_time_detail_for_failure( description, product_id, status): """Test for get product live time with invalid product id.""" request_url = '/product/{product_id}'.format(product_id=product_id) result = app.test_client().get(request_url) assert result.status_code == status def test_get_product_live_time_detail_success(monkeypatch): """Test get product live time with valid product id.""" expected_response = { 'product_id': 122, 'time_of_day_product': '02:01:00', 'time_zone': 'GMT', 'store_ids': [1]} monkeypatch.setattr( product_live_time, 'get_product_live_time_details', MagicMock(return_value=response.Response(message=expected_response))) result = app.test_client().get('/product/122') message = json.loads(result.data.decode()) assert result.status_code == http_status.OK assert message == expected_response def test_get_product_live_time_detail_for_not_found( monkeypatch): """Test for get product live time with product_id not found.""" product_id = 1 expected_response = error.ERROR_MESSAGE_PRODUCT_NOT_FOUND.format( product_id) monkeypatch.setattr( product_live_time, 'get_product_live_time_details', MagicMock(return_value=response.create_not_found_response( message=expected_response))) request_url = '/product/{product_id}'.format(product_id=product_id) result = app.test_client().get(request_url) assert result.status_code == http_status.NOT_FOUND def test_get_product_live_time_detail_for_db_failure(mocker): """Test db failure response.""" mocker.patch.object( product_live_time, 'get_product_live_time_details', return_value=response.Response(status=500)) result = app.test_client().get('/product/1') assert result.status_code == http_status.INTERNAL_ERROR def test_create_product_live_time_detail_success(monkeypatch): """Test for Create product live time detail success.""" request_body = { 'product_id': 2080166, 'time_of_day_product': '20:15:00', 'time_zone': 'local', 'store_ids': [286] } expected_response = {'product': request_body} monkeypatch.setattr( product_live_time, 'create_product_live_time_detail', MagicMock(return_value=response.Response(message=expected_response))) with app.test_client() as client: result = client.post( '/product', data=json.dumps(request_body), content_type='application/json') message = json.loads(result.data.decode()) assert result.status_code == http_status.OK assert message == expected_response @pytest.mark.parametrize( 'description, product_id, time_of_day_product, time_zone, store_ids', [ ('testing with negative value store_id', 2080168, '20:30:00', 'GMT', [-286]), ('testing with empty store_ids', 2080168, '20:30:00', 'GMT', []), ('testing with non-int store_ids', 2080168, '20:30:00', 'GMT', ['286', 289])]) def test_create_product_live_time_detail_failure( description, product_id, time_of_day_product, time_zone, store_ids, monkeypatch): """Test for create product live time detail with failure.""" request_body = { 'product_id': product_id, 'time_of_day_product': time_of_day_product, 'time_zone': time_zone, 'store_ids': store_ids } with app.test_client() as client: result = client.post( '/product', data=json.dumps(request_body), content_type='application/json') assert result.status_code == http_status.BAD_REQUEST def test_create_product_live_time_detail_db_failure(mocker): """Test db failure response.""" request_body = { 'product_id': 2080166, 'time_of_day_product': '20:15:00', 'time_zone': 'local', 'store_ids': [286] } mocker.patch.object( product_live_time, 'create_product_live_time_detail', return_value=response.Response(status=500)) with app.test_client() as client: result = client.post( '/product', data=json.dumps(request_body), content_type='application/json') assert result.status_code == http_status.INTERNAL_ERROR @pytest.mark.parametrize('description, request_body', [ ('test with empty request body', ''), ('test with invalid data passed to request body', 'abc')]) def test_create_product_live_time_detail_invalid_request_body( description, request_body, valid_headers): """Test for create product live time detail invalid request body.""" with app.test_client() as client: result = client.post( '/product', headers=valid_headers, data=request_body, content_type='application/json') assert result.status_code == http_status.BAD_REQUEST def test_delete_product_live_details_success(monkeypatch): """Test delete product live time with success.""" monkeypatch.setattr( product_live_time, 'delete_product_live_time_details', MagicMock(return_value=response.Response( message=success.DELETE_SUCCESS_MESSAGE_TIMED_RELEASE))) result = app.test_client().delete('/product/122') message = result.data.decode() assert result.status_code == http_status.OK assert message == success.DELETE_SUCCESS_MESSAGE_TIMED_RELEASE def test_delete_product_live_details_for_not_found(monkeypatch): """Test for delete product live time with product_id not found.""" product_id = 1 expected_response = error.ERROR_MESSAGE_PRODUCT_NOT_FOUND.format( product_id) monkeypatch.setattr( product_live_time, 'delete_product_live_time_details', MagicMock(return_value=response.create_not_found_response( message=expected_response))) request_url = '/product/{product_id}'.format(product_id=product_id) result = app.test_client().delete(request_url) assert result.status_code == http_status.NOT_FOUND def test_update_product_live_detail_by_id_success(monkeypatch): """Test update product live time with success.""" request_body = { 'product_id': 10, 'time_of_day_product': '20:15:00', 'time_zone': 'local', 'store_ids': [286] } monkeypatch.setattr( product_live_time, 'update_product_live_time', MagicMock(return_value=response.Response( message=success.UPDATE_SUCCESS_MESSAGE))) result = app.test_client().put( '/product/10', data=json.dumps(request_body), content_type='application/json') message = result.data.decode() assert result.status_code == http_status.OK assert message == success.UPDATE_SUCCESS_MESSAGE @pytest.mark.parametrize('description, request_body', [ ('Empty Store ids', { 'product_id': 10, 'time_of_day_product': '23:00:00', 'time_zone': 'local', 'store_ids': []}), ('Store_ids has negative value', { 'product_id': 10, 'time_of_day_product': '23:00:00', 'time_zone': 'local', 'store_ids': [-286]}), ('Store ids has non-integer values', { 'product_id': 10, 'time_of_day_product': '23:00:00', 'time_zone': 'local', 'store_ids': ['286', 289]}), ('Store ids is not an array', { 'product_id': 10, 'time_of_day_product': '23:00:00', 'time_zone': 'local', 'store_ids': 286}), ('Store ids is not sent', { 'product_id': 10, 'time_of_day_product': '23:00:00', 'time_zone': 'local'})]) def test_update_product_live_detail_failure( description, request_body): """Test for update product live time failure.""" with app.test_client() as client: result = client.put( '/product/10', data=json.dumps(request_body), content_type='application/json') assert result.status_code == http_status.BAD_REQUEST def test_update_product_live_detail_by_id_not_found(monkeypatch): """Test update product live time with product_id not found.""" request_body = { 'product_id': 12, 'time_of_day_product': '20:15:00', 'time_zone': 'local', 'store_ids': [286] } monkeypatch.setattr( product_live_time, 'update_product_live_time', MagicMock(return_value=response.create_not_found_response( message=error.ERROR_MESSAGE_PRODUCT_NOT_FOUND.format(12)))) with app.test_client() as client: result = client.put( '/product/12', data=json.dumps(request_body), content_type='application/json') assert result.status_code == http_status.NOT_FOUND @pytest.mark.parametrize('description, request_body', [ ('test with empty request body', ''), ('test with invalid data passed to request body', 'abc')]) def test_update_product_live_detail_by_id_invalid_request_body( description, request_body, valid_headers): """Test for update product live time detail invalid request body.""" result = app.test_client().put( '/product/10', headers=valid_headers, data=json.dumps(request_body), content_type='application/json') assert result.status_code == http_status.BAD_REQUEST def test_get_timed_release_success(monkeypatch): """Test get timed release data with valid product id.""" expected_response = { 'timed': [{ 'sale_date_time': '2023-01-27T18:03:22Z', 'delivery_store': { 'id': 286 } }], 'staggered': [{ 'sale_date': '2023-01-25', 'delivery_store': { 'id': 1 } }] } headers = { 'orchard-identity-id': 1234, 'orchard-profile-id': 123, 'orchard-profile-type': 'OrchAdminProfile' } monkeypatch.setattr( timed_release, 'get_timed_release_data', MagicMock(return_value=response.Response(message=expected_response))) result = app.test_client().get('/timedrelease/12', headers=headers) message = json.loads(result.data.decode()) assert result.status_code == http_status.OK assert message == expected_response def test_get_timed_release_failure(monkeypatch): """Test get timed release data with valid product id.""" result = app.test_client().get('/timedrelease/12') assert result.status_code == http_status.BAD_REQUEST def test_update_timed_release_data_success(monkeypatch): """Test update timed release data with success.""" request_body = { 'timed': [{ 'sale_date_time': '2023-01-27T18:03:22', 'delivery_store': { 'id': 286 } }], 'staggered': [{ 'sale_date': '2023-01-25', 'delivery_store': { 'id': 1 } }] } headers = { 'orchard-identity-id': 1234, 'orchard-profile-id': 123, 'orchard-profile-type': 'OrchAdminProfile' } response_body = request_body monkeypatch.setattr( timed_release, 'update_timed_release_data', MagicMock(return_value=response.Response( message=response_body))) result = app.test_client().put( '/timedrelease/12', data=json.dumps(request_body), content_type='application/json', headers=headers) message = json.loads(result.data.decode()) assert result.status_code == http_status.OK assert message == response_body def test_get_scheduled_update_success(monkeypatch): """Test get scheduled update data with valid product id.""" expected_response = { 'product_id': 2958087, 'schedule_datetime': '2023-02-11T00:00:00Z', 'delivery_store_ids': [268], 'update': {'sale_start_date': '2022-01-11'}} headers = { 'orchard-identity-id': 1234, 'orchard-profile-id': 123, 'orchard-profile-type': 'OrchAdminProfile' } monkeypatch.setattr( scheduled_update, 'get_scheduled_update', MagicMock(return_value=response.Response(message=expected_response))) result = app.test_client().get('/scheduled_update/12', headers=headers) message = json.loads(result.data.decode()) assert result.status_code == http_status.OK assert message == expected_response def test_get_scheduled_update_failure(monkeypatch): """Test get scheduled update with valid product id.""" result = app.test_client().get('/scheduled_update/12') assert result.status_code == http_status.BAD_REQUEST def test_execute_scheduled_update_success(monkeypatch): """Test execute scheduled update endpoint.""" headers = { 'orchard-identity-id': 1234, } monkeypatch.setattr( scheduled_update, 'execute_scheduled_update', MagicMock(return_value=response.Response(message='Success')) ) result = app.test_client().post( '/scheduled_update/123/execute', headers=headers) assert result.status_code == http_status.OK def test_execute_scheduled_update_failure(): """Test execute scheduled update without headers.""" result = app.test_client().post('/scheduled_update/123/execute') assert result.status_code == http_status.BAD_REQUEST @pytest.mark.parametrize( 'headers, mock_jwt_identity', [ ( { 'orchard-identity-id': 1234, 'orchard-profile-id': 123, 'orchard-profile-type': 'OrchAdminProfile' }, False, ), ( {}, True, ), ] ) def test_upsert_timed_release_and_schedule_success( headers, mock_jwt_identity, monkeypatch ): """Test upsert_timed_release_and_schedule handler.""" request_body = { 'sales_date_time': '2025-04-24T05:30:00Z' } response_body = request_body monkeypatch.setattr( timed_release_ws, 'upsert_timed_release_and_schedule', MagicMock(return_value=response.Response(message=response_body)), ) if mock_jwt_identity: with patch('timed_release.handlers.g', spec=['request_context']) \ as mock_g: jwt_identity_id = authorization.OWS_DELIVERY_METADATA_IDENTITY_UUID mock_g.request_context.jwt_identity_id = jwt_identity_id result = app.test_client().put( '/product/1/timed_release/ws', data=json.dumps(request_body), content_type='application/json', headers=headers, ) ( timed_release_ws.upsert_timed_release_and_schedule. assert_called_with( 1, request_body, jwt_identity_id, ANY ) ) else: result = app.test_client().put( '/product/1/timed_release/ws', data=json.dumps(request_body), content_type='application/json', headers=headers, ) ( timed_release_ws.upsert_timed_release_and_schedule. assert_called_with( 1, request_body, '1234', ANY ) ) message = json.loads(result.data.decode()) assert result.status_code == http_status.OK assert message == response_body def test_upsert_timed_release_and_schedule_no_headers_failure(monkeypatch): """Test upsert_timed_release_and_schedule failure with no headers.""" request_body = { 'sales_start_datetime': '2025-04-24T05:30:00Z' } headers = {} result = app.test_client().put( '/product/1/timed_release/ws', data=json.dumps(request_body), content_type='application/json', headers=headers) assert result.status_code == http_status.BAD_REQUEST def test_get_product_timed_release_by_store_id_success( monkeypatch, timed_release_data_fixture): """Test get_product_timed_release_by_store_id success.""" headers = { 'orchard-identity-id': 1234, 'orchard-profile-id': 123, 'orchard-profile-type': 'OrchAdminProfile' } mock_get_timed_release = MagicMock(return_value=response.Response( status=http_status.OK, message=timed_release_data_fixture) ) monkeypatch.setattr( timed_release, 'get_product_timed_release_by_store_id', mock_get_timed_release ) result = app.test_client().get( '/product/12/timed_release/store/187', headers=headers ) result_message = json.loads(result.data.decode()) mock_get_timed_release.assert_called_with(12, 187) assert result.status_code == http_status.OK assert result_message == timed_release_data_fixture @pytest.mark.parametrize( 'headers, expected_status', [ ( { 'orchard-profile-id': 123, 'orchard-profile-type': 'OrchAdminProfile' }, http_status.BAD_REQUEST ), ( { 'orchard-identity-id': 1234 }, http_status.FORBIDDEN ), ] ) def test_get_product_timed_release_by_store_id_header_failures( headers, expected_status ): """Test get_product_timed_release_by_store_id headers.""" result = app.test_client().get( '/product/10/timed_release/store/1234', headers=headers ) assert result.status_code == expected_status def test_get_product_timed_release_for_ws_success( monkeypatch, timed_release_ws_fixture): """Test get_product_timed_release_for_ws success.""" headers = { 'orchard-identity-id': 1234, 'orchard-profile-id': 123, 'orchard-profile-type': 'OrchAdminProfile' } mock_get_timed_release_ws = MagicMock(return_value=response.Response( status=http_status.OK, message=timed_release_ws_fixture) ) monkeypatch.setattr( timed_release_ws, 'get_product_timed_release_for_ws', mock_get_timed_release_ws ) result = app.test_client().get( '/product/12/timed_release/ws', headers=headers ) result_message = json.loads(result.data.decode()) mock_get_timed_release_ws.assert_called_with(12) assert result.status_code == http_status.OK assert result_message == timed_release_ws_fixture @pytest.mark.parametrize( 'headers, expected_status', [ ( { 'orchard-profile-id': 123, 'orchard-profile-type': 'OrchAdminProfile' }, http_status.BAD_REQUEST ), ( { 'orchard-identity-id': 1234 }, http_status.FORBIDDEN ) ] ) def test_get_product_timed_release_for_ws_header_failures( headers, expected_status ): """Test get_product_timed_release_for_ws headers.""" result = app.test_client().get( '/product/10/timed_release/ws', headers=headers ) assert result.status_code == expected_status def test_delete_timed_release_ws_success(monkeypatch): """Test delete_timed_release_ws success.""" headers = { 'orchard-identity-id': 1234, 'orchard-profile-id': 123, 'orchard-profile-type': 'OrchAdminProfile' } mock_delete_timed_releases_and_scheduled_updates_ws = MagicMock( return_value=response.Response(status=http_status.NO_CONTENT) ) monkeypatch.setattr( timed_release_ws, 'delete_timed_releases_and_scheduled_updates_ws', mock_delete_timed_releases_and_scheduled_updates_ws ) result = app.test_client().delete( '/product/12/timed_release/ws', headers=headers ) mock_delete_timed_releases_and_scheduled_updates_ws.assert_called_with(12) assert result.status_code == http_status.NO_CONTENT @pytest.mark.parametrize( 'headers, expected_status', [ ( { 'orchard-profile-id': 123, 'orchard-profile-type': 'OrchAdminProfile' }, http_status.BAD_REQUEST ), ( { 'orchard-identity-id': 1234 }, http_status.FORBIDDEN ) ] ) def test_delete_timed_release_ws_header_failures( headers, expected_status ): """Test delete_timed_release_ws headers.""" result = app.test_client().delete( '/product/10/timed_release/ws', headers=headers ) assert result.status_code == expected_status