@@ -196,7 +196,7 @@ def test_resolve_subscription_bearer_token_skips_invalid_generic_token(
196196 monkeypatch .setattr (
197197 copilot_auth ,
198198 "iter_oauth_token_candidates" ,
199- lambda : [
199+ lambda ** _kwargs : [
200200 copilot_auth .CopilotTokenCandidate (
201201 token = "ghp-generic" ,
202202 source = "env:GITHUB_TOKEN" ,
@@ -233,7 +233,7 @@ def test_resolve_subscription_bearer_token_does_not_fallback_to_unexchanged_oaut
233233 monkeypatch .setattr (
234234 copilot_auth ,
235235 "iter_oauth_token_candidates" ,
236- lambda : [
236+ lambda ** _kwargs : [
237237 copilot_auth .CopilotTokenCandidate (
238238 token = "gho-copilot" ,
239239 source = "macos-keychain:copilot-cli" ,
@@ -254,6 +254,94 @@ def test_resolve_subscription_bearer_token_does_not_fallback_to_unexchanged_oaut
254254 assert copilot_auth .resolve_subscription_bearer_token () is None
255255
256256
257+ def test_resolve_subscription_bearer_token_defers_keychain_when_saved_token_resolves (
258+ monkeypatch : pytest .MonkeyPatch ,
259+ ) -> None :
260+ copilot_auth .save_headroom_copilot_oauth_token ("gho-saved" )
261+ monkeypatch .setattr (copilot_auth , "_read_windows_copilot_cli_oauth_token" , lambda : None )
262+ monkeypatch .setattr (
263+ copilot_auth ,
264+ "_read_macos_keychain_oauth_token" ,
265+ lambda : pytest .fail ("Keychain must not be read when saved OAuth resolves" ),
266+ )
267+ monkeypatch .setattr (copilot_auth , "_read_linux_secret_oauth_token" , lambda : None )
268+ monkeypatch .setattr (copilot_auth , "_read_file_oauth_token_candidates" , lambda : [])
269+ monkeypatch .setattr (copilot_auth , "_read_gh_cli_oauth_token" , lambda : None )
270+ monkeypatch .setattr (
271+ copilot_auth ,
272+ "_subscription_resolution_from_token_exchange" ,
273+ lambda candidate : (
274+ copilot_auth ._subscription_resolution (
275+ token = "copilot-api" ,
276+ source = f"{ candidate .source } :token-exchange" ,
277+ confidence = "copilot-token-exchange" ,
278+ api_url = copilot_auth .DEFAULT_API_URL ,
279+ refresh_oauth_token = candidate .token ,
280+ )
281+ if candidate .token == "gho-saved"
282+ else None
283+ ),
284+ )
285+
286+ assert copilot_auth .resolve_subscription_bearer_token () == "copilot-api"
287+
288+
289+ def test_resolve_subscription_bearer_token_reads_keychain_after_noninteractive_rejection (
290+ monkeypatch : pytest .MonkeyPatch ,
291+ ) -> None :
292+ copilot_auth .save_headroom_copilot_oauth_token ("gho-rejected" )
293+ monkeypatch .setattr (copilot_auth , "_read_windows_copilot_cli_oauth_token" , lambda : None )
294+ monkeypatch .setattr (
295+ copilot_auth ,
296+ "_read_macos_keychain_oauth_token" ,
297+ lambda : "gho-keychain" ,
298+ )
299+ monkeypatch .setattr (copilot_auth , "_read_linux_secret_oauth_token" , lambda : None )
300+ monkeypatch .setattr (copilot_auth , "_read_file_oauth_token_candidates" , lambda : [])
301+ monkeypatch .setattr (copilot_auth , "_read_gh_cli_oauth_token" , lambda : None )
302+ monkeypatch .setattr (
303+ copilot_auth ,
304+ "_subscription_resolution_from_token_exchange" ,
305+ lambda candidate : (
306+ copilot_auth ._subscription_resolution (
307+ token = "copilot-api" ,
308+ source = f"{ candidate .source } :token-exchange" ,
309+ confidence = "copilot-token-exchange" ,
310+ api_url = copilot_auth .DEFAULT_API_URL ,
311+ refresh_oauth_token = candidate .token ,
312+ )
313+ if candidate .token == "gho-keychain"
314+ else None
315+ ),
316+ )
317+
318+ assert copilot_auth .resolve_subscription_bearer_token () == "copilot-api"
319+
320+
321+ def test_resolve_subscription_bearer_token_skips_duplicate_keychain_candidate (
322+ monkeypatch : pytest .MonkeyPatch ,
323+ ) -> None :
324+ copilot_auth .save_headroom_copilot_oauth_token ("gho-duplicate" )
325+ monkeypatch .setattr (copilot_auth , "_read_windows_copilot_cli_oauth_token" , lambda : None )
326+ monkeypatch .setattr (
327+ copilot_auth ,
328+ "_read_macos_keychain_oauth_token" ,
329+ lambda : "gho-duplicate" ,
330+ )
331+ monkeypatch .setattr (copilot_auth , "_read_linux_secret_oauth_token" , lambda : None )
332+ monkeypatch .setattr (copilot_auth , "_read_file_oauth_token_candidates" , lambda : [])
333+ monkeypatch .setattr (copilot_auth , "_read_gh_cli_oauth_token" , lambda : None )
334+ attempted_tokens : list [str ] = []
335+ monkeypatch .setattr (
336+ copilot_auth ,
337+ "_subscription_resolution_from_token_exchange" ,
338+ lambda candidate : attempted_tokens .append (candidate .token ) or None ,
339+ )
340+
341+ assert copilot_auth .resolve_subscription_bearer_token () is None
342+ assert attempted_tokens == ["gho-duplicate" ]
343+
344+
257345def test_subscription_enterprise_host_repro (
258346 monkeypatch : pytest .MonkeyPatch ,
259347) -> None :
@@ -268,7 +356,7 @@ def test_subscription_enterprise_host_repro(
268356 monkeypatch .setattr (
269357 copilot_auth ,
270358 "iter_oauth_token_candidates" ,
271- lambda : [
359+ lambda ** _kwargs : [
272360 copilot_auth .CopilotTokenCandidate (
273361 token = "gho-oauth" ,
274362 source = "headroom-copilot-auth:/tmp/copilot_auth.json" ,
@@ -324,7 +412,7 @@ def test_resolve_subscription_exchange_uses_cloud_enterprise_advertised_api(
324412 monkeypatch .setattr (
325413 copilot_auth ,
326414 "iter_oauth_token_candidates" ,
327- lambda : [
415+ lambda ** _kwargs : [
328416 copilot_auth .CopilotTokenCandidate (
329417 token = "gho-oauth" ,
330418 source = "env:GITHUB_COPILOT_TOKEN" ,
@@ -391,7 +479,7 @@ def _resolve_subscription_producer_path(
391479 patch .setattr (
392480 copilot_auth ,
393481 "iter_oauth_token_candidates" ,
394- lambda : [
482+ lambda ** _kwargs : [
395483 copilot_auth .CopilotTokenCandidate (
396484 token = "gho-oauth" , source = "test" , confidence = "test"
397485 )
@@ -409,7 +497,7 @@ def _resolve_subscription_producer_path(
409497 patch .setattr (
410498 copilot_auth ,
411499 "iter_oauth_token_candidates" ,
412- lambda : [
500+ lambda ** _kwargs : [
413501 copilot_auth .CopilotTokenCandidate (
414502 token = "tid_api" , source = "test" , confidence = "test"
415503 )
@@ -1565,3 +1653,57 @@ def __exit__(self, *args):
15651653 )
15661654
15671655 assert result == payload
1656+
1657+
1658+ def test_iter_oauth_token_candidates_includes_linux_secret_service_token (
1659+ monkeypatch : pytest .MonkeyPatch ,
1660+ ) -> None :
1661+ monkeypatch .delenv ("GITHUB_COPILOT_GITHUB_TOKEN" , raising = False )
1662+ monkeypatch .delenv ("GITHUB_COPILOT_TOKEN" , raising = False )
1663+ monkeypatch .delenv ("COPILOT_GITHUB_TOKEN" , raising = False )
1664+ monkeypatch .delenv ("GITHUB_TOKEN" , raising = False )
1665+ monkeypatch .delenv ("GH_TOKEN" , raising = False )
1666+ monkeypatch .setattr (copilot_auth , "_read_windows_copilot_cli_oauth_token" , lambda : None )
1667+ monkeypatch .setattr (copilot_auth , "_read_macos_keychain_oauth_token" , lambda : None )
1668+ monkeypatch .setattr (copilot_auth , "_read_linux_secret_oauth_token" , lambda : "gho-linux" )
1669+ monkeypatch .setattr (copilot_auth , "_read_file_oauth_token_candidates" , lambda : [])
1670+ monkeypatch .setattr (copilot_auth , "_read_gh_cli_oauth_token" , lambda : None )
1671+
1672+ candidates = copilot_auth .iter_oauth_token_candidates ()
1673+
1674+ assert [(candidate .source , candidate .token ) for candidate in candidates ] == [
1675+ ("linux-secret-service:copilot-cli" , "gho-linux" ),
1676+ ]
1677+
1678+
1679+ def test_iter_oauth_token_candidates_skips_platform_secret_stores_when_disabled (
1680+ monkeypatch : pytest .MonkeyPatch ,
1681+ ) -> None :
1682+ monkeypatch .delenv ("GITHUB_COPILOT_GITHUB_TOKEN" , raising = False )
1683+ monkeypatch .delenv ("GITHUB_COPILOT_TOKEN" , raising = False )
1684+ monkeypatch .delenv ("COPILOT_GITHUB_TOKEN" , raising = False )
1685+ monkeypatch .delenv ("GITHUB_TOKEN" , raising = False )
1686+ monkeypatch .delenv ("GH_TOKEN" , raising = False )
1687+ monkeypatch .setattr (copilot_auth , "_read_windows_copilot_cli_oauth_token" , lambda : None )
1688+ monkeypatch .setattr (
1689+ copilot_auth ,
1690+ "_platform_secret_store_oauth_token_candidates" ,
1691+ lambda : pytest .fail ("platform secret stores should not be read" ),
1692+ )
1693+ monkeypatch .setattr (copilot_auth , "_read_file_oauth_token_candidates" , lambda : [])
1694+ monkeypatch .setattr (copilot_auth , "_read_gh_cli_oauth_token" , lambda : None )
1695+
1696+ assert copilot_auth .iter_oauth_token_candidates (include_platform_secret_stores = False ) == []
1697+
1698+
1699+ def test_platform_secret_store_candidates_include_macos_keychain_token (
1700+ monkeypatch : pytest .MonkeyPatch ,
1701+ ) -> None :
1702+ monkeypatch .setattr (copilot_auth , "_read_macos_keychain_oauth_token" , lambda : "gho-macos" )
1703+ monkeypatch .setattr (copilot_auth , "_read_linux_secret_oauth_token" , lambda : None )
1704+
1705+ candidates = copilot_auth ._platform_secret_store_oauth_token_candidates ()
1706+
1707+ assert [(candidate .source , candidate .token ) for candidate in candidates ] == [
1708+ ("macos-keychain:copilot-cli" , "gho-macos" ),
1709+ ]
0 commit comments