Skip to content

Commit 4448117

Browse files
google-genai-botcopybara-github
authored andcommitted
fix(test): correct polling timeout unit test in IAM Connector credentials provider
- Correct `test_get_auth_credential_raises_error_if_polling_times_out` to properly execute and verify the `_poll_credentials` while loop and timeout mechanism. - Replace instant client exception and completed operation (`done=True`) with a pending operation (`done=False`), allowing simulated time progression to trigger `TimeoutError`. - Add `test_get_auth_credential_polling_succeeds_before_timeout` to verify the positive path where polling completes successfully after multiple attempts before the timeout is reached. PiperOrigin-RevId: 948377108
1 parent ccbedd8 commit 4448117

1 file changed

Lines changed: 69 additions & 17 deletions

File tree

tests/unittests/integrations/agent_identity/test_iam_connector_credentials_provider.py

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -271,40 +271,40 @@ async def test_get_auth_credential_raises_error_if_upstream_call_fails(
271271
@patch.object(_iam_connector_credentials_provider.time, "time")
272272
async def test_get_auth_credential_raises_error_if_polling_times_out(
273273
mock_time,
274-
mock_operation,
274+
mock_client,
275275
auth_scheme,
276276
context,
277277
provider,
278278
):
279279
"""Test get_auth_credential raises RuntimeError if polling times out."""
280280

281-
# Force the operation into the polling loop state
281+
# 1. Setup the operation metadata to indicate consent is pending
282282
meta_pb = RetrieveCredentialsMetadata.pb()()
283283
meta_pb.consent_pending.SetInParent()
284-
meta = RetrieveCredentialsMetadata.deserialize(meta_pb.SerializeToString())
285-
mock_operation.metadata.value = RetrieveCredentialsMetadata.serialize(meta)
286284

287-
# First call sets start_time=0.0, second call checks time > timeout
288-
# (20.0 > 10.0)
289-
mock_time.side_effect = [0.0, 20.0]
285+
# 2. Keep the operation in the pending state (done=False)
286+
op_pending = Operation(done=False)
287+
op_pending.metadata.value = meta_pb.SerializeToString()
290288

291-
mock_metadata = Mock(spec=RetrieveCredentialsMetadata)
292-
mock_metadata.consent_pending = True
293-
mock_metadata.uri_consent_required = False
294-
mock_operation.done = True
295-
mock_operation.ClearField("error")
296-
mock_client = Mock(spec=Client)
297-
mock_client.retrieve_credentials.side_effect = Exception(
298-
"Timeout waiting for credentials."
299-
)
300-
provider._client = mock_client
289+
# Configure the mock client to repeatedly return this pending operation
290+
mock_client.retrieve_credentials.return_value = Mock(operation=op_pending)
291+
292+
# 3. Simulate the passage of time:
293+
# - 1st time.time() sets start/end time (returns 0.0)
294+
# - 2nd time.time() inside the while-loop check returns a value exceeding the
295+
# NON_INTERACTIVE_TOKEN_POLL_TIMEOUT_SEC timeout (currently 10.0s)
296+
provider_lib = _iam_connector_credentials_provider
297+
timeout = provider_lib.NON_INTERACTIVE_TOKEN_POLL_TIMEOUT_SEC
298+
mock_time.side_effect = [0.0, timeout + 10.0]
301299

300+
# 4. Verify that timeout raises RuntimeError wrapping TimeoutError
302301
with pytest.raises(
303302
RuntimeError,
304303
match="Failed to retrieve credential for user 'user' on connector",
305304
) as exc_info:
306305
await provider.get_auth_credential(auth_scheme, context)
307306

307+
# Assert that the underlying cause was indeed a TimeoutError from polling
308308
assert "Timeout waiting for credentials." in str(exc_info.value.__cause__)
309309

310310

@@ -508,3 +508,55 @@ async def test_get_auth_credential_handles_consent_pending_state_correctly(
508508

509509
# Verify that retrieve_credentials was called twice (initial + 1 poll)
510510
assert mock_client.retrieve_credentials.call_count == 2
511+
512+
513+
@patch.object(_iam_connector_credentials_provider.time, "time")
514+
@patch.object(_iam_connector_credentials_provider.asyncio, "sleep")
515+
async def test_get_auth_credential_polling_succeeds_before_timeout(
516+
mock_sleep,
517+
mock_time,
518+
mock_client,
519+
auth_scheme,
520+
context,
521+
provider,
522+
):
523+
"""Test get_auth_credential returns credential if polling succeeds."""
524+
# 1. Setup the first retrieve_credentials call to return a pending Operation
525+
# containing consent_pending metadata.
526+
meta_pb = RetrieveCredentialsMetadata.pb()()
527+
meta_pb.consent_pending.SetInParent()
528+
529+
op_pending = Operation(done=False)
530+
op_pending.metadata.value = meta_pb.SerializeToString()
531+
532+
# 2. Setup the operation to succeed on a subsequent poll.
533+
op_success = Operation(done=True)
534+
resp = RetrieveCredentialsResponse(
535+
header="Authorization: Bearer", token="valid-token"
536+
)
537+
op_success.response.value = RetrieveCredentialsResponse.serialize(resp)
538+
539+
# Configure mock client to return pending, pending, then success.
540+
mock_client.retrieve_credentials.side_effect = [
541+
Mock(operation=op_pending),
542+
Mock(operation=op_pending),
543+
Mock(operation=op_success),
544+
]
545+
546+
# 3. Simulate the passage of time:
547+
# - 1st time.time() sets end_time (returns 0.0)
548+
# - 2nd time.time() inside loop (returns 0.0, first poll -> pending)
549+
# - 3rd time.time() inside loop (returns timeout / 5.0, 2nd poll -> success)
550+
provider_lib = _iam_connector_credentials_provider
551+
timeout = provider_lib.NON_INTERACTIVE_TOKEN_POLL_TIMEOUT_SEC
552+
mock_time.side_effect = [0.0, 0.0, timeout / 5.0]
553+
554+
# 4. Call the provider and verify.
555+
credential = await provider.get_auth_credential(auth_scheme, context)
556+
557+
assert credential is not None
558+
assert credential.http.credentials.token == "valid-token"
559+
assert mock_client.retrieve_credentials.call_count == 3
560+
mock_sleep.assert_called_once_with(
561+
provider_lib.NON_INTERACTIVE_TOKEN_POLL_INTERVAL_SEC
562+
)

0 commit comments

Comments
 (0)