"""Tests for Handlers.""" import json from unittest.mock import MagicMock, Mock, patch import pytest from flask.testing import FlaskClient from owsresponse import status from syrupy.assertion import SnapshotAssertion from werkzeug.exceptions import NotFound from vectororder import exceptions from vectororder.constants import error, header, pagination from vectororder.schemas import search_schema def test_search_handler_validation_error( fixture_client: FlaskClient, mocker: Mock ) -> None: """Test search endpoint validation error is propagated.""" err_field = "product_ids" payload = {err_field: [-42]} message = "msg" headers = { header.CONTENT_TYPE: header.CONTENT_TYPE_JSON_VAL, "Orchard-User-Id": "oa:123", } handlers_utils_mock = mocker.patch("vectororder.handlers.search.handlers") parse_request_json_mock = mocker.Mock( side_effect=exceptions.RequestError(message=message) ) handlers_utils_mock.parse_request_json = parse_request_json_mock search_logic_mock = mocker.patch("vectororder.handlers.search.search") result = fixture_client.post( "/jobs/search", headers=headers, data=json.dumps(payload) ) res_data = json.loads(result.data.decode()) parse_request_json_mock.assert_called_with( schema=search_schema.SearchPOSTSchema, request_data=payload ) assert result.status_code == 400 assert res_data["message"] == message assert search_logic_mock.search_vector_orders.call_count == 0 @pytest.mark.parametrize( "payload, offset, limit", ( ({"product_ids": [1]}, None, None), ({"statuses": ["foo", "bar"]}, None, None), ({"statuses": ["foo", "bar"]}, 0, 0), ({"statuses": ["foo", "bar"]}, 1, 5), ({"product_ids": [1]}, None, None), ), ) def test_search_handler_success( payload: dict[str, list[int | str]], offset: int, limit: int, fixture_client: FlaskClient, mocker: Mock, snapshot: SnapshotAssertion, ) -> None: """Test search endpoint success.""" order_by = "foo,bar" expected_order_by = ["foo", "bar"] search_result_items = [{}, 42] search_result_total = 20 request_id = "abc123" parsed_payload = {"status": ["foo", "bar"]} headers = { header.CONTENT_TYPE: header.CONTENT_TYPE_JSON_VAL, "Orchard-User-Id": "oa:123", } handlers_utils_mock = mocker.patch("vectororder.handlers.search.handlers") parse_request_json_mock = mocker.Mock(return_value=parsed_payload) handlers_utils_mock.parse_request_json = parse_request_json_mock handlers_utils_mock.get_order_by.return_value = expected_order_by search_logic_mock = mocker.patch("vectororder.handlers.search.search") search_vector_orders_mock = mocker.Mock( return_value=(search_result_items, search_result_total) ) search_logic_mock.search_vector_orders = search_vector_orders_mock save_search_request_mock = mocker.Mock(return_value=request_id) search_logic_mock.save_search_request = save_search_request_mock query_params = {} if offset: query_params["page_offset"] = str(offset) expected_offset = offset else: expected_offset = pagination.PAGE_OFFSET_DEFAULT if limit: query_params["page_limit"] = str(limit) expected_limit = limit else: expected_limit = pagination.PAGE_LIMIT_DEFAULT query_params["order_by"] = order_by result = fixture_client.post( "/jobs/search", headers=headers, data=json.dumps(payload), query_string=query_params, ) result_data = json.loads(result.data.decode()) parse_request_json_mock.assert_called_with( schema=search_schema.SearchPOSTSchema, request_data=payload ) assert handlers_utils_mock.get_order_by.call_args[0] == (order_by,) assert search_vector_orders_mock.call_count == 1 assert search_vector_orders_mock.call_args[1] == { "offset": expected_offset, "limit": expected_limit, "order_by": expected_order_by, "search_kwargs": parsed_payload, } assert save_search_request_mock.call_count == 1 assert save_search_request_mock.call_args[0] == (payload,) assert result.status_code == 200 assert snapshot == result_data def test_search_handler_with_search_request_id( fixture_client: FlaskClient, mocker: Mock, snapshot: SnapshotAssertion ) -> None: """Test search endpoint with request ID.""" order_by = "foo,bar" expected_order_by = ["foo", "bar"] search_result_items = [{}, 42] search_result_total = 20 request_id = "abc123" payload = {"statuses": ["foo", "bar"]} parsed_payload = {"status": ["foo", "bar"]} headers = { header.CONTENT_TYPE: header.CONTENT_TYPE_JSON_VAL, "Orchard-User-Id": "oa:123", } handlers_utils_mock = mocker.patch("vectororder.handlers.search.handlers") parse_request_json_mock = mocker.Mock(return_value=parsed_payload) handlers_utils_mock.parse_request_json = parse_request_json_mock handlers_utils_mock.get_order_by.return_value = expected_order_by search_logic_mock = mocker.patch("vectororder.handlers.search.search") search_vector_orders_mock = mocker.Mock( return_value=(search_result_items, search_result_total) ) search_logic_mock.search_vector_orders = search_vector_orders_mock get_search_request_mock = mocker.Mock(return_value=payload) search_logic_mock.get_search_request = get_search_request_mock query_params = { "order_by": order_by, "request_id": request_id, } result = fixture_client.post( "/jobs/search", headers=headers, data=json.dumps(payload), query_string=query_params, ) result_data = json.loads(result.data.decode()) parse_request_json_mock.assert_called_with( schema=search_schema.SearchPOSTSchema, request_data=payload ) assert handlers_utils_mock.get_order_by.call_args[0] == (order_by,) assert search_vector_orders_mock.call_count == 1 assert search_vector_orders_mock.call_args[1] == { "offset": pagination.PAGE_OFFSET_DEFAULT, "limit": pagination.PAGE_LIMIT_DEFAULT, "order_by": expected_order_by, "search_kwargs": parsed_payload, } assert get_search_request_mock.call_count == 1 assert get_search_request_mock.call_args[0] == (request_id,) assert result.status_code == 200 assert snapshot == result_data @patch( "vectororder.handlers.search.search.get_search_request", side_effect=NotFound(error.ERROR_MESSAGE_SEARCH_REQUEST_NOT_FOUND), ) def test_search_handler_with_request_id_not_found( get_search_request_mock: MagicMock, fixture_client: FlaskClient, ) -> None: """Test search handler with no matching request ID.""" request_id = "123abc" headers = { header.CONTENT_TYPE: header.CONTENT_TYPE_JSON_VAL, "Orchard-User-Id": "oa:123", } result = fixture_client.post( "/jobs/search", headers=headers, data=json.dumps({}), query_string={"request_id": request_id}, ) assert result.status_code == status.NOT_FOUND assert json.loads(result.data.decode()) == { "code": "NotFound", "message": "Search request for provided ID was not found", } assert get_search_request_mock.call_count == 1 assert get_search_request_mock.call_args[0] == (request_id,)