@@ -4,9 +4,11 @@ use std::time::Duration;
44
55use codex_config:: types:: OAuthCredentialsStoreMode ;
66use codex_exec_server:: Environment ;
7+ use codex_rmcp_client:: McpAuthStatus ;
78use codex_rmcp_client:: RmcpClient ;
89use codex_rmcp_client:: StoredOAuthTokens ;
910use codex_rmcp_client:: WrappedOAuthTokenResponse ;
11+ use codex_rmcp_client:: determine_streamable_http_auth_status;
1012use codex_rmcp_client:: save_oauth_tokens;
1113use oauth2:: AccessToken ;
1214use oauth2:: RefreshToken ;
@@ -33,6 +35,7 @@ const EXPIRED_ACCESS_TOKEN: &str = "expired-access-token";
3335const REFRESH_TOKEN : & str = "valid-refresh-token" ;
3436const REFRESHED_ACCESS_TOKEN : & str = "refreshed-access-token" ;
3537const CHILD_SERVER_URL_ENV : & str = "MCP_TEST_OAUTH_STARTUP_SERVER_URL" ;
38+ const UNREFRESHABLE_SERVER_URL : & str = "https://unrefreshable.example/mcp" ;
3639
3740#[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
3841async fn refreshes_expired_persisted_token_before_initialize ( ) -> anyhow:: Result < ( ) > {
@@ -112,6 +115,59 @@ async fn refreshes_expired_persisted_token_before_initialize() -> anyhow::Result
112115 Ok ( ( ) )
113116}
114117
118+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
119+ async fn reports_expired_unrefreshable_credentials_as_not_logged_in ( ) -> anyhow:: Result < ( ) > {
120+ let codex_home = TempDir :: new ( ) ?;
121+
122+ let status = Command :: new ( std:: env:: current_exe ( ) ?)
123+ . args ( [
124+ "expired_unrefreshable_auth_status_child" ,
125+ "--exact" ,
126+ "--ignored" ,
127+ "--nocapture" ,
128+ ] )
129+ . env ( "CODEX_HOME" , codex_home. path ( ) )
130+ . status ( )
131+ . await ?;
132+
133+ assert ! (
134+ status. success( ) ,
135+ "expired unrefreshable auth status child failed: {status}"
136+ ) ;
137+ Ok ( ( ) )
138+ }
139+
140+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
141+ #[ ignore = "spawned by reports_expired_unrefreshable_credentials_as_not_logged_in" ]
142+ async fn expired_unrefreshable_auth_status_child ( ) -> anyhow:: Result < ( ) > {
143+ let response = OAuthTokenResponse :: new (
144+ AccessToken :: new ( EXPIRED_ACCESS_TOKEN . to_string ( ) ) ,
145+ BasicTokenType :: Bearer ,
146+ VendorExtraTokenFields :: default ( ) ,
147+ ) ;
148+ let tokens = StoredOAuthTokens {
149+ server_name : SERVER_NAME . to_string ( ) ,
150+ url : UNREFRESHABLE_SERVER_URL . to_string ( ) ,
151+ client_id : "test-client-id" . to_string ( ) ,
152+ token_response : WrappedOAuthTokenResponse ( response) ,
153+ expires_at : Some ( 0 ) ,
154+ } ;
155+ save_oauth_tokens ( SERVER_NAME , & tokens, OAuthCredentialsStoreMode :: File ) ?;
156+
157+ let status = determine_streamable_http_auth_status (
158+ SERVER_NAME ,
159+ UNREFRESHABLE_SERVER_URL ,
160+ /*bearer_token_env_var*/ None ,
161+ /*http_headers*/ None ,
162+ /*env_http_headers*/ None ,
163+ OAuthCredentialsStoreMode :: File ,
164+ )
165+ . await ?;
166+
167+ assert_eq ! ( status, McpAuthStatus :: NotLoggedIn ) ;
168+ Ok ( ( ) )
169+ }
170+
115171#[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
116172#[ ignore = "spawned by refreshes_expired_persisted_token_before_initialize" ]
117173async fn oauth_startup_child ( ) -> anyhow:: Result < ( ) > {
0 commit comments