@@ -107,6 +107,71 @@ async def fake_exchange(code: str, code_verifier: str, redirect_uri: str) -> dic
107107 assert config .default_model == "xai/grok-4"
108108
109109
110+ @pytest .mark .asyncio
111+ async def test_xai_persistence_errors_hide_internal_details (
112+ monkeypatch : pytest .MonkeyPatch ,
113+ ) -> None :
114+ from pythinker_code .auth import xai
115+
116+ diagnostic = "permission denied at /private/credentials/xai.json"
117+ config = Config (is_from_default_location = True )
118+
119+ async def fake_loopback (** _kwargs : Any ) -> LoopbackAuthorization :
120+ return LoopbackAuthorization ("auth-code" , "verifier" , "http://127.0.0.1:56121/callback" )
121+
122+ async def fake_exchange (
123+ _code : str ,
124+ _code_verifier : str ,
125+ _redirect_uri : str ,
126+ ) -> dict [str , Any ]:
127+ return {
128+ "access_token" : "xai-access" ,
129+ "refresh_token" : "xai-refresh" ,
130+ "expires_in" : 3600 ,
131+ }
132+
133+ async def fake_request_device_code (** _kwargs : Any ) -> DeviceCode :
134+ return DeviceCode (
135+ user_code = "GROK-CODE" ,
136+ verification_uri = "https://auth.x.ai/activate" ,
137+ device_code = "device-secret" ,
138+ interval = 5 ,
139+ expires_in = 900 ,
140+ )
141+
142+ async def fake_poll_device_token (** _kwargs : Any ) -> dict [str , Any ]:
143+ return {
144+ "access_token" : "xai-access" ,
145+ "refresh_token" : "xai-refresh" ,
146+ "expires_in" : 3600 ,
147+ }
148+
149+ async def fake_models () -> tuple [xai .XAIModel , ...]:
150+ return xai .XAI_MODELS
151+
152+ async def fail_persistence (* _args : object , ** _kwargs : object ) -> None :
153+ raise OSError (diagnostic )
154+
155+ monkeypatch .setattr (xai , "run_loopback_pkce_flow" , fake_loopback )
156+ monkeypatch .setattr (xai , "_exchange_code_for_tokens" , fake_exchange )
157+ monkeypatch .setattr (xai , "request_device_code" , fake_request_device_code )
158+ monkeypatch .setattr (xai , "poll_device_token" , fake_poll_device_token )
159+ monkeypatch .setattr (xai , "_discover_xai_models" , fake_models )
160+ monkeypatch .setattr (xai , "persist_login" , fail_persistence )
161+ monkeypatch .setattr (xai , "persist_logout" , fail_persistence )
162+
163+ browser_events = [event async for event in xai .login_xai_browser (config )]
164+ headless_events = [event async for event in xai .login_xai_headless (config )]
165+ logout_events = [event async for event in xai .logout_xai (config )]
166+
167+ assert browser_events [- 1 ].message == "Failed to save xAI Grok login."
168+ assert headless_events [- 1 ].message == "Failed to save xAI Grok login."
169+ assert logout_events [- 1 ].message == "Failed to log out of xAI Grok."
170+ assert diagnostic not in browser_events [- 1 ].json
171+ assert diagnostic not in headless_events [- 1 ].json
172+ assert diagnostic not in logout_events [- 1 ].json
173+
174+
110175@pytest .mark .asyncio
111176async def test_login_xai_headless_uses_device_flow (
112177 monkeypatch : pytest .MonkeyPatch ,
0 commit comments