"""Tests for Handlers.""" import json import time from operator import itemgetter from typing import Any from unittest.mock import MagicMock, Mock, patch import boto3 import pytest from _pytest.monkeypatch import MonkeyPatch from flask.ctx import AppContext from flask.testing import FlaskClient from moto import mock_aws from owsrequest import model from owsrequest.config import logger from owsresponse import response from requests import RequestException from werkzeug.exceptions import BadRequest from vectororder import handlers # noqa (handlers are imported for test client) from vectororder.constants import ( STATUS_BY_CATEGORY, delivery_statuses, encoding_statuses, fields, ) from vectororder.models.schemas import OrderData, OrderStatus, Priority def test_exception_handler(app_context: AppContext) -> None: """Verify exception_Handler returns 500 status code and json payload.""" message = ( "The server encountered an internal error " "and was unable to complete your request." ) server_response = handlers.main.exception_handler(Exception(message)) # type: ignore[attr-defined] # 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 def test_get_priorities(fixture_client: FlaskClient) -> None: """Test 'GET /priorities' handler.""" result = fixture_client.get("/priorities") data = json.loads(result.data.decode()) assert result.status_code == 200 assert fields.RESPONSE_ITEMS in data result_items = data[fields.RESPONSE_ITEMS] assert len(result_items) == len(Priority) assert sorted(result_items, key=itemgetter("priority_id")) == result_items for item in result_items: assert (item["priority_id"], item["title"]) in { 3: "Low", 2: "Normal", 1: "High", }.items() def test_get_statuses(fixture_client: FlaskClient) -> None: """Test 'GET /statuses' handler.""" result = fixture_client.get("/statuses") assert result.data.decode() == STATUS_BY_CATEGORY.model_dump_json(indent=2) @pytest.mark.parametrize( "logic_response, stores_query, stores_arg", ( ({"test": 1}, True, True), ({}, None, False), ), ) def test_get_order( logic_response: dict[Any, Any], stores_query: bool, stores_arg: bool, fixture_client: FlaskClient, mocker: Mock, ) -> None: """Test endpoint that retrieves a single Vector order.""" mock_get_order = mocker.patch( "vectororder.handlers.main.orders.get_order", return_value=logic_response, ) result = fixture_client.get("/orders/0", query_string={"stores": stores_query}) data = json.loads(result.data.decode()) assert mock_get_order.call_args[0] == (0, stores_arg) assert data == logic_response @mock_aws @pytest.mark.parametrize( ( "logic_response", "stores_arg", "expected_stores", "order_id_arg", "expected_order_ids", "expected_data", ), ( ( [{"a": 1}, {"b": 2}], "True", True, "1,2", [1, 2], {fields.RESPONSE_ITEMS: [{"a": 1}, {"b": 2}]}, ), ( [], "n", False, "1", [1], {fields.RESPONSE_ITEMS: []}, ), ), ) def test_get_orders( logic_response: list[Any], stores_arg: bool, expected_stores: bool, order_id_arg: str, expected_order_ids: list[int], expected_data: dict[Any, Any], fixture_client: FlaskClient, mocker: Mock, monkeypatch: MonkeyPatch, ) -> None: """Test endpoint that retrieves multiple Vector orders.""" mock_get_orders = mocker.patch("vectororder.handlers.main.orders.get_orders") mock_get_orders.return_value = logic_response boto3.setup_default_session(region_name="us-east-1") monkeypatch.setattr(time, "time", MagicMock(return_value=1580458555)) monkeypatch.setattr(model, "request_latency", MagicMock(return_value=True)) mocker.patch.object(logger, "info", return_value="") result = fixture_client.get( "/orders", query_string={"stores": stores_arg, "order_id": order_id_arg}, ) data = json.loads(result.data.decode()) assert data == expected_data assert mock_get_orders.call_count == 1 assert mock_get_orders.call_args[0] == (expected_order_ids, expected_stores) @pytest.mark.parametrize("logic_result", [{}, {"test": 1}]) def test_get_encoders( logic_result: dict[str, int], fixture_client: FlaskClient, mocker: Mock ) -> None: """Test 'GET /encoders' handler.""" mocked_get_encoders = mocker.patch("vectororder.logic.encoders.get_encoders") mocked_get_encoders.return_value = logic_result result = fixture_client.get("/encoders") data = json.loads(result.data.decode()) assert result.status_code == 200 assert data == logic_result def test_get_encoding_statuses(fixture_client: FlaskClient) -> None: """Test 'GET /encoding-statuses' handler.""" result = fixture_client.get("/encoding-statuses") data = json.loads(result.data.decode()) assert result.status_code == 200 assert data == encoding_statuses.ENCODING_STATUSES def test_get_delivery_statuses(fixture_client: FlaskClient) -> None: """Test 'GET /delivery-statuses' handler.""" result = fixture_client.get("/delivery-statuses") data = json.loads(result.data.decode()) assert result.status_code == 200 assert data == delivery_statuses.DELIVERY_STATUSES def test_http_exception_handler(app_context: AppContext) -> None: """Verify http_exception_handler returns HTTP status code and json payload.""" mock_error = BadRequest("Invalid payload") server_response = handlers.main.http_exception_handler(mock_error) # type: ignore[attr-defined] assert server_response.status_code == 400 assert json.loads(server_response.data.decode("utf-8")) == { "code": "BadRequest", "message": "Invalid payload", } def test_request_exceptions_handlers(app_context: AppContext) -> None: """Verify not_found_exception_handler returns HTTP status code and json payload.""" mock_error = RequestException("Some Exception") server_response = handlers.main.request_exception_handler(mock_error) # type: ignore[attr-defined] assert server_response.status_code == 502 assert json.loads(server_response.data.decode("utf-8")) == { "code": "RequestException", "message": "Some Exception", } @patch("vectororder.handlers.main.orders.create_order") def test_create_order( mock_create_order_logic: MagicMock, fixture_client: FlaskClient ) -> None: """Test 'POST /orders' handler.""" payload: dict[str, Any] = { "encoder_id": 18, "is_meta_update": False, "order_status": "open", "priority": 1, "store_ids": [1, 2, 3], "upcs": [], "user_id": 101, } mock_create_order_logic.return_value = 1 result = fixture_client.post( "/orders", data=json.dumps(payload), headers={"Content-Type": "application/json"}, ) assert result.status_code == 200 assert result.json == {"order_id": 1} assert mock_create_order_logic.call_count == 1 mock_create_order_logic.assert_called_with(OrderData(**payload)) def test_create_order_invalid_request(fixture_client: FlaskClient) -> None: """Test 'POST /orders' handler.""" payload = { "encoder_id": 18, "is_meta_update": False, "order_status": "close", "priority": 1, "store_ids": [1, 2, 3], "upcs": [], "user_id": 101, } result = fixture_client.post( "/orders", data=json.dumps(payload), headers={"Content-Type": "application/json"}, ) assert result.status_code == 400 @patch("vectororder.handlers.main.orders.update_order") def test_update_order( mock_update_order_logic: MagicMock, fixture_client: FlaskClient ) -> None: """Test 'PATCH /orders' handler.""" payload = {"order_status": "close"} result = fixture_client.patch( "/orders/123", data=json.dumps(payload), headers={"Content-Type": "application/json"}, ) assert result.status_code == 204 assert mock_update_order_logic.call_count == 1 mock_update_order_logic.assert_called_with(123, set(), OrderStatus.CLOSED) def test_update_order_invalid_order_status(fixture_client: FlaskClient) -> None: """Test 'PATCH /orders' handler.""" order_status = "some_other_value" payload = {"order_status": order_status} result = fixture_client.patch( "/orders/123", data=json.dumps(payload), headers={"Content-Type": "application/json"}, ) assert result.status_code == 400 assert json.loads(result.text) == { "code": "InvalidOrderRequest", "message": "Order status 'some_other_value' not allowed.", }