From b6af7b75ac0530bb1f8223de32aee132593f77c7 Mon Sep 17 00:00:00 2001 From: Mitchell McKenzie Date: Thu, 10 Sep 2026 15:54:18 -0400 Subject: [PATCH 1/4] (fix) update handling of NotImplementedError to ensure a retry is signal properly --- .../google/auth/aio/transport/sessions.py | 1 - .../tests/transport/aio/test_sessions_mtls.py | 49 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/google/auth/aio/transport/sessions.py b/packages/google-auth/google/auth/aio/transport/sessions.py index f2ced1280e50..d67bf71e0a49 100644 --- a/packages/google-auth/google/auth/aio/transport/sessions.py +++ b/packages/google-auth/google/auth/aio/transport/sessions.py @@ -485,7 +485,6 @@ async def _recover_auth_state(): _LOGGER.debug( "Credentials do not implement refresh()." ) - return response except ( exceptions.RefreshError, getattr(exceptions, "InvalidOperation", Exception), diff --git a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py index f6f1185a660e..f15aca7fcf45 100644 --- a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py +++ b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py @@ -1126,3 +1126,52 @@ async def slow_mtls_init(): assert not session._mtls_init_task.cancelled() assert session._is_mtls is True await session.close() + + @pytest.mark.asyncio + async def test_cert_rotation_credential_refresh_not_implemented_retries(self): + """Validate credentials that raise NotImplementedError on refresh() + still trigger a retry after mTLS reconfiguration, not return the 401.""" + mock_creds = mock.AsyncMock(spec=credentials.Credentials) + mock_creds.before_request = mock.AsyncMock(return_value=None) + mock_creds.refresh = mock.AsyncMock(side_effect=NotImplementedError) + + mock_resp_401 = mock.Mock() + mock_resp_401.status_code = http_client.UNAUTHORIZED + mock_resp_401.close = mock.AsyncMock() + + mock_resp_200 = mock.Mock() + mock_resp_200.status_code = http_client.OK + mock_resp_200.close = mock.AsyncMock() + + mock_auth_req = mock.AsyncMock(side_effect=[mock_resp_401, mock_resp_200]) + + session = sessions.AsyncAuthorizedSession( + mock_creds, auth_request=mock_auth_req + ) + session._is_mtls = True + session._cached_cert = b"old_cert" + + with ( + mock.patch( + "google.auth.aio.transport.mtls.check_parameters_for_unauthorized_response", + new_callable=mock.AsyncMock, + ) as mock_check, + mock.patch.object( + session, "configure_mtls_channel", new_callable=mock.AsyncMock + ) as mock_conf, + ): + mock_check.return_value = (b"new_cert", b"new_key", b"old_fp", b"new_fp") + + resp = await session.request( + "GET", "https://pubsub.mtls.googleapis.com/test" + ) + + # Validate that the handler falls through to `return None` + # on NotImplementedError in order to signal retry. + assert resp == mock_resp_200 + mock_conf.assert_called_once() + mock_creds.refresh.assert_called_once() + assert mock_auth_req.call_count == 2 + mock_resp_401.close.assert_called_once() + + await session.close() From 938571eb77035f93ed51651eefe7eb8dd12eec84 Mon Sep 17 00:00:00 2001 From: Mitchell McKenzie Date: Thu, 10 Sep 2026 15:54:18 -0400 Subject: [PATCH 2/4] (fix) updating the tests to use with instread of old syntax --- .../tests/transport/aio/test_sessions_mtls.py | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py index f15aca7fcf45..bb6b0bbf5066 100644 --- a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py +++ b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py @@ -1151,27 +1151,25 @@ async def test_cert_rotation_credential_refresh_not_implemented_retries(self): session._is_mtls = True session._cached_cert = b"old_cert" - with ( - mock.patch( - "google.auth.aio.transport.mtls.check_parameters_for_unauthorized_response", - new_callable=mock.AsyncMock, - ) as mock_check, - mock.patch.object( + with mock.patch( + "google.auth.aio.transport.mtls.check_parameters_for_unauthorized_response", + new_callable=mock.AsyncMock, + ) as mock_check: + with mock.patch.object( session, "configure_mtls_channel", new_callable=mock.AsyncMock - ) as mock_conf, - ): - mock_check.return_value = (b"new_cert", b"new_key", b"old_fp", b"new_fp") + ) as mock_conf: + mock_check.return_value = (b"new_cert", b"new_key", b"old_fp", b"new_fp") - resp = await session.request( - "GET", "https://pubsub.mtls.googleapis.com/test" - ) + resp = await session.request( + "GET", "https://pubsub.mtls.googleapis.com/test" + ) - # Validate that the handler falls through to `return None` - # on NotImplementedError in order to signal retry. - assert resp == mock_resp_200 - mock_conf.assert_called_once() - mock_creds.refresh.assert_called_once() - assert mock_auth_req.call_count == 2 - mock_resp_401.close.assert_called_once() + # Validate that the handler falls through to `return None` + # on NotImplementedError in order to signal retry. + assert resp == mock_resp_200 + mock_conf.assert_called_once() + mock_creds.refresh.assert_called_once() + assert mock_auth_req.call_count == 2 + mock_resp_401.close.assert_called_once() await session.close() From b6f93273842fa101234e3d2a4ca0200cf1770d91 Mon Sep 17 00:00:00 2001 From: Mitchell McKenzie Date: Thu, 10 Sep 2026 15:54:18 -0400 Subject: [PATCH 3/4] (fix) lint line length correction --- .../google-auth/tests/transport/aio/test_sessions_mtls.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py index bb6b0bbf5066..9dd3b60f1e13 100644 --- a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py +++ b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py @@ -1158,7 +1158,12 @@ async def test_cert_rotation_credential_refresh_not_implemented_retries(self): with mock.patch.object( session, "configure_mtls_channel", new_callable=mock.AsyncMock ) as mock_conf: - mock_check.return_value = (b"new_cert", b"new_key", b"old_fp", b"new_fp") + mock_check.return_value = ( + b"new_cert", + b"new_key", + b"old_fp", + b"new_fp", + ) resp = await session.request( "GET", "https://pubsub.mtls.googleapis.com/test" From c49b9786be839c7d606f830b9a8cf8d7d8e1838b Mon Sep 17 00:00:00 2001 From: Mitchell McKenzie Date: Fri, 11 Sep 2026 08:51:29 -0400 Subject: [PATCH 4/4] test(auth): assert reconfigure callback and refresh args in NotImplementedError retry test --- .../google-auth/tests/transport/aio/test_sessions_mtls.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py index 9dd3b60f1e13..2d6e558ae9bf 100644 --- a/packages/google-auth/tests/transport/aio/test_sessions_mtls.py +++ b/packages/google-auth/tests/transport/aio/test_sessions_mtls.py @@ -1173,7 +1173,13 @@ async def test_cert_rotation_credential_refresh_not_implemented_retries(self): # on NotImplementedError in order to signal retry. assert resp == mock_resp_200 mock_conf.assert_called_once() - mock_creds.refresh.assert_called_once() + cb = ( + mock_conf.call_args.args[0] + if mock_conf.call_args.args + else mock_conf.call_args.kwargs["client_cert_callback"] + ) + assert cb() == (b"new_cert", b"new_key") + mock_creds.refresh.assert_called_once_with(mock_auth_req) assert mock_auth_req.call_count == 2 mock_resp_401.close.assert_called_once()