@@ -232,7 +232,7 @@ def test_resolve_subscription_bearer_token_skips_invalid_generic_token(
232232 monkeypatch .setattr (
233233 copilot_auth ,
234234 "iter_oauth_token_candidates" ,
235- lambda : [
235+ lambda ** _kwargs : [
236236 copilot_auth .CopilotTokenCandidate (
237237 token = "ghp-generic" ,
238238 source = "env:GITHUB_TOKEN" ,
@@ -269,7 +269,7 @@ def test_resolve_subscription_bearer_token_does_not_fallback_to_unexchanged_oaut
269269 monkeypatch .setattr (
270270 copilot_auth ,
271271 "iter_oauth_token_candidates" ,
272- lambda : [
272+ lambda ** _kwargs : [
273273 copilot_auth .CopilotTokenCandidate (
274274 token = "gho-copilot" ,
275275 source = "macos-keychain:copilot-cli" ,
@@ -290,6 +290,94 @@ def test_resolve_subscription_bearer_token_does_not_fallback_to_unexchanged_oaut
290290 assert copilot_auth .resolve_subscription_bearer_token () is None
291291
292292
293+ def test_resolve_subscription_bearer_token_defers_keychain_when_saved_token_resolves (
294+ monkeypatch : pytest .MonkeyPatch ,
295+ ) -> None :
296+ copilot_auth .save_headroom_copilot_oauth_token ("gho-saved" )
297+ monkeypatch .setattr (copilot_auth , "_read_windows_copilot_cli_oauth_token" , lambda : None )
298+ monkeypatch .setattr (
299+ copilot_auth ,
300+ "_read_macos_keychain_oauth_token" ,
301+ lambda : pytest .fail ("Keychain must not be read when saved OAuth resolves" ),
302+ )
303+ monkeypatch .setattr (copilot_auth , "_read_linux_secret_oauth_token" , lambda : None )
304+ monkeypatch .setattr (copilot_auth , "_read_file_oauth_token_candidates" , lambda : [])
305+ monkeypatch .setattr (copilot_auth , "_read_gh_cli_oauth_token" , lambda : None )
306+ monkeypatch .setattr (
307+ copilot_auth ,
308+ "_subscription_resolution_from_token_exchange" ,
309+ lambda candidate : (
310+ copilot_auth ._subscription_resolution (
311+ token = "copilot-api" ,
312+ source = f"{ candidate .source } :token-exchange" ,
313+ confidence = "copilot-token-exchange" ,
314+ api_url = copilot_auth .DEFAULT_API_URL ,
315+ refresh_oauth_token = candidate .token ,
316+ )
317+ if candidate .token == "gho-saved"
318+ else None
319+ ),
320+ )
321+
322+ assert copilot_auth .resolve_subscription_bearer_token () == "copilot-api"
323+
324+
325+ def test_resolve_subscription_bearer_token_reads_keychain_after_noninteractive_rejection (
326+ monkeypatch : pytest .MonkeyPatch ,
327+ ) -> None :
328+ copilot_auth .save_headroom_copilot_oauth_token ("gho-rejected" )
329+ monkeypatch .setattr (copilot_auth , "_read_windows_copilot_cli_oauth_token" , lambda : None )
330+ monkeypatch .setattr (
331+ copilot_auth ,
332+ "_read_macos_keychain_oauth_token" ,
333+ lambda : "gho-keychain" ,
334+ )
335+ monkeypatch .setattr (copilot_auth , "_read_linux_secret_oauth_token" , lambda : None )
336+ monkeypatch .setattr (copilot_auth , "_read_file_oauth_token_candidates" , lambda : [])
337+ monkeypatch .setattr (copilot_auth , "_read_gh_cli_oauth_token" , lambda : None )
338+ monkeypatch .setattr (
339+ copilot_auth ,
340+ "_subscription_resolution_from_token_exchange" ,
341+ lambda candidate : (
342+ copilot_auth ._subscription_resolution (
343+ token = "copilot-api" ,
344+ source = f"{ candidate .source } :token-exchange" ,
345+ confidence = "copilot-token-exchange" ,
346+ api_url = copilot_auth .DEFAULT_API_URL ,
347+ refresh_oauth_token = candidate .token ,
348+ )
349+ if candidate .token == "gho-keychain"
350+ else None
351+ ),
352+ )
353+
354+ assert copilot_auth .resolve_subscription_bearer_token () == "copilot-api"
355+
356+
357+ def test_resolve_subscription_bearer_token_skips_duplicate_keychain_candidate (
358+ monkeypatch : pytest .MonkeyPatch ,
359+ ) -> None :
360+ copilot_auth .save_headroom_copilot_oauth_token ("gho-duplicate" )
361+ monkeypatch .setattr (copilot_auth , "_read_windows_copilot_cli_oauth_token" , lambda : None )
362+ monkeypatch .setattr (
363+ copilot_auth ,
364+ "_read_macos_keychain_oauth_token" ,
365+ lambda : "gho-duplicate" ,
366+ )
367+ monkeypatch .setattr (copilot_auth , "_read_linux_secret_oauth_token" , lambda : None )
368+ monkeypatch .setattr (copilot_auth , "_read_file_oauth_token_candidates" , lambda : [])
369+ monkeypatch .setattr (copilot_auth , "_read_gh_cli_oauth_token" , lambda : None )
370+ attempted_tokens : list [str ] = []
371+ monkeypatch .setattr (
372+ copilot_auth ,
373+ "_subscription_resolution_from_token_exchange" ,
374+ lambda candidate : attempted_tokens .append (candidate .token ) or None ,
375+ )
376+
377+ assert copilot_auth .resolve_subscription_bearer_token () is None
378+ assert attempted_tokens == ["gho-duplicate" ]
379+
380+
293381def test_subscription_enterprise_host_repro (
294382 monkeypatch : pytest .MonkeyPatch ,
295383) -> None :
@@ -304,7 +392,7 @@ def test_subscription_enterprise_host_repro(
304392 monkeypatch .setattr (
305393 copilot_auth ,
306394 "iter_oauth_token_candidates" ,
307- lambda : [
395+ lambda ** _kwargs : [
308396 copilot_auth .CopilotTokenCandidate (
309397 token = "gho-oauth" ,
310398 source = "headroom-copilot-auth:/tmp/copilot_auth.json" ,
@@ -360,7 +448,7 @@ def test_resolve_subscription_exchange_uses_cloud_enterprise_advertised_api(
360448 monkeypatch .setattr (
361449 copilot_auth ,
362450 "iter_oauth_token_candidates" ,
363- lambda : [
451+ lambda ** _kwargs : [
364452 copilot_auth .CopilotTokenCandidate (
365453 token = "gho-oauth" ,
366454 source = "env:GITHUB_COPILOT_TOKEN" ,
@@ -427,7 +515,7 @@ def _resolve_subscription_producer_path(
427515 patch .setattr (
428516 copilot_auth ,
429517 "iter_oauth_token_candidates" ,
430- lambda : [
518+ lambda ** _kwargs : [
431519 copilot_auth .CopilotTokenCandidate (
432520 token = "gho-oauth" , source = "test" , confidence = "test"
433521 )
@@ -445,7 +533,7 @@ def _resolve_subscription_producer_path(
445533 patch .setattr (
446534 copilot_auth ,
447535 "iter_oauth_token_candidates" ,
448- lambda : [
536+ lambda ** _kwargs : [
449537 copilot_auth .CopilotTokenCandidate (
450538 token = "tid_api" , source = "test" , confidence = "test"
451539 )
@@ -1615,3 +1703,57 @@ def __exit__(self, *args):
16151703 )
16161704
16171705 assert result == payload
1706+
1707+
1708+ def test_iter_oauth_token_candidates_includes_linux_secret_service_token (
1709+ monkeypatch : pytest .MonkeyPatch ,
1710+ ) -> None :
1711+ monkeypatch .delenv ("GITHUB_COPILOT_GITHUB_TOKEN" , raising = False )
1712+ monkeypatch .delenv ("GITHUB_COPILOT_TOKEN" , raising = False )
1713+ monkeypatch .delenv ("COPILOT_GITHUB_TOKEN" , raising = False )
1714+ monkeypatch .delenv ("GITHUB_TOKEN" , raising = False )
1715+ monkeypatch .delenv ("GH_TOKEN" , raising = False )
1716+ monkeypatch .setattr (copilot_auth , "_read_windows_copilot_cli_oauth_token" , lambda : None )
1717+ monkeypatch .setattr (copilot_auth , "_read_macos_keychain_oauth_token" , lambda : None )
1718+ monkeypatch .setattr (copilot_auth , "_read_linux_secret_oauth_token" , lambda : "gho-linux" )
1719+ monkeypatch .setattr (copilot_auth , "_read_file_oauth_token_candidates" , lambda : [])
1720+ monkeypatch .setattr (copilot_auth , "_read_gh_cli_oauth_token" , lambda : None )
1721+
1722+ candidates = copilot_auth .iter_oauth_token_candidates ()
1723+
1724+ assert [(candidate .source , candidate .token ) for candidate in candidates ] == [
1725+ ("linux-secret-service:copilot-cli" , "gho-linux" ),
1726+ ]
1727+
1728+
1729+ def test_iter_oauth_token_candidates_skips_platform_secret_stores_when_disabled (
1730+ monkeypatch : pytest .MonkeyPatch ,
1731+ ) -> None :
1732+ monkeypatch .delenv ("GITHUB_COPILOT_GITHUB_TOKEN" , raising = False )
1733+ monkeypatch .delenv ("GITHUB_COPILOT_TOKEN" , raising = False )
1734+ monkeypatch .delenv ("COPILOT_GITHUB_TOKEN" , raising = False )
1735+ monkeypatch .delenv ("GITHUB_TOKEN" , raising = False )
1736+ monkeypatch .delenv ("GH_TOKEN" , raising = False )
1737+ monkeypatch .setattr (copilot_auth , "_read_windows_copilot_cli_oauth_token" , lambda : None )
1738+ monkeypatch .setattr (
1739+ copilot_auth ,
1740+ "_platform_secret_store_oauth_token_candidates" ,
1741+ lambda : pytest .fail ("platform secret stores should not be read" ),
1742+ )
1743+ monkeypatch .setattr (copilot_auth , "_read_file_oauth_token_candidates" , lambda : [])
1744+ monkeypatch .setattr (copilot_auth , "_read_gh_cli_oauth_token" , lambda : None )
1745+
1746+ assert copilot_auth .iter_oauth_token_candidates (include_platform_secret_stores = False ) == []
1747+
1748+
1749+ def test_platform_secret_store_candidates_include_macos_keychain_token (
1750+ monkeypatch : pytest .MonkeyPatch ,
1751+ ) -> None :
1752+ monkeypatch .setattr (copilot_auth , "_read_macos_keychain_oauth_token" , lambda : "gho-macos" )
1753+ monkeypatch .setattr (copilot_auth , "_read_linux_secret_oauth_token" , lambda : None )
1754+
1755+ candidates = copilot_auth ._platform_secret_store_oauth_token_candidates ()
1756+
1757+ assert [(candidate .source , candidate .token ) for candidate in candidates ] == [
1758+ ("macos-keychain:copilot-cli" , "gho-macos" ),
1759+ ]
0 commit comments