Skip to content

Commit 339452e

Browse files
authored
fix(functions): Remove usage of deprecated datetime.utcnow() and fix flaky unit test (#896)
1 parent 363166b commit 339452e

File tree

2 files changed

+40
-23
lines changed

2 files changed

+40
-23
lines changed

firebase_admin/functions.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"""Firebase Functions module."""
1616

1717
from __future__ import annotations
18-
from datetime import datetime, timedelta
18+
from datetime import datetime, timedelta, timezone
1919
from urllib import parse
2020
import re
2121
import json
@@ -255,7 +255,8 @@ def _validate_task_options(
255255
if not isinstance(opts.schedule_delay_seconds, int) \
256256
or opts.schedule_delay_seconds < 0:
257257
raise ValueError('schedule_delay_seconds should be positive int.')
258-
schedule_time = datetime.utcnow() + timedelta(seconds=opts.schedule_delay_seconds)
258+
schedule_time = (
259+
datetime.now(timezone.utc) + timedelta(seconds=opts.schedule_delay_seconds))
259260
task.schedule_time = schedule_time.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
260261
if opts.dispatch_deadline_seconds is not None:
261262
if not isinstance(opts.dispatch_deadline_seconds, int) \

tests/test_functions.py

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
"""Test cases for the firebase_admin.functions module."""
1616

17-
from datetime import datetime, timedelta
17+
from datetime import datetime, timedelta, timezone
1818
import json
1919
import time
2020
import pytest
@@ -33,8 +33,6 @@
3333
_CLOUD_TASKS_URL + 'projects/test-project/locations/us-central1/queues/test-function-name/tasks'
3434
_DEFAULT_TASK_URL = _CLOUD_TASKS_URL + _DEFAULT_TASK_PATH
3535
_DEFAULT_RESPONSE = json.dumps({'name': _DEFAULT_TASK_PATH})
36-
_ENQUEUE_TIME = datetime.utcnow()
37-
_SCHEDULE_TIME = _ENQUEUE_TIME + timedelta(seconds=100)
3836

3937
class TestTaskQueue:
4038
@classmethod
@@ -185,47 +183,65 @@ def _instrument_functions_service(self, app=None, status=200, payload=_DEFAULT_R
185183
testutils.MockAdapter(payload, status, recorder))
186184
return functions_service, recorder
187185

188-
189-
@pytest.mark.parametrize('task_opts_params', [
190-
{
186+
def test_task_options_delay_seconds(self):
187+
_, recorder = self._instrument_functions_service()
188+
enqueue_time = datetime.now(timezone.utc)
189+
expected_schedule_time = enqueue_time + timedelta(seconds=100)
190+
task_opts_params = {
191191
'schedule_delay_seconds': 100,
192192
'schedule_time': None,
193193
'dispatch_deadline_seconds': 200,
194194
'task_id': 'test-task-id',
195195
'headers': {'x-test-header': 'test-header-value'},
196196
'uri': 'https://google.com'
197-
},
198-
{
197+
}
198+
queue = functions.task_queue('test-function-name')
199+
task_opts = functions.TaskOptions(**task_opts_params)
200+
queue.enqueue(_DEFAULT_DATA, task_opts)
201+
202+
assert len(recorder) == 1
203+
task = json.loads(recorder[0].body.decode())['task']
204+
205+
task_schedule_time = datetime.fromisoformat(task['schedule_time'].replace('Z', '+00:00'))
206+
delta = abs(task_schedule_time - expected_schedule_time)
207+
assert delta <= timedelta(seconds=1)
208+
209+
assert task['dispatch_deadline'] == '200s'
210+
assert task['http_request']['headers']['x-test-header'] == 'test-header-value'
211+
assert task['http_request']['url'] in ['http://google.com', 'https://google.com']
212+
assert task['name'] == _DEFAULT_TASK_PATH
213+
214+
def test_task_options_utc_time(self):
215+
_, recorder = self._instrument_functions_service()
216+
enqueue_time = datetime.now(timezone.utc)
217+
expected_schedule_time = enqueue_time + timedelta(seconds=100)
218+
task_opts_params = {
199219
'schedule_delay_seconds': None,
200-
'schedule_time': _SCHEDULE_TIME,
220+
'schedule_time': expected_schedule_time,
201221
'dispatch_deadline_seconds': 200,
202222
'task_id': 'test-task-id',
203223
'headers': {'x-test-header': 'test-header-value'},
204224
'uri': 'http://google.com'
205-
},
206-
])
207-
def test_task_options(self, task_opts_params):
208-
_, recorder = self._instrument_functions_service()
225+
}
209226
queue = functions.task_queue('test-function-name')
210227
task_opts = functions.TaskOptions(**task_opts_params)
211228
queue.enqueue(_DEFAULT_DATA, task_opts)
212229

213230
assert len(recorder) == 1
214231
task = json.loads(recorder[0].body.decode())['task']
215232

216-
schedule_time = datetime.fromisoformat(task['schedule_time'][:-1])
217-
delta = abs(schedule_time - _SCHEDULE_TIME)
218-
assert delta <= timedelta(seconds=15)
233+
task_schedule_time = datetime.fromisoformat(task['schedule_time'].replace('Z', '+00:00'))
234+
assert task_schedule_time == expected_schedule_time
219235

220236
assert task['dispatch_deadline'] == '200s'
221237
assert task['http_request']['headers']['x-test-header'] == 'test-header-value'
222238
assert task['http_request']['url'] in ['http://google.com', 'https://google.com']
223239
assert task['name'] == _DEFAULT_TASK_PATH
224240

225-
226241
def test_schedule_set_twice_error(self):
227242
_, recorder = self._instrument_functions_service()
228-
opts = functions.TaskOptions(schedule_delay_seconds=100, schedule_time=datetime.utcnow())
243+
opts = functions.TaskOptions(
244+
schedule_delay_seconds=100, schedule_time=datetime.now(timezone.utc))
229245
queue = functions.task_queue('test-function-name')
230246
with pytest.raises(ValueError) as excinfo:
231247
queue.enqueue(_DEFAULT_DATA, opts)
@@ -236,9 +252,9 @@ def test_schedule_set_twice_error(self):
236252

237253
@pytest.mark.parametrize('schedule_time', [
238254
time.time(),
239-
str(datetime.utcnow()),
240-
datetime.utcnow().isoformat(),
241-
datetime.utcnow().isoformat() + 'Z',
255+
str(datetime.now(timezone.utc)),
256+
datetime.now(timezone.utc).isoformat(),
257+
datetime.now(timezone.utc).isoformat() + 'Z',
242258
'', ' '
243259
])
244260
def test_invalid_schedule_time_error(self, schedule_time):

0 commit comments

Comments
 (0)