From fd080b3fef37dc653194e8ec30779f305b0291c0 Mon Sep 17 00:00:00 2001 From: Kayvan Zahiri <123409429+Kayvan-Zahiri@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:14:49 -0700 Subject: [PATCH] Fix timeout resets to use the call-time clock --- linode_api4/polling.py | 9 ++++++--- test/unit/polling_test.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 test/unit/polling_test.py diff --git a/linode_api4/polling.py b/linode_api4/polling.py index 7dc08d915..9e0643dfd 100644 --- a/linode_api4/polling.py +++ b/linode_api4/polling.py @@ -35,14 +35,17 @@ def __init__(self, timeout_seconds=120): self._start_time = datetime.datetime.now() self._timeout_seconds = timeout_seconds - def start(self, start_time=datetime.datetime.now()): + def start(self, start_time=None): """ Sets the timeout start time to the current time. - :param start_time: The moment when the context started. + :param start_time: The moment when the context started. If omitted, + use the current time when this method is called. :type start_time: datetime """ - self._start_time = start_time + self._start_time = ( + datetime.datetime.now() if start_time is None else start_time + ) def extend(self, seconds: int): """ diff --git a/test/unit/polling_test.py b/test/unit/polling_test.py new file mode 100644 index 000000000..3e275c7d9 --- /dev/null +++ b/test/unit/polling_test.py @@ -0,0 +1,37 @@ +import datetime +from unittest import TestCase +from unittest.mock import patch + +from linode_api4.polling import TimeoutContext + + +class TimeoutContextTest(TestCase): + def test_start_restarts_an_expired_timeout(self): + started_at = datetime.datetime(2026, 1, 1) + with patch( + "linode_api4.polling.datetime.datetime", wraps=datetime.datetime + ) as clock: + clock.now.return_value = started_at + context = TimeoutContext(timeout_seconds=10) + context.start(started_at) + + for elapsed in (30, 60): + clock.now.return_value = started_at + datetime.timedelta( + seconds=elapsed + ) + self.assertTrue(context.expired) + context.start() + self.assertEqual(context.seconds_remaining, 10) + self.assertTrue(context.valid) + + def test_start_preserves_explicit_start_time(self): + started_at = datetime.datetime(2026, 1, 1) + now = started_at + datetime.timedelta(seconds=7) + with patch( + "linode_api4.polling.datetime.datetime", wraps=datetime.datetime + ) as clock: + clock.now.return_value = now + context = TimeoutContext(timeout_seconds=10) + context.start(started_at) + self.assertEqual(context.seconds_remaining, 3) + self.assertTrue(context.valid)