diff --git a/src/mock_vws/_constants.py b/src/mock_vws/_constants.py index 68bf9375f..52c88bb9b 100644 --- a/src/mock_vws/_constants.py +++ b/src/mock_vws/_constants.py @@ -62,6 +62,7 @@ class ResultCodes(Enum): TOO_MANY_REQUESTS = "TooManyRequests" INVALID_ACCEPT_HEADER = "InvalidAcceptHeader" INVALID_INSTANCE_ID = "InvalidInstanceId" + BAD_REQUEST = "BadRequest" @beartype diff --git a/src/mock_vws/_services_validators/__init__.py b/src/mock_vws/_services_validators/__init__.py index 44487b365..03a39bd1c 100644 --- a/src/mock_vws/_services_validators/__init__.py +++ b/src/mock_vws/_services_validators/__init__.py @@ -103,7 +103,7 @@ def run_services_validators( validate_date_format(request_headers=request_headers) validate_date_in_range(request_headers=request_headers) - validate_json(request_body=request_body) + validate_json(request_body=request_body, request_path=request_path) validate_keys( request_body=request_body, diff --git a/src/mock_vws/_services_validators/exceptions.py b/src/mock_vws/_services_validators/exceptions.py index f7cbe2439..a722f29ab 100644 --- a/src/mock_vws/_services_validators/exceptions.py +++ b/src/mock_vws/_services_validators/exceptions.py @@ -184,6 +184,46 @@ def __init__(self, *, status_code: HTTPStatus) -> None: } +@beartype +class BadRequestError(ValidatorError): + """Exception raised when Vuforia returns a response with a result code + 'BadRequest'. + """ + + def __init__(self) -> None: + """ + Attributes: + status_code: The status code to use in a response if this is + raised. + response_text: The response text to use in a response if this + is + raised. + """ + super().__init__() + self.status_code = HTTPStatus.BAD_REQUEST + body = { + "transaction_id": uuid.uuid4().hex, + "result_code": ResultCodes.BAD_REQUEST.value, + } + self.response_text = json_dump(body=body) + date = email.utils.formatdate( + timeval=None, + localtime=False, + usegmt=True, + ) + self.headers = { + "Connection": "keep-alive", + "Content-Type": "application/json", + "server": "envoy", + "Date": date, + "x-envoy-upstream-service-time": "5", + "Content-Length": str(object=len(self.response_text)), + "strict-transport-security": "max-age=31536000", + "x-aws-region": "us-east-2, us-west-2", + "x-content-type-options": "nosniff", + } + + @beartype class MetadataTooLargeError(ValidatorError): """Exception raised when Vuforia returns a response with a result code diff --git a/src/mock_vws/_services_validators/json_validators.py b/src/mock_vws/_services_validators/json_validators.py index 9d04eec29..4e0549cd0 100644 --- a/src/mock_vws/_services_validators/json_validators.py +++ b/src/mock_vws/_services_validators/json_validators.py @@ -8,6 +8,7 @@ from beartype import beartype from mock_vws._services_validators.exceptions import ( + BadRequestError, FailError, UnnecessaryRequestBodyError, ) @@ -43,14 +44,18 @@ def validate_body_given(*, request_body: bytes, request_method: str) -> None: @beartype -def validate_json(*, request_body: bytes) -> None: +def validate_json(*, request_body: bytes, request_path: str) -> None: """Validate that any given body is valid JSON. Args: request_body: The body of the request. + request_path: The path of the request. Raises: - FailError: The request body includes invalid JSON. + BadRequestError: The request body includes invalid JSON for the + VuMark instance generation endpoint. + FailError: The request body includes invalid JSON for other + endpoints. """ if not request_body: return @@ -59,4 +64,6 @@ def validate_json(*, request_body: bytes) -> None: json.loads(s=request_body.decode()) except JSONDecodeError as exc: _LOGGER.warning(msg="The request body is not valid JSON.") + if request_path.endswith("/instances"): + raise BadRequestError from exc raise FailError(status_code=HTTPStatus.BAD_REQUEST) from exc diff --git a/tests/conftest.py b/tests/conftest.py index 64ef1427f..76b970470 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -85,6 +85,7 @@ def target_id( "target_summary", "update_target", "query", + "vumark_generate_instance", ], ) def endpoint(request: pytest.FixtureRequest) -> Endpoint: diff --git a/tests/mock_vws/fixtures/prepared_requests.py b/tests/mock_vws/fixtures/prepared_requests.py index 6a1d93aa8..724ea8e27 100644 --- a/tests/mock_vws/fixtures/prepared_requests.py +++ b/tests/mock_vws/fixtures/prepared_requests.py @@ -5,6 +5,7 @@ import json from http import HTTPMethod, HTTPStatus from typing import Any +from uuid import uuid4 import pytest from urllib3.filepost import encode_multipart_formdata @@ -13,6 +14,7 @@ from mock_vws._constants import ResultCodes from mock_vws.database import VuforiaDatabase +from tests.mock_vws.fixtures.credentials import VuMarkVuforiaDatabase from tests.mock_vws.utils import Endpoint from tests.mock_vws.utils.retries import RETRY_ON_TOO_MANY_REQUESTS @@ -451,3 +453,49 @@ def query( access_key=access_key, secret_key=secret_key, ) + + +@pytest.fixture +def vumark_generate_instance( + vumark_vuforia_database: VuMarkVuforiaDatabase, +) -> Endpoint: + """Return details of the endpoint for generating a VuMark instance.""" + request_path = f"/targets/{vumark_vuforia_database.target_id}/instances" + content_type = "application/json" + method = HTTPMethod.POST + content = json.dumps(obj={"instance_id": uuid4().hex}).encode( + encoding="utf-8" + ) + date = rfc_1123_date() + + access_key = vumark_vuforia_database.server_access_key + secret_key = vumark_vuforia_database.server_secret_key + authorization_string = authorization_header( + access_key=access_key, + secret_key=secret_key, + method=method, + content=content, + content_type=content_type, + date=date, + request_path=request_path, + ) + + headers = { + "Accept": "image/png", + "Authorization": authorization_string, + "Content-Length": str(object=len(content)), + "Content-Type": content_type, + "Date": date, + } + + return Endpoint( + successful_headers_status_code=HTTPStatus.OK, + successful_headers_result_code=None, + base_url=VWS_HOST, + path_url=request_path, + method=method, + headers=headers, + data=content, + access_key=access_key, + secret_key=secret_key, + ) diff --git a/tests/mock_vws/test_date_header.py b/tests/mock_vws/test_date_header.py index eea178ac3..d5e5f0b45 100644 --- a/tests/mock_vws/test_date_header.py +++ b/tests/mock_vws/test_date_header.py @@ -381,6 +381,12 @@ def test_date_in_range_after(endpoint: Endpoint) -> None: assert_query_success(response=response) return + if endpoint.successful_headers_result_code is None: + assert ( + response.status_code == endpoint.successful_headers_status_code + ) + return + assert_vws_response( response=response, status_code=endpoint.successful_headers_status_code, @@ -445,6 +451,12 @@ def test_date_in_range_before(endpoint: Endpoint) -> None: assert_query_success(response=response) return + if endpoint.successful_headers_result_code is None: + assert ( + response.status_code == endpoint.successful_headers_status_code + ) + return + assert_vws_response( response=response, status_code=endpoint.successful_headers_status_code, diff --git a/tests/mock_vws/test_invalid_json.py b/tests/mock_vws/test_invalid_json.py index 55d01ff6d..35b3253cd 100644 --- a/tests/mock_vws/test_invalid_json.py +++ b/tests/mock_vws/test_invalid_json.py @@ -75,10 +75,15 @@ def test_invalid_json(endpoint: Endpoint) -> None: assert_valid_date_header(response=response) if takes_json_data: + expected_result_code = ( + ResultCodes.BAD_REQUEST + if endpoint.path_url.endswith("/instances") + else ResultCodes.FAIL + ) assert_vws_failure( response=response, status_code=HTTPStatus.BAD_REQUEST, - result_code=ResultCodes.FAIL, + result_code=expected_result_code, ) return diff --git a/tests/mock_vws/utils/__init__.py b/tests/mock_vws/utils/__init__.py index c554e4571..d4bfa0014 100644 --- a/tests/mock_vws/utils/__init__.py +++ b/tests/mock_vws/utils/__init__.py @@ -47,7 +47,7 @@ class Endpoint: method: str headers: Mapping[str, str] data: bytes | str - successful_headers_result_code: ResultCodes + successful_headers_result_code: ResultCodes | None successful_headers_status_code: int access_key: str secret_key: str