@@ -110,6 +110,7 @@ def mock_invocation_context(self):
110110 ctx .credential_service = None
111111 ctx .app_name = "test-app"
112112 ctx .user_id = "test-user"
113+ ctx .credential_by_key = {}
113114 return ctx
114115
115116 @pytest .fixture
@@ -154,10 +155,10 @@ async def test_toolset_without_auth_config_skipped(
154155 assert mock_invocation_context .end_invocation is False
155156
156157 @pytest .mark .asyncio
157- async def test_toolset_with_credential_available_populates_config (
158+ async def test_toolset_with_credential_available_populates_context (
158159 self , mock_invocation_context , mock_agent
159160 ):
160- """Test that credential is populated in auth_config when available."""
161+ """Test that credential is stored in invocation context when available."""
161162 auth_config = create_oauth2_auth_config ()
162163 toolset = MockToolset (auth_config = auth_config )
163164 mock_agent .tools = [toolset ]
@@ -184,8 +185,52 @@ async def test_toolset_with_credential_available_populates_config(
184185 # No auth request events - credential was available
185186 assert len (events ) == 0
186187 assert mock_invocation_context .end_invocation is False
187- # Credential should be populated in auth_config
188- assert auth_config .exchanged_auth_credential == mock_credential
188+ # Credential should be stored in invocation context, not auth_config
189+ assert (
190+ mock_invocation_context .credential_by_key [auth_config .credential_key ]
191+ == mock_credential
192+ )
193+ assert auth_config .exchanged_auth_credential is None
194+
195+ @pytest .mark .asyncio
196+ async def test_toolset_auth_uses_copy_and_does_not_mutate_shared_config (
197+ self , mock_invocation_context , mock_agent
198+ ):
199+ """Test that _resolve_toolset_auth uses a copy and does not mutate shared config."""
200+ auth_config = create_oauth2_auth_config ()
201+ toolset = MockToolset (auth_config = auth_config )
202+ mock_agent .tools = [toolset ]
203+
204+ def create_mock_cm (cfg ):
205+ m = AsyncMock ()
206+ m ._auth_config = cfg
207+
208+ async def get_cred (ctx ):
209+ cfg .exchanged_auth_credential = AuthCredential (
210+ auth_type = AuthCredentialTypes .OAUTH2 ,
211+ oauth2 = OAuth2Auth (auth_uri = "https://example.com/consent" ),
212+ )
213+ return None
214+
215+ m .get_auth_credential = AsyncMock (side_effect = get_cred )
216+ return m
217+
218+ with patch (
219+ "google.adk.flows.llm_flows.base_llm_flow.CredentialManager" ,
220+ side_effect = create_mock_cm ,
221+ ):
222+ events = []
223+ async for event in _resolve_toolset_auth (
224+ mock_invocation_context , mock_agent
225+ ):
226+ events .append (event )
227+
228+ # Should yield one auth request event
229+ assert len (events ) == 1
230+ assert mock_invocation_context .end_invocation is True
231+
232+ # The shared auth_config should NOT be mutated
233+ assert auth_config .exchanged_auth_credential is None
189234
190235 @pytest .mark .asyncio
191236 async def test_toolset_without_credential_yields_auth_event (
0 commit comments