From 1d053dcd5421bfa85021aa98289172705fb754f7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 20:20:51 +0000 Subject: [PATCH 1/3] Fix(tests): Use mock time for consistent token generation and verification tests Patches time.time and google.auth.jwt._helpers.utcnow to use a fixed timestamp (MOCK_CURRENT_TIME) throughout tests/test_token_gen.py. This addresses test flakiness and inconsistencies by ensuring that: 1. Tokens and cookies are generated with predictable `iat` and `exp` claims based on MOCK_CURRENT_TIME. 2. The verification logic within the Firebase Admin SDK and the underlying google-auth library also uses MOCK_CURRENT_TIME. Helper functions _get_id_token and _get_session_cookie were updated to default to using MOCK_CURRENT_TIME for their internal time calculations, simplifying test code. Relevant fixtures and token definitions were updated to rely on these new defaults and the fixed timestamp. The setup_method in TestVerifyIdToken, TestVerifySessionCookie, TestCertificateCaching, and TestCertificateFetchTimeout now mock time.time and google.auth.jwt._helpers.utcnow to ensure that all time-sensitive operations during testing use the MOCK_CURRENT_TIME. --- tests/test_token_gen.py | 87 +++++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 25 deletions(-) diff --git a/tests/test_token_gen.py b/tests/test_token_gen.py index 536a5ec91..87b583641 100644 --- a/tests/test_token_gen.py +++ b/tests/test_token_gen.py @@ -19,11 +19,14 @@ import json import os import time +import unittest.mock +import datetime from google.auth import crypt from google.auth import jwt import google.auth.exceptions import google.oauth2.id_token +from google.auth.jwt import _helpers as jwt_helpers import pytest from pytest_localserver import plugin @@ -36,6 +39,7 @@ from tests import testutils +MOCK_CURRENT_TIME = 1500000000 MOCK_UID = 'user1' MOCK_CREDENTIAL = credentials.Certificate( testutils.resource_filename('service_account.json')) @@ -105,16 +109,17 @@ def verify_custom_token(custom_token, expected_claims, tenant_id=None): for key, value in expected_claims.items(): assert value == token['claims'][key] -def _get_id_token(payload_overrides=None, header_overrides=None): +def _get_id_token(payload_overrides=None, header_overrides=None, current_time=MOCK_CURRENT_TIME): signer = crypt.RSASigner.from_string(MOCK_PRIVATE_KEY) headers = { 'kid': 'mock-key-id-1' } + now = int(current_time if current_time is not None else time.time()) payload = { 'aud': MOCK_CREDENTIAL.project_id, 'iss': 'https://securetoken.google.com/' + MOCK_CREDENTIAL.project_id, - 'iat': int(time.time()) - 100, - 'exp': int(time.time()) + 3600, + 'iat': now - 100, + 'exp': now + 3600, 'sub': '1234567890', 'admin': True, 'firebase': { @@ -127,12 +132,12 @@ def _get_id_token(payload_overrides=None, header_overrides=None): payload = _merge_jwt_claims(payload, payload_overrides) return jwt.encode(signer, payload, header=headers) -def _get_session_cookie(payload_overrides=None, header_overrides=None): +def _get_session_cookie(payload_overrides=None, header_overrides=None, current_time=MOCK_CURRENT_TIME): payload_overrides = payload_overrides or {} if 'iss' not in payload_overrides: payload_overrides['iss'] = 'https://session.firebase.google.com/{0}'.format( MOCK_CREDENTIAL.project_id) - return _get_id_token(payload_overrides, header_overrides) + return _get_id_token(payload_overrides, header_overrides, current_time=current_time) def _instrument_user_manager(app, status, payload): client = auth._get_client(app) @@ -205,7 +210,7 @@ def env_var_app(request): @pytest.fixture(scope='module') def revoked_tokens(): mock_user = json.loads(testutils.resource('get_user.json')) - mock_user['users'][0]['validSince'] = str(int(time.time())+100) + mock_user['users'][0]['validSince'] = str(MOCK_CURRENT_TIME + 100) return json.dumps(mock_user) @pytest.fixture(scope='module') @@ -218,7 +223,7 @@ def user_disabled(): def user_disabled_and_revoked(): mock_user = json.loads(testutils.resource('get_user.json')) mock_user['users'][0]['disabled'] = True - mock_user['users'][0]['validSince'] = str(int(time.time())+100) + mock_user['users'][0]['validSince'] = str(MOCK_CURRENT_TIME + 100) return json.dumps(mock_user) @@ -420,6 +425,17 @@ def test_unexpected_response(self, user_mgt_app): class TestVerifyIdToken: + def setup_method(self, method): + self.time_patch = unittest.mock.patch('time.time', return_value=MOCK_CURRENT_TIME) + self.time_patch.start() + self.utcnow_patch = unittest.mock.patch.object( + jwt_helpers, 'utcnow', return_value=datetime.datetime.utcfromtimestamp(MOCK_CURRENT_TIME)) + self.utcnow_patch.start() + + def teardown_method(self, method): + self.time_patch.stop() + self.utcnow_patch.stop() + valid_tokens = { 'BinaryToken': TEST_ID_TOKEN, 'TextToken': TEST_ID_TOKEN.decode('utf-8'), @@ -435,15 +451,9 @@ class TestVerifyIdToken: 'EmptySubject': _get_id_token({'sub': ''}), 'IntSubject': _get_id_token({'sub': 10}), 'LongStrSubject': _get_id_token({'sub': 'a' * 129}), - 'FutureToken': _get_id_token({'iat': int(time.time()) + 1000}), - 'ExpiredToken': _get_id_token({ - 'iat': int(time.time()) - 10000, - 'exp': int(time.time()) - 3600 - }), - 'ExpiredTokenShort': _get_id_token({ - 'iat': int(time.time()) - 10000, - 'exp': int(time.time()) - 30 - }), + 'FutureToken': _get_id_token({'iat': MOCK_CURRENT_TIME + 1000}), + 'ExpiredToken': _get_id_token({'iat': MOCK_CURRENT_TIME - 10000, 'exp': MOCK_CURRENT_TIME - 3600}), + 'ExpiredTokenShort': _get_id_token({'iat': MOCK_CURRENT_TIME - 10000, 'exp': MOCK_CURRENT_TIME - 30}), 'BadFormatToken': 'foobar' } @@ -618,6 +628,17 @@ def test_certificate_request_failure(self, user_mgt_app): class TestVerifySessionCookie: + def setup_method(self, method): + self.time_patch = unittest.mock.patch('time.time', return_value=MOCK_CURRENT_TIME) + self.time_patch.start() + self.utcnow_patch = unittest.mock.patch.object( + jwt_helpers, 'utcnow', return_value=datetime.datetime.utcfromtimestamp(MOCK_CURRENT_TIME)) + self.utcnow_patch.start() + + def teardown_method(self, method): + self.time_patch.stop() + self.utcnow_patch.stop() + valid_cookies = { 'BinaryCookie': TEST_SESSION_COOKIE, 'TextCookie': TEST_SESSION_COOKIE.decode('utf-8'), @@ -633,15 +654,9 @@ class TestVerifySessionCookie: 'EmptySubject': _get_session_cookie({'sub': ''}), 'IntSubject': _get_session_cookie({'sub': 10}), 'LongStrSubject': _get_session_cookie({'sub': 'a' * 129}), - 'FutureCookie': _get_session_cookie({'iat': int(time.time()) + 1000}), - 'ExpiredCookie': _get_session_cookie({ - 'iat': int(time.time()) - 10000, - 'exp': int(time.time()) - 3600 - }), - 'ExpiredCookieShort': _get_session_cookie({ - 'iat': int(time.time()) - 10000, - 'exp': int(time.time()) - 30 - }), + 'FutureCookie': _get_session_cookie({'iat': MOCK_CURRENT_TIME + 1000}), + 'ExpiredCookie': _get_session_cookie({'iat': MOCK_CURRENT_TIME - 10000, 'exp': MOCK_CURRENT_TIME - 3600}), + 'ExpiredCookieShort': _get_session_cookie({'iat': MOCK_CURRENT_TIME - 10000, 'exp': MOCK_CURRENT_TIME - 30}), 'BadFormatCookie': 'foobar', 'IDToken': TEST_ID_TOKEN, } @@ -792,6 +807,17 @@ def test_certificate_request_failure(self, user_mgt_app): class TestCertificateCaching: + def setup_method(self, method): + self.time_patch = unittest.mock.patch('time.time', return_value=MOCK_CURRENT_TIME) + self.time_patch.start() + self.utcnow_patch = unittest.mock.patch.object( + jwt_helpers, 'utcnow', return_value=datetime.datetime.utcfromtimestamp(MOCK_CURRENT_TIME)) + self.utcnow_patch.start() + + def teardown_method(self, method): + self.time_patch.stop() + self.utcnow_patch.stop() + def test_certificate_caching(self, user_mgt_app, httpserver): httpserver.serve_content(MOCK_PUBLIC_CERTS, 200, headers={'Cache-Control': 'max-age=3600'}) verifier = _token_gen.TokenVerifier(user_mgt_app) @@ -810,6 +836,17 @@ def test_certificate_caching(self, user_mgt_app, httpserver): class TestCertificateFetchTimeout: + def setup_method(self, method): + self.time_patch = unittest.mock.patch('time.time', return_value=MOCK_CURRENT_TIME) + self.time_patch.start() + self.utcnow_patch = unittest.mock.patch.object( + jwt_helpers, 'utcnow', return_value=datetime.datetime.utcfromtimestamp(MOCK_CURRENT_TIME)) + self.utcnow_patch.start() + + def teardown_method(self, method): + self.time_patch.stop() + self.utcnow_patch.stop() + timeout_configs = [ ({'httpTimeout': 4}, 4), ({'httpTimeout': None}, None), From cd346f4941b80284e15f388f48d8c365a333abd6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 20:44:24 +0000 Subject: [PATCH 2/3] Fix(tests): Apply time mocking to test_tenant_mgt.py Extends the time mocking strategy (using a fixed MOCK_CURRENT_TIME) to tests in `tests/test_tenant_mgt.py` to ensure consistency with changes previously made in `tests/test_token_gen.py`. Specifically: - Imported `MOCK_CURRENT_TIME` from `tests.test_token_gen`. - Added `setup_method` (and `teardown_method`) to the `TestVerifyIdToken` and `TestCreateCustomToken` classes. - These setup methods patch `time.time` and `google.auth.jwt._helpers.utcnow` to return `MOCK_CURRENT_TIME` (or its datetime equivalent). This ensures that token generation (for custom tokens) and token verification within `test_tenant_mgt.py` align with the mocked timeline, preventing potential flakiness or failures due to time inconsistencies. All tests in `test_tenant_mgt.py` pass with these changes. --- tests/test_tenant_mgt.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/test_tenant_mgt.py b/tests/test_tenant_mgt.py index 224fdcc16..530256cd9 100644 --- a/tests/test_tenant_mgt.py +++ b/tests/test_tenant_mgt.py @@ -15,6 +15,9 @@ """Test cases for the firebase_admin.tenant_mgt module.""" import json +import time +import datetime +import unittest.mock from urllib import parse import pytest @@ -29,6 +32,9 @@ from firebase_admin import _utils from tests import testutils from tests import test_token_gen +from tests.test_token_gen import MOCK_CURRENT_TIME +# jwt_helpers will be used in mocker.patch.object, if not, the string path is fine. +from google.auth.jwt import _helpers as jwt_helpers GET_TENANT_RESPONSE = """{ @@ -964,6 +970,18 @@ def _assert_saml_provider_config(self, provider_config, want_id='saml.provider') class TestVerifyIdToken: + def setup_method(self, method): + self.time_patch = unittest.mock.patch('time.time', return_value=MOCK_CURRENT_TIME) + self.mock_time = self.time_patch.start() + self.utcnow_patch = unittest.mock.patch.object( + jwt_helpers, 'utcnow', return_value=datetime.datetime.fromtimestamp( + MOCK_CURRENT_TIME, tz=datetime.timezone.utc)) + self.mock_utcnow = self.utcnow_patch.start() + + def teardown_method(self, method): + self.time_patch.stop() + self.utcnow_patch.stop() + def test_valid_token(self, tenant_mgt_app): client = tenant_mgt.auth_for_tenant('test-tenant', app=tenant_mgt_app) client._token_verifier.request = test_token_gen.MOCK_REQUEST @@ -997,6 +1015,18 @@ def tenant_aware_custom_token_app(): class TestCreateCustomToken: + def setup_method(self, method): + self.time_patch = unittest.mock.patch('time.time', return_value=MOCK_CURRENT_TIME) + self.mock_time = self.time_patch.start() + self.utcnow_patch = unittest.mock.patch.object( + jwt_helpers, 'utcnow', return_value=datetime.datetime.fromtimestamp( + MOCK_CURRENT_TIME, tz=datetime.timezone.utc)) + self.mock_utcnow = self.utcnow_patch.start() + + def teardown_method(self, method): + self.time_patch.stop() + self.utcnow_patch.stop() + def test_custom_token(self, tenant_aware_custom_token_app): client = tenant_mgt.auth_for_tenant('test-tenant', app=tenant_aware_custom_token_app) From 997c5a3bb79759e87f2a9190e37821c3b0dcb881 Mon Sep 17 00:00:00 2001 From: jonathanedey Date: Mon, 26 May 2025 17:33:01 -0400 Subject: [PATCH 3/3] fix lint and refactor --- tests/test_tenant_mgt.py | 24 ++++++--------- tests/test_token_gen.py | 63 +++++++++++++++++++++++----------------- 2 files changed, 46 insertions(+), 41 deletions(-) diff --git a/tests/test_tenant_mgt.py b/tests/test_tenant_mgt.py index 530256cd9..018892e3a 100644 --- a/tests/test_tenant_mgt.py +++ b/tests/test_tenant_mgt.py @@ -15,8 +15,6 @@ """Test cases for the firebase_admin.tenant_mgt module.""" import json -import time -import datetime import unittest.mock from urllib import parse @@ -32,9 +30,7 @@ from firebase_admin import _utils from tests import testutils from tests import test_token_gen -from tests.test_token_gen import MOCK_CURRENT_TIME -# jwt_helpers will be used in mocker.patch.object, if not, the string path is fine. -from google.auth.jwt import _helpers as jwt_helpers +from tests.test_token_gen import MOCK_CURRENT_TIME, MOCK_CURRENT_TIME_UTC GET_TENANT_RESPONSE = """{ @@ -970,15 +966,14 @@ def _assert_saml_provider_config(self, provider_config, want_id='saml.provider') class TestVerifyIdToken: - def setup_method(self, method): + def setup_method(self): self.time_patch = unittest.mock.patch('time.time', return_value=MOCK_CURRENT_TIME) self.mock_time = self.time_patch.start() - self.utcnow_patch = unittest.mock.patch.object( - jwt_helpers, 'utcnow', return_value=datetime.datetime.fromtimestamp( - MOCK_CURRENT_TIME, tz=datetime.timezone.utc)) + self.utcnow_patch = unittest.mock.patch( + 'google.auth.jwt._helpers.utcnow', return_value=MOCK_CURRENT_TIME_UTC) self.mock_utcnow = self.utcnow_patch.start() - def teardown_method(self, method): + def teardown_method(self): self.time_patch.stop() self.utcnow_patch.stop() @@ -1015,15 +1010,14 @@ def tenant_aware_custom_token_app(): class TestCreateCustomToken: - def setup_method(self, method): + def setup_method(self): self.time_patch = unittest.mock.patch('time.time', return_value=MOCK_CURRENT_TIME) self.mock_time = self.time_patch.start() - self.utcnow_patch = unittest.mock.patch.object( - jwt_helpers, 'utcnow', return_value=datetime.datetime.fromtimestamp( - MOCK_CURRENT_TIME, tz=datetime.timezone.utc)) + self.utcnow_patch = unittest.mock.patch( + 'google.auth.jwt._helpers.utcnow', return_value=MOCK_CURRENT_TIME_UTC) self.mock_utcnow = self.utcnow_patch.start() - def teardown_method(self, method): + def teardown_method(self): self.time_patch.stop() self.utcnow_patch.stop() diff --git a/tests/test_token_gen.py b/tests/test_token_gen.py index 87b583641..fe0b28dbe 100644 --- a/tests/test_token_gen.py +++ b/tests/test_token_gen.py @@ -20,13 +20,11 @@ import os import time import unittest.mock -import datetime from google.auth import crypt from google.auth import jwt import google.auth.exceptions import google.oauth2.id_token -from google.auth.jwt import _helpers as jwt_helpers import pytest from pytest_localserver import plugin @@ -40,6 +38,8 @@ MOCK_CURRENT_TIME = 1500000000 +MOCK_CURRENT_TIME_UTC = datetime.datetime.fromtimestamp( + MOCK_CURRENT_TIME, tz=datetime.timezone.utc) MOCK_UID = 'user1' MOCK_CREDENTIAL = credentials.Certificate( testutils.resource_filename('service_account.json')) @@ -132,7 +132,8 @@ def _get_id_token(payload_overrides=None, header_overrides=None, current_time=MO payload = _merge_jwt_claims(payload, payload_overrides) return jwt.encode(signer, payload, header=headers) -def _get_session_cookie(payload_overrides=None, header_overrides=None, current_time=MOCK_CURRENT_TIME): +def _get_session_cookie( + payload_overrides=None, header_overrides=None, current_time=MOCK_CURRENT_TIME): payload_overrides = payload_overrides or {} if 'iss' not in payload_overrides: payload_overrides['iss'] = 'https://session.firebase.google.com/{0}'.format( @@ -425,14 +426,14 @@ def test_unexpected_response(self, user_mgt_app): class TestVerifyIdToken: - def setup_method(self, method): + def setup_method(self): self.time_patch = unittest.mock.patch('time.time', return_value=MOCK_CURRENT_TIME) self.time_patch.start() - self.utcnow_patch = unittest.mock.patch.object( - jwt_helpers, 'utcnow', return_value=datetime.datetime.utcfromtimestamp(MOCK_CURRENT_TIME)) + self.utcnow_patch = unittest.mock.patch( + 'google.auth.jwt._helpers.utcnow', return_value=MOCK_CURRENT_TIME_UTC) self.utcnow_patch.start() - def teardown_method(self, method): + def teardown_method(self): self.time_patch.stop() self.utcnow_patch.stop() @@ -452,8 +453,14 @@ def teardown_method(self, method): 'IntSubject': _get_id_token({'sub': 10}), 'LongStrSubject': _get_id_token({'sub': 'a' * 129}), 'FutureToken': _get_id_token({'iat': MOCK_CURRENT_TIME + 1000}), - 'ExpiredToken': _get_id_token({'iat': MOCK_CURRENT_TIME - 10000, 'exp': MOCK_CURRENT_TIME - 3600}), - 'ExpiredTokenShort': _get_id_token({'iat': MOCK_CURRENT_TIME - 10000, 'exp': MOCK_CURRENT_TIME - 30}), + 'ExpiredToken': _get_id_token({ + 'iat': MOCK_CURRENT_TIME - 10000, + 'exp': MOCK_CURRENT_TIME - 3600 + }), + 'ExpiredTokenShort': _get_id_token({ + 'iat': MOCK_CURRENT_TIME - 10000, + 'exp': MOCK_CURRENT_TIME - 30 + }), 'BadFormatToken': 'foobar' } @@ -628,14 +635,14 @@ def test_certificate_request_failure(self, user_mgt_app): class TestVerifySessionCookie: - def setup_method(self, method): + def setup_method(self): self.time_patch = unittest.mock.patch('time.time', return_value=MOCK_CURRENT_TIME) self.time_patch.start() - self.utcnow_patch = unittest.mock.patch.object( - jwt_helpers, 'utcnow', return_value=datetime.datetime.utcfromtimestamp(MOCK_CURRENT_TIME)) + self.utcnow_patch = unittest.mock.patch( + 'google.auth.jwt._helpers.utcnow', return_value=MOCK_CURRENT_TIME_UTC) self.utcnow_patch.start() - def teardown_method(self, method): + def teardown_method(self): self.time_patch.stop() self.utcnow_patch.stop() @@ -655,8 +662,14 @@ def teardown_method(self, method): 'IntSubject': _get_session_cookie({'sub': 10}), 'LongStrSubject': _get_session_cookie({'sub': 'a' * 129}), 'FutureCookie': _get_session_cookie({'iat': MOCK_CURRENT_TIME + 1000}), - 'ExpiredCookie': _get_session_cookie({'iat': MOCK_CURRENT_TIME - 10000, 'exp': MOCK_CURRENT_TIME - 3600}), - 'ExpiredCookieShort': _get_session_cookie({'iat': MOCK_CURRENT_TIME - 10000, 'exp': MOCK_CURRENT_TIME - 30}), + 'ExpiredCookie': _get_session_cookie({ + 'iat': MOCK_CURRENT_TIME - 10000, + 'exp': MOCK_CURRENT_TIME - 3600 + }), + 'ExpiredCookieShort': _get_session_cookie({ + 'iat': MOCK_CURRENT_TIME - 10000, + 'exp': MOCK_CURRENT_TIME - 30 + }), 'BadFormatCookie': 'foobar', 'IDToken': TEST_ID_TOKEN, } @@ -807,14 +820,14 @@ def test_certificate_request_failure(self, user_mgt_app): class TestCertificateCaching: - def setup_method(self, method): + def setup_method(self): self.time_patch = unittest.mock.patch('time.time', return_value=MOCK_CURRENT_TIME) self.time_patch.start() - self.utcnow_patch = unittest.mock.patch.object( - jwt_helpers, 'utcnow', return_value=datetime.datetime.utcfromtimestamp(MOCK_CURRENT_TIME)) + self.utcnow_patch = unittest.mock.patch( + 'google.auth.jwt._helpers.utcnow', return_value=MOCK_CURRENT_TIME_UTC) self.utcnow_patch.start() - def teardown_method(self, method): + def teardown_method(self): self.time_patch.stop() self.utcnow_patch.stop() @@ -836,16 +849,17 @@ def test_certificate_caching(self, user_mgt_app, httpserver): class TestCertificateFetchTimeout: - def setup_method(self, method): + def setup_method(self): self.time_patch = unittest.mock.patch('time.time', return_value=MOCK_CURRENT_TIME) self.time_patch.start() - self.utcnow_patch = unittest.mock.patch.object( - jwt_helpers, 'utcnow', return_value=datetime.datetime.utcfromtimestamp(MOCK_CURRENT_TIME)) + self.utcnow_patch = unittest.mock.patch( + 'google.auth.jwt._helpers.utcnow', return_value=MOCK_CURRENT_TIME_UTC) self.utcnow_patch.start() - def teardown_method(self, method): + def teardown_method(self): self.time_patch.stop() self.utcnow_patch.stop() + testutils.cleanup_apps() timeout_configs = [ ({'httpTimeout': 4}, 4), @@ -889,6 +903,3 @@ def _instrument_session(self, app): recorder = [] request.session.mount('https://', testutils.MockAdapter(MOCK_PUBLIC_CERTS, 200, recorder)) return recorder - - def teardown_method(self): - testutils.cleanup_apps()