Skip to content

Commit 69412b1

Browse files
Merge pull request #2915 from VWS-Python/adamtheturtle/flask-response-delay
Add response_delay_seconds to Flask mock
2 parents 0e2ef17 + 0e09b21 commit 69412b1

File tree

5 files changed

+86
-12
lines changed

5 files changed

+86
-12
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,8 @@ Changelog
44
Next
55
----
66

7-
2026.02.15.2
8-
------------
9-
10-
11-
2026.02.15.1
12-
------------
13-
14-
15-
2026.02.15
16-
----------
17-
18-
197
- Add ``response_delay_seconds`` parameter to ``MockVWS`` for simulating slow server responses and testing timeout handling.
8+
- Add ``response_delay_seconds`` setting to the Flask mock (``VWSSettings`` and ``VWQSettings``) for simulating slow server responses.
209

2110
2025.03.10.1
2211
------------

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ ignore_decorators = [
446446
"@pytest.fixture",
447447
# Flask
448448
"@*APP.route",
449+
"@*APP.after_request",
449450
"@*APP.before_request",
450451
"@*APP.errorhandler",
451452
]

src/mock_vws/_flask_server/vwq.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66

77
import email.utils
8+
import time
89
from enum import StrEnum, auto
910
from http import HTTPMethod, HTTPStatus
1011

@@ -58,6 +59,7 @@ class VWQSettings(BaseSettings):
5859
query_image_matcher: _ImageMatcherChoice = (
5960
_ImageMatcherChoice.STRUCTURAL_SIMILARITY
6061
)
62+
response_delay_seconds: float = 0.0
6163

6264

6365
@beartype
@@ -101,6 +103,15 @@ def set_terminate_wsgi_input() -> None:
101103
request.environ["wsgi.input_terminated"] = True
102104

103105

106+
@CLOUDRECO_FLASK_APP.after_request
107+
@beartype
108+
def add_response_delay(response: Response) -> Response:
109+
"""Add a delay to each response."""
110+
settings = VWQSettings.model_validate(obj={})
111+
time.sleep(settings.response_delay_seconds)
112+
return response
113+
114+
104115
@CLOUDRECO_FLASK_APP.errorhandler(code_or_exception=ValidatorError)
105116
def handle_exceptions(exc: ValidatorError) -> Response:
106117
"""Return the error response associated with the given exception."""

src/mock_vws/_flask_server/vws.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import email.utils
99
import json
1010
import logging
11+
import time
1112
import uuid
1213
from enum import StrEnum, auto
1314
from http import HTTPMethod, HTTPStatus
@@ -73,6 +74,7 @@ class VWSSettings(BaseSettings):
7374
duplicates_image_matcher: _ImageMatcherChoice = (
7475
_ImageMatcherChoice.STRUCTURAL_SIMILARITY
7576
)
77+
response_delay_seconds: float = 0.0
7678

7779

7880
@beartype
@@ -130,6 +132,15 @@ def validate_request() -> None:
130132
)
131133

132134

135+
@VWS_FLASK_APP.after_request
136+
@beartype
137+
def add_response_delay(response: Response) -> Response:
138+
"""Add a delay to each response."""
139+
settings = VWSSettings.model_validate(obj={})
140+
time.sleep(settings.response_delay_seconds)
141+
return response
142+
143+
133144
@VWS_FLASK_APP.errorhandler(code_or_exception=ValidatorError)
134145
def handle_exceptions(exc: ValidatorError) -> Response:
135146
"""Return the error response associated with the given exception."""

tests/mock_vws/test_flask_app_usage.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
"""Tests for the usage of the mock Flask application."""
22

3+
import email.utils
34
import io
45
import json
6+
import time
57
import uuid
68
from collections.abc import Iterator
79
from http import HTTPStatus
@@ -605,3 +607,63 @@ def test_random(
605607
assert lowest_rating >= minimum_rating
606608
assert highest_rating <= maximum_rating
607609
assert lowest_rating != highest_rating
610+
611+
612+
class TestResponseDelay:
613+
"""Tests for the response delay feature.
614+
615+
These tests run through the ``responses`` library, which intercepts
616+
requests in-process. Because of this, the client ``timeout`` parameter
617+
is not enforced — the delay blocks but never raises
618+
``requests.exceptions.Timeout``. When running the Flask app as a real
619+
server (e.g. in Docker), the delay causes a genuinely slow HTTP
620+
response and the ``requests`` client will raise ``Timeout`` on its own.
621+
"""
622+
623+
DELAY_SECONDS = 0.5
624+
625+
@staticmethod
626+
def _make_request() -> None:
627+
"""Make a request to the VWS API."""
628+
requests.get(
629+
url="https://vws.vuforia.com/summary",
630+
headers={
631+
"Date": email.utils.formatdate(
632+
timeval=None,
633+
localtime=False,
634+
usegmt=True,
635+
),
636+
"Authorization": "bad_auth_token",
637+
},
638+
data=b"",
639+
timeout=30,
640+
)
641+
642+
def test_default_no_delay(self) -> None:
643+
"""By default, there is no response delay."""
644+
database = VuforiaDatabase()
645+
databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases"
646+
requests.post(url=databases_url, json=database.to_dict(), timeout=30)
647+
648+
start = time.monotonic()
649+
self._make_request()
650+
elapsed = time.monotonic() - start
651+
assert elapsed < self.DELAY_SECONDS
652+
653+
def test_delay_is_applied(
654+
self,
655+
monkeypatch: pytest.MonkeyPatch,
656+
) -> None:
657+
"""When response_delay_seconds is set, the response is delayed."""
658+
monkeypatch.setenv(
659+
name="RESPONSE_DELAY_SECONDS",
660+
value=f"{self.DELAY_SECONDS}",
661+
)
662+
database = VuforiaDatabase()
663+
databases_url = _EXAMPLE_URL_FOR_TARGET_MANAGER + "/databases"
664+
requests.post(url=databases_url, json=database.to_dict(), timeout=30)
665+
666+
start = time.monotonic()
667+
self._make_request()
668+
elapsed = time.monotonic() - start
669+
assert elapsed >= self.DELAY_SECONDS

0 commit comments

Comments
 (0)