From feee571bc8d813a4bc9ea540c0a95177f193f4db Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Thu, 24 Oct 2024 20:24:55 +0200 Subject: [PATCH] Fix time code to work in timezones other than UTC, and add tests in multiple timezones (#810) * Add tests in multiple timezones. * Fix get_epoch_seconds() for timestamps without timezones. * Add changelog fragment. * Pin version for Python 2.6. --- changelogs/fragments/810-time.yml | 2 + plugins/module_utils/time.py | 3 + .../plugins/module_utils/acme/backend_data.py | 18 +- .../acme/test_backend_cryptography.py | 80 +++++--- .../acme/test_backend_openssl_cli.py | 74 +++---- tests/unit/plugins/module_utils/test_time.py | 180 +++++++++++------- tests/unit/requirements.txt | 2 + 7 files changed, 220 insertions(+), 139 deletions(-) create mode 100644 changelogs/fragments/810-time.yml diff --git a/changelogs/fragments/810-time.yml b/changelogs/fragments/810-time.yml new file mode 100644 index 00000000..a270e82d --- /dev/null +++ b/changelogs/fragments/810-time.yml @@ -0,0 +1,2 @@ +bugfixes: + - "time module utils - fix conversion of naive ``datetime`` objects to UNIX timestamps for Python 3 (https://github.com/ansible-collections/community.crypto/issues/808, https://github.com/ansible-collections/community.crypto/pull/810)." diff --git a/plugins/module_utils/time.py b/plugins/module_utils/time.py index 4adc4620..c8f44410 100644 --- a/plugins/module_utils/time.py +++ b/plugins/module_utils/time.py @@ -83,6 +83,9 @@ if sys.version_info < (3, 3): return (delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10**6) / 10**6 else: def get_epoch_seconds(timestamp): + if timestamp.tzinfo is None: + # timestamp.timestamp() is offset by the local timezone if timestamp has no timezone + timestamp = ensure_utc_timezone(timestamp) return timestamp.timestamp() diff --git a/tests/unit/plugins/module_utils/acme/backend_data.py b/tests/unit/plugins/module_utils/acme/backend_data.py index c4aa09a6..cf3e5b93 100644 --- a/tests/unit/plugins/module_utils/acme/backend_data.py +++ b/tests/unit/plugins/module_utils/acme/backend_data.py @@ -20,6 +20,8 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor BackendException, ) +from ..test_time import cartesian_product, TIMEZONES + def load_fixture(name): with open(os.path.join(os.path.dirname(__file__), 'fixtures', name)) as f: @@ -89,11 +91,11 @@ TEST_CERT_OPENSSL_OUTPUT_2 = load_fixture("cert_2.txt") # OpenSSL 3.3.0 output TEST_CERT_OPENSSL_OUTPUT_2B = load_fixture("cert_2-b.txt") # OpenSSL 1.1.1f output -TEST_CERT_DAYS = [ +TEST_CERT_DAYS = cartesian_product(TIMEZONES, [ (datetime.datetime(2018, 11, 15, 1, 2, 3), 11), (datetime.datetime(2018, 11, 25, 15, 20, 0), 1), (datetime.datetime(2018, 11, 25, 15, 30, 0), 0), -] +]) TEST_CERT_INFO = CertificateInformation( @@ -121,7 +123,7 @@ TEST_CERT_INFO = [ ] -TEST_PARSE_ACME_TIMESTAMP = [ +TEST_PARSE_ACME_TIMESTAMP = cartesian_product(TIMEZONES, [ ( '2024-01-01T00:11:22Z', dict(year=2024, month=1, day=1, hour=0, minute=11, second=22), @@ -134,10 +136,10 @@ TEST_PARSE_ACME_TIMESTAMP = [ '2024-04-17T06:54:13.333333334Z', dict(year=2024, month=4, day=17, hour=6, minute=54, second=13, microsecond=333333), ), -] +]) if sys.version_info >= (3, 5): - TEST_PARSE_ACME_TIMESTAMP.extend([ + TEST_PARSE_ACME_TIMESTAMP.extend(cartesian_product(TIMEZONES, [ ( '2024-01-01T00:11:22+0100', dict(year=2023, month=12, day=31, hour=23, minute=11, second=22), @@ -146,10 +148,10 @@ if sys.version_info >= (3, 5): '2024-01-01T00:11:22.123+0100', dict(year=2023, month=12, day=31, hour=23, minute=11, second=22, microsecond=123000), ), - ]) + ])) -TEST_INTERPOLATE_TIMESTAMP = [ +TEST_INTERPOLATE_TIMESTAMP = cartesian_product(TIMEZONES, [ ( dict(year=2024, month=1, day=1, hour=0, minute=0, second=0), dict(year=2024, month=1, day=1, hour=1, minute=0, second=0), @@ -168,7 +170,7 @@ TEST_INTERPOLATE_TIMESTAMP = [ 1.0, dict(year=2024, month=1, day=1, hour=1, minute=0, second=0), ), -] +]) class FakeBackend(CryptoBackend): diff --git a/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py b/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py index 9186e243..dbd5e02f 100644 --- a/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py +++ b/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py @@ -5,11 +5,14 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +import datetime import pytest +from freezegun import freeze_time from ansible_collections.community.crypto.tests.unit.compat.mock import MagicMock +from ansible_collections.community.crypto.plugins.module_utils.time import UTC from ansible_collections.community.crypto.plugins.module_utils.acme.backend_cryptography import ( HAS_CURRENT_CRYPTOGRAPHY, @@ -34,6 +37,8 @@ from .backend_data import ( TEST_INTERPOLATE_TIMESTAMP, ) +from ..test_time import TIMEZONES + if not HAS_CURRENT_CRYPTOGRAPHY: pytest.skip('cryptography not found') @@ -65,16 +70,17 @@ def test_csridentifiers_cryptography(csr, result, openssl_output, tmpdir): assert identifiers == result -@pytest.mark.parametrize("now, expected_days", TEST_CERT_DAYS) -def test_certdays_cryptography(now, expected_days, tmpdir): - fn = tmpdir / 'test-cert.pem' - fn.write(TEST_CERT) - module = MagicMock() - backend = CryptographyBackend(module) - days = backend.get_cert_days(cert_filename=str(fn), now=now) - assert days == expected_days - days = backend.get_cert_days(cert_content=TEST_CERT, now=now) - assert days == expected_days +@pytest.mark.parametrize("timezone, now, expected_days", TEST_CERT_DAYS) +def test_certdays_cryptography(timezone, now, expected_days, tmpdir): + with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): + fn = tmpdir / 'test-cert.pem' + fn.write(TEST_CERT) + module = MagicMock() + backend = CryptographyBackend(module) + days = backend.get_cert_days(cert_filename=str(fn), now=now) + assert days == expected_days + days = backend.get_cert_days(cert_content=TEST_CERT, now=now) + assert days == expected_days @pytest.mark.parametrize("cert_content, expected_cert_info, openssl_output", TEST_CERT_INFO) @@ -96,28 +102,40 @@ def test_get_cert_information(cert_content, expected_cert_info, openssl_output, assert cert_info == expected_cert_info -def test_now(): - module = MagicMock() - backend = CryptographyBackend(module) - now = backend.get_now() - assert CRYPTOGRAPHY_TIMEZONE == (now.tzinfo is not None) +# @pytest.mark.parametrize("timezone", TIMEZONES) +# Due to a bug in freezegun (https://github.com/spulec/freezegun/issues/348, https://github.com/spulec/freezegun/issues/553) +# this only works with timezone = UTC if CRYPTOGRAPHY_TIMEZONE is truish +@pytest.mark.parametrize("timezone", [datetime.timedelta(hours=0)] if CRYPTOGRAPHY_TIMEZONE else TIMEZONES) +def test_now(timezone): + with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): + module = MagicMock() + backend = CryptographyBackend(module) + now = backend.get_now() + if CRYPTOGRAPHY_TIMEZONE: + assert now.tzinfo is not None + assert now == datetime.datetime(2024, 2, 3, 4, 5, 6, tzinfo=UTC) + else: + assert now.tzinfo is None + assert now == datetime.datetime(2024, 2, 3, 4, 5, 6) -@pytest.mark.parametrize("input, expected", TEST_PARSE_ACME_TIMESTAMP) -def test_parse_acme_timestamp(input, expected): - module = MagicMock() - backend = CryptographyBackend(module) - ts_expected = backend.get_utc_datetime(**expected) - timestamp = backend.parse_acme_timestamp(input) - assert ts_expected == timestamp +@pytest.mark.parametrize("timezone, input, expected", TEST_PARSE_ACME_TIMESTAMP) +def test_parse_acme_timestamp(timezone, input, expected): + with freeze_time("2024-02-03 04:05:06 +00:00", tz_offset=timezone): + module = MagicMock() + backend = CryptographyBackend(module) + ts_expected = backend.get_utc_datetime(**expected) + timestamp = backend.parse_acme_timestamp(input) + assert ts_expected == timestamp -@pytest.mark.parametrize("start, end, percentage, expected", TEST_INTERPOLATE_TIMESTAMP) -def test_interpolate_timestamp(start, end, percentage, expected): - module = MagicMock() - backend = CryptographyBackend(module) - ts_start = backend.get_utc_datetime(**start) - ts_end = backend.get_utc_datetime(**end) - ts_expected = backend.get_utc_datetime(**expected) - timestamp = backend.interpolate_timestamp(ts_start, ts_end, percentage) - assert ts_expected == timestamp +@pytest.mark.parametrize("timezone, start, end, percentage, expected", TEST_INTERPOLATE_TIMESTAMP) +def test_interpolate_timestamp(timezone, start, end, percentage, expected): + with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): + module = MagicMock() + backend = CryptographyBackend(module) + ts_start = backend.get_utc_datetime(**start) + ts_end = backend.get_utc_datetime(**end) + ts_expected = backend.get_utc_datetime(**expected) + timestamp = backend.interpolate_timestamp(ts_start, ts_end, percentage) + assert ts_expected == timestamp diff --git a/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py b/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py index 5138a620..8cc1bd82 100644 --- a/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py +++ b/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py @@ -5,8 +5,10 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +import datetime import pytest +from freezegun import freeze_time from ansible_collections.community.crypto.tests.unit.compat.mock import MagicMock @@ -26,6 +28,8 @@ from .backend_data import ( TEST_INTERPOLATE_TIMESTAMP, ) +from ..test_time import TIMEZONES + TEST_IPS = [ ("0:0:0:0:0:0:0:1", "::1"), @@ -69,17 +73,18 @@ def test_normalize_ip(ip, result): assert backend._normalize_ip(ip) == result -@pytest.mark.parametrize("now, expected_days", TEST_CERT_DAYS) -def test_certdays_cryptography(now, expected_days, tmpdir): - fn = tmpdir / 'test-cert.pem' - fn.write(TEST_CERT) - module = MagicMock() - module.run_command = MagicMock(return_value=(0, TEST_CERT_OPENSSL_OUTPUT, 0)) - backend = OpenSSLCLIBackend(module, openssl_binary='openssl') - days = backend.get_cert_days(cert_filename=str(fn), now=now) - assert days == expected_days - days = backend.get_cert_days(cert_content=TEST_CERT, now=now) - assert days == expected_days +@pytest.mark.parametrize("timezone, now, expected_days", TEST_CERT_DAYS) +def test_certdays_cryptography(timezone, now, expected_days, tmpdir): + with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): + fn = tmpdir / 'test-cert.pem' + fn.write(TEST_CERT) + module = MagicMock() + module.run_command = MagicMock(return_value=(0, TEST_CERT_OPENSSL_OUTPUT, 0)) + backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + days = backend.get_cert_days(cert_filename=str(fn), now=now) + assert days == expected_days + days = backend.get_cert_days(cert_content=TEST_CERT, now=now) + assert days == expected_days @pytest.mark.parametrize("cert_content, expected_cert_info, openssl_output", TEST_CERT_INFO) @@ -95,28 +100,33 @@ def test_get_cert_information(cert_content, expected_cert_info, openssl_output, assert cert_info == expected_cert_info -def test_now(): - module = MagicMock() - backend = OpenSSLCLIBackend(module, openssl_binary='openssl') - now = backend.get_now() - assert now.tzinfo is None +@pytest.mark.parametrize("timezone", TIMEZONES) +def test_now(timezone): + with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): + module = MagicMock() + backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + now = backend.get_now() + assert now.tzinfo is None + assert now == datetime.datetime(2024, 2, 3, 4, 5, 6) -@pytest.mark.parametrize("input, expected", TEST_PARSE_ACME_TIMESTAMP) -def test_parse_acme_timestamp(input, expected): - module = MagicMock() - backend = OpenSSLCLIBackend(module, openssl_binary='openssl') - ts_expected = backend.get_utc_datetime(**expected) - timestamp = backend.parse_acme_timestamp(input) - assert ts_expected == timestamp +@pytest.mark.parametrize("timezone, input, expected", TEST_PARSE_ACME_TIMESTAMP) +def test_parse_acme_timestamp(timezone, input, expected): + with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): + module = MagicMock() + backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + ts_expected = backend.get_utc_datetime(**expected) + timestamp = backend.parse_acme_timestamp(input) + assert ts_expected == timestamp -@pytest.mark.parametrize("start, end, percentage, expected", TEST_INTERPOLATE_TIMESTAMP) -def test_interpolate_timestamp(start, end, percentage, expected): - module = MagicMock() - backend = OpenSSLCLIBackend(module, openssl_binary='openssl') - ts_start = backend.get_utc_datetime(**start) - ts_end = backend.get_utc_datetime(**end) - ts_expected = backend.get_utc_datetime(**expected) - timestamp = backend.interpolate_timestamp(ts_start, ts_end, percentage) - assert ts_expected == timestamp +@pytest.mark.parametrize("timezone, start, end, percentage, expected", TEST_INTERPOLATE_TIMESTAMP) +def test_interpolate_timestamp(timezone, start, end, percentage, expected): + with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): + module = MagicMock() + backend = OpenSSLCLIBackend(module, openssl_binary='openssl') + ts_start = backend.get_utc_datetime(**start) + ts_end = backend.get_utc_datetime(**end) + ts_expected = backend.get_utc_datetime(**expected) + timestamp = backend.interpolate_timestamp(ts_start, ts_end, percentage) + assert ts_expected == timestamp diff --git a/tests/unit/plugins/module_utils/test_time.py b/tests/unit/plugins/module_utils/test_time.py index 35a83f4e..0d4f9168 100644 --- a/tests/unit/plugins/module_utils/test_time.py +++ b/tests/unit/plugins/module_utils/test_time.py @@ -10,7 +10,9 @@ import datetime import sys import pytest +from freezegun import freeze_time +from ansible.module_utils.common.collections import is_sequence from ansible_collections.community.crypto.plugins.module_utils.time import ( add_or_remove_timezone, @@ -25,18 +27,42 @@ from ansible_collections.community.crypto.plugins.module_utils.time import ( ) -TEST_REMOVE_TIMEZONE = [ - ( - datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), - datetime.datetime(2024, 1, 1, 0, 1, 2), - ), - ( - datetime.datetime(2024, 1, 1, 0, 1, 2), - datetime.datetime(2024, 1, 1, 0, 1, 2), - ), +TIMEZONES = [ + datetime.timedelta(hours=0), + datetime.timedelta(hours=1), + datetime.timedelta(hours=2), + datetime.timedelta(hours=-6), ] -TEST_UTC_TIMEZONE = [ + +def cartesian_product(list1, list2): + result = [] + for item1 in list1: + if not is_sequence(item1): + item1 = (item1, ) + elif not isinstance(item1, tuple): + item1 = tuple(item1) + for item2 in list2: + if not is_sequence(item2): + item2 = (item2, ) + elif not isinstance(item2, tuple): + item2 = tuple(item2) + result.append(item1 + item2) + return result + + +TEST_REMOVE_TIMEZONE = cartesian_product(TIMEZONES, [ + ( + datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), + datetime.datetime(2024, 1, 1, 0, 1, 2), + ), + ( + datetime.datetime(2024, 1, 1, 0, 1, 2), + datetime.datetime(2024, 1, 1, 0, 1, 2), + ), +]) + +TEST_UTC_TIMEZONE = cartesian_product(TIMEZONES, [ ( datetime.datetime(2024, 1, 1, 0, 1, 2), datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), @@ -45,21 +71,21 @@ TEST_UTC_TIMEZONE = [ datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC), ), -] +]) -TEST_EPOCH_SECONDS = [ +TEST_EPOCH_SECONDS = cartesian_product(TIMEZONES, [ (0, dict(year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=0)), (1E-6, dict(year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=1)), (1E-3, dict(year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=1000)), (3691.2, dict(year=1970, day=1, month=1, hour=1, minute=1, second=31, microsecond=200000)), -] +]) -TEST_EPOCH_TO_SECONDS = [ +TEST_EPOCH_TO_SECONDS = cartesian_product(TIMEZONES, [ (datetime.datetime(1970, 1, 1, 0, 1, 2, 0), 62), (datetime.datetime(1970, 1, 1, 0, 1, 2, 0, tzinfo=UTC), 62), -] +]) -TEST_CONVERT_RELATIVE_TO_DATETIME = [ +TEST_CONVERT_RELATIVE_TO_DATETIME = cartesian_product(TIMEZONES, [ ( '+0', False, @@ -96,9 +122,9 @@ TEST_CONVERT_RELATIVE_TO_DATETIME = [ datetime.datetime(2024, 1, 1, 0, 0, 0), datetime.datetime(2023, 10, 1, 17, 19, 10, tzinfo=UTC), ), -] +]) -TEST_GET_RELATIVE_TIME_OPTION = [ +TEST_GET_RELATIVE_TIME_OPTION = cartesian_product(TIMEZONES, [ ( '+1d2h3m4s', 'foo', @@ -195,28 +221,28 @@ TEST_GET_RELATIVE_TIME_OPTION = [ datetime.datetime(2024, 1, 1, 0, 0, 0), '202401020405Z', ), -] +]) if sys.version_info >= (3, 5): ONE_HOUR_PLUS = datetime.timezone(datetime.timedelta(hours=1)) - TEST_REMOVE_TIMEZONE.extend([ + TEST_REMOVE_TIMEZONE.extend(cartesian_product(TIMEZONES, [ ( datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=ONE_HOUR_PLUS), datetime.datetime(2023, 12, 31, 23, 1, 2), ), - ]) - TEST_UTC_TIMEZONE.extend([ + ])) + TEST_UTC_TIMEZONE.extend(cartesian_product(TIMEZONES, [ ( datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=ONE_HOUR_PLUS), datetime.datetime(2023, 12, 31, 23, 1, 2, tzinfo=UTC), ), - ]) - TEST_EPOCH_TO_SECONDS.extend([ + ])) + TEST_EPOCH_TO_SECONDS.extend(cartesian_product(TIMEZONES, [ (datetime.datetime(1970, 1, 1, 0, 1, 2, 0, tzinfo=ONE_HOUR_PLUS), 62 - 3600), - ]) - TEST_GET_RELATIVE_TIME_OPTION.extend([ + ])) + TEST_GET_RELATIVE_TIME_OPTION.extend(cartesian_product(TIMEZONES, [ ( '20240102040506+0100', 'foo', @@ -265,59 +291,77 @@ if sys.version_info >= (3, 5): datetime.datetime(2024, 1, 1, 0, 0, 0), '202401020405+0100', ), - ]) + ])) -@pytest.mark.parametrize("input, expected", TEST_REMOVE_TIMEZONE) -def test_remove_timezone(input, expected): - output_1 = remove_timezone(input) - assert expected == output_1 - output_2 = add_or_remove_timezone(input, with_timezone=False) - assert expected == output_2 +@pytest.mark.parametrize("timezone, input, expected", TEST_REMOVE_TIMEZONE) +def test_remove_timezone(timezone, input, expected): + with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): + output_1 = remove_timezone(input) + assert expected == output_1 + output_2 = add_or_remove_timezone(input, with_timezone=False) + assert expected == output_2 -@pytest.mark.parametrize("input, expected", TEST_UTC_TIMEZONE) -def test_utc_timezone(input, expected): - output_1 = ensure_utc_timezone(input) - assert expected == output_1 - output_2 = add_or_remove_timezone(input, with_timezone=True) - assert expected == output_2 +@pytest.mark.parametrize("timezone, input, expected", TEST_UTC_TIMEZONE) +def test_utc_timezone(timezone, input, expected): + with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): + output_1 = ensure_utc_timezone(input) + assert expected == output_1 + output_2 = add_or_remove_timezone(input, with_timezone=True) + assert expected == output_2 -def test_get_now_datetime(): - output_1 = get_now_datetime(with_timezone=False) - assert output_1.tzinfo is None - output_2 = get_now_datetime(with_timezone=True) - assert output_2.tzinfo is not None - assert output_2.tzinfo == UTC +# @pytest.mark.parametrize("timezone", TIMEZONES) +# Due to a bug in freezegun (https://github.com/spulec/freezegun/issues/348, https://github.com/spulec/freezegun/issues/553) +# this only works with timezone = UTC +@pytest.mark.parametrize("timezone", [datetime.timedelta(hours=0)]) +def test_get_now_datetime_w_timezone(timezone): + with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): + output_2 = get_now_datetime(with_timezone=True) + assert output_2.tzinfo is not None + assert output_2.tzinfo == UTC + assert output_2 == datetime.datetime(2024, 2, 3, 4, 5, 6, tzinfo=UTC) -@pytest.mark.parametrize("seconds, timestamp", TEST_EPOCH_SECONDS) -def test_epoch_seconds(seconds, timestamp): - ts_wo_tz = datetime.datetime(**timestamp) - assert seconds == get_epoch_seconds(ts_wo_tz) - timestamp_w_tz = dict(timestamp) - timestamp_w_tz['tzinfo'] = UTC - ts_w_tz = datetime.datetime(**timestamp_w_tz) - assert seconds == get_epoch_seconds(ts_w_tz) - output_1 = from_epoch_seconds(seconds, with_timezone=False) - assert ts_wo_tz == output_1 - output_2 = from_epoch_seconds(seconds, with_timezone=True) - assert ts_w_tz == output_2 +@pytest.mark.parametrize("timezone", TIMEZONES) +def test_get_now_datetime_wo_timezone(timezone): + with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): + output_1 = get_now_datetime(with_timezone=False) + assert output_1.tzinfo is None + assert output_1 == datetime.datetime(2024, 2, 3, 4, 5, 6) -@pytest.mark.parametrize("timestamp, expected_seconds", TEST_EPOCH_TO_SECONDS) -def test_epoch_to_seconds(timestamp, expected_seconds): - assert expected_seconds == get_epoch_seconds(timestamp) +@pytest.mark.parametrize("timezone, seconds, timestamp", TEST_EPOCH_SECONDS) +def test_epoch_seconds(timezone, seconds, timestamp): + with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): + ts_wo_tz = datetime.datetime(**timestamp) + assert seconds == get_epoch_seconds(ts_wo_tz) + timestamp_w_tz = dict(timestamp) + timestamp_w_tz['tzinfo'] = UTC + ts_w_tz = datetime.datetime(**timestamp_w_tz) + assert seconds == get_epoch_seconds(ts_w_tz) + output_1 = from_epoch_seconds(seconds, with_timezone=False) + assert ts_wo_tz == output_1 + output_2 = from_epoch_seconds(seconds, with_timezone=True) + assert ts_w_tz == output_2 -@pytest.mark.parametrize("relative_time_string, with_timezone, now, expected", TEST_CONVERT_RELATIVE_TO_DATETIME) -def test_convert_relative_to_datetime(relative_time_string, with_timezone, now, expected): - output = convert_relative_to_datetime(relative_time_string, with_timezone=with_timezone, now=now) - assert expected == output +@pytest.mark.parametrize("timezone, timestamp, expected_seconds", TEST_EPOCH_TO_SECONDS) +def test_epoch_to_seconds(timezone, timestamp, expected_seconds): + with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): + assert expected_seconds == get_epoch_seconds(timestamp) -@pytest.mark.parametrize("input_string, input_name, backend, with_timezone, now, expected", TEST_GET_RELATIVE_TIME_OPTION) -def test_get_relative_time_option(input_string, input_name, backend, with_timezone, now, expected): - output = get_relative_time_option(input_string, input_name, backend=backend, with_timezone=with_timezone, now=now) - assert expected == output +@pytest.mark.parametrize("timezone, relative_time_string, with_timezone, now, expected", TEST_CONVERT_RELATIVE_TO_DATETIME) +def test_convert_relative_to_datetime(timezone, relative_time_string, with_timezone, now, expected): + with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): + output = convert_relative_to_datetime(relative_time_string, with_timezone=with_timezone, now=now) + assert expected == output + + +@pytest.mark.parametrize("timezone, input_string, input_name, backend, with_timezone, now, expected", TEST_GET_RELATIVE_TIME_OPTION) +def test_get_relative_time_option(timezone, input_string, input_name, backend, with_timezone, now, expected): + with freeze_time("2024-02-03 04:05:06", tz_offset=timezone): + output = get_relative_time_option(input_string, input_name, backend=backend, with_timezone=with_timezone, now=now) + assert expected == output diff --git a/tests/unit/requirements.txt b/tests/unit/requirements.txt index 0f2275f1..68e73f69 100644 --- a/tests/unit/requirements.txt +++ b/tests/unit/requirements.txt @@ -7,5 +7,7 @@ cryptography idna ipaddress ; python_version < '3.0' +freezegun == 0.3.10 ; python_version < '2.7' +freezegun ; python_version >= '2.7' unittest2 ; python_version < '2.7' importlib ; python_version < '2.7'