Skip to content

Commit f2eb397

Browse files
committed
Narrow reco counts request JSON
1 parent cbf8c01 commit f2eb397

2 files changed

Lines changed: 51 additions & 11 deletions

File tree

src/mock_vws/_reco_counts_web_api.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@
33
import base64
44
import datetime
55
import email.utils
6-
import json
76
import logging
87
import re
98
import secrets
109
import string
1110
import uuid
1211
from collections.abc import Mapping
1312
from http import HTTPStatus
14-
from typing import Any, Protocol, runtime_checkable
13+
from typing import Protocol, TypedDict, runtime_checkable
1514
from urllib.parse import parse_qs, urlencode, urlsplit
1615
from zoneinfo import ZoneInfo
1716

1817
from beartype import beartype
18+
from pydantic import TypeAdapter, ValidationError
1919

2020
from mock_vws._constants import ResultCodes
2121
from mock_vws._mock_common import json_dump
@@ -38,6 +38,15 @@
3838
_SIGNING_REGION = "us-west-1"
3939

4040

41+
class _RecoCountsRequest(TypedDict):
42+
"""JSON body for a recognition-count report request."""
43+
44+
month: str
45+
46+
47+
_RECO_COUNTS_REQUEST_ADAPTER = TypeAdapter(type=_RecoCountsRequest)
48+
49+
4150
@runtime_checkable
4251
class RecoCountsReportStore(Protocol):
4352
"""Storage for generated reco counts reports."""
@@ -285,13 +294,13 @@ def create_reco_counts_report(
285294
FailError: The given month is not a month in the ``YYYY-mm`` form
286295
which the report can be requested for.
287296
"""
288-
request_json: dict[str, Any] = json.loads(s=request_body) # pyrefly: ignore [explicit-any]
297+
try:
298+
request_json = _RECO_COUNTS_REQUEST_ADAPTER.validate_json(request_body)
299+
except ValidationError as exc:
300+
_LOGGER.warning(msg='The given "month" is not in the YYYY-mm form.')
301+
raise FailError(status_code=HTTPStatus.BAD_REQUEST) from exc
289302
month = request_json["month"]
290-
if not isinstance(month, str) or not bool(
291-
_MONTH_PATTERN.fullmatch(
292-
string=month,
293-
)
294-
):
303+
if not bool(_MONTH_PATTERN.fullmatch(string=month)):
295304
_LOGGER.warning(msg='The given "month" is not in the YYYY-mm form.')
296305
raise FailError(status_code=HTTPStatus.BAD_REQUEST)
297306

tests/mock_vws/test_reco_counts_report.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,11 @@ def _month_offset_from_now(*, months: int) -> str:
8383

8484

8585
@beartype
86-
def _request_reco_counts_report(
86+
def _request_reco_counts_report_json(
8787
*,
8888
vuforia_database: CloudDatabase,
8989
database_id: str,
90-
month: str | int,
90+
request_json: object,
9191
) -> requests.Response:
9292
"""Request a reco counts report and return the response.
9393
@@ -96,7 +96,7 @@ def _request_reco_counts_report(
9696
"""
9797
request_path = f"/imagetargets/databases/{database_id}/reports/recoCounts"
9898
content_type = "application/json"
99-
content = json.dumps(obj={"month": month}).encode(encoding="utf-8")
99+
content = json.dumps(obj=request_json).encode(encoding="utf-8")
100100
date = rfc_1123_date()
101101
authorization_string = authorization_header(
102102
access_key=vuforia_database.server_access_key,
@@ -121,6 +121,21 @@ def _request_reco_counts_report(
121121
)
122122

123123

124+
@beartype
125+
def _request_reco_counts_report(
126+
*,
127+
vuforia_database: CloudDatabase,
128+
database_id: str,
129+
month: str | int,
130+
) -> requests.Response:
131+
"""Request a reco counts report for one month."""
132+
return _request_reco_counts_report_json(
133+
vuforia_database=vuforia_database,
134+
database_id=database_id,
135+
request_json={"month": month},
136+
)
137+
138+
124139
@beartype
125140
def _presigned_url(*, vuforia_database: CloudDatabase, month: str) -> str:
126141
"""Request a report for the given month and return its download
@@ -386,6 +401,22 @@ def test_malformed_month(
386401
response_json = json.loads(s=response.text)
387402
assert response_json["result_code"] == ResultCodes.FAIL.value
388403

404+
@staticmethod
405+
def test_body_is_not_an_object(
406+
*,
407+
vuforia_database: CloudDatabase,
408+
) -> None:
409+
"""The request body must be a JSON object."""
410+
response = _request_reco_counts_report_json(
411+
vuforia_database=vuforia_database,
412+
database_id=vuforia_database.database_id,
413+
request_json=[],
414+
)
415+
416+
assert response.status_code == HTTPStatus.BAD_REQUEST
417+
response_json = json.loads(s=response.text)
418+
assert response_json["result_code"] == ResultCodes.FAIL.value
419+
389420
@staticmethod
390421
def test_unknown_database_id(*, vuforia_database: CloudDatabase) -> None:
391422
"""The path must name the database which the request's server

0 commit comments

Comments
 (0)