11mod streamable_http_test_support;
22
33use std:: time:: Duration ;
4+ use std:: time:: SystemTime ;
5+ use std:: time:: UNIX_EPOCH ;
46
57use codex_config:: types:: OAuthCredentialsStoreMode ;
68use codex_exec_server:: Environment ;
@@ -13,6 +15,7 @@ use codex_rmcp_client::save_oauth_tokens;
1315use oauth2:: AccessToken ;
1416use oauth2:: RefreshToken ;
1517use oauth2:: basic:: BasicTokenType ;
18+ use pretty_assertions:: assert_eq;
1619use rmcp:: transport:: auth:: OAuthTokenResponse ;
1720use rmcp:: transport:: auth:: VendorExtraTokenFields ;
1821use serde_json:: Value ;
@@ -36,6 +39,8 @@ const REFRESH_TOKEN: &str = "valid-refresh-token";
3639const REFRESHED_ACCESS_TOKEN : & str = "refreshed-access-token" ;
3740const CHILD_SERVER_URL_ENV : & str = "MCP_TEST_OAUTH_STARTUP_SERVER_URL" ;
3841const UNREFRESHABLE_SERVER_URL : & str = "https://unrefreshable.example/mcp" ;
42+ const UNEXPIRED_SERVER_URL : & str = "https://unexpired.example/mcp" ;
43+ const REFRESHABLE_SERVER_URL : & str = "https://refreshable.example/mcp" ;
3944
4045#[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
4146async fn refreshes_expired_persisted_token_before_initialize ( ) -> anyhow:: Result < ( ) > {
@@ -116,12 +121,12 @@ async fn refreshes_expired_persisted_token_before_initialize() -> anyhow::Result
116121}
117122
118123#[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
119- async fn reports_expired_unrefreshable_credentials_as_not_logged_in ( ) -> anyhow:: Result < ( ) > {
124+ async fn reports_auth_status_for_persisted_credentials ( ) -> anyhow:: Result < ( ) > {
120125 let codex_home = TempDir :: new ( ) ?;
121126
122127 let status = Command :: new ( std:: env:: current_exe ( ) ?)
123128 . args ( [
124- "expired_unrefreshable_auth_status_child " ,
129+ "persisted_credentials_auth_status_child " ,
125130 "--exact" ,
126131 "--ignored" ,
127132 "--nocapture" ,
@@ -132,14 +137,14 @@ async fn reports_expired_unrefreshable_credentials_as_not_logged_in() -> anyhow:
132137
133138 assert ! (
134139 status. success( ) ,
135- "expired unrefreshable auth status child failed: {status}"
140+ "persisted credentials auth status child failed: {status}"
136141 ) ;
137142 Ok ( ( ) )
138143}
139144
140145#[ 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 < ( ) > {
146+ #[ ignore = "spawned by reports_auth_status_for_persisted_credentials " ]
147+ async fn persisted_credentials_auth_status_child ( ) -> anyhow:: Result < ( ) > {
143148 let response = OAuthTokenResponse :: new (
144149 AccessToken :: new ( EXPIRED_ACCESS_TOKEN . to_string ( ) ) ,
145150 BasicTokenType :: Bearer ,
@@ -154,18 +159,60 @@ async fn expired_unrefreshable_auth_status_child() -> anyhow::Result<()> {
154159 } ;
155160 save_oauth_tokens ( SERVER_NAME , & tokens, OAuthCredentialsStoreMode :: File ) ?;
156161
157- let status = determine_streamable_http_auth_status (
162+ let status = auth_status ( UNREFRESHABLE_SERVER_URL ) . await ?;
163+ assert_eq ! ( status, McpAuthStatus :: NotLoggedIn ) ;
164+
165+ let response = OAuthTokenResponse :: new (
166+ AccessToken :: new ( "unexpired-access-token" . to_string ( ) ) ,
167+ BasicTokenType :: Bearer ,
168+ VendorExtraTokenFields :: default ( ) ,
169+ ) ;
170+ let now = SystemTime :: now ( )
171+ . duration_since ( UNIX_EPOCH )
172+ . unwrap_or_else ( |_| Duration :: from_secs ( 0 ) )
173+ . as_millis ( ) as u64 ;
174+ let tokens = StoredOAuthTokens {
175+ server_name : SERVER_NAME . to_string ( ) ,
176+ url : UNEXPIRED_SERVER_URL . to_string ( ) ,
177+ client_id : "test-client-id" . to_string ( ) ,
178+ token_response : WrappedOAuthTokenResponse ( response) ,
179+ expires_at : Some ( now. saturating_add ( /*rhs*/ 60_000 ) ) ,
180+ } ;
181+ save_oauth_tokens ( SERVER_NAME , & tokens, OAuthCredentialsStoreMode :: File ) ?;
182+
183+ let status = auth_status ( UNEXPIRED_SERVER_URL ) . await ?;
184+ assert_eq ! ( status, McpAuthStatus :: OAuth ) ;
185+
186+ let mut response = OAuthTokenResponse :: new (
187+ AccessToken :: new ( EXPIRED_ACCESS_TOKEN . to_string ( ) ) ,
188+ BasicTokenType :: Bearer ,
189+ VendorExtraTokenFields :: default ( ) ,
190+ ) ;
191+ response. set_refresh_token ( Some ( RefreshToken :: new ( REFRESH_TOKEN . to_string ( ) ) ) ) ;
192+ let tokens = StoredOAuthTokens {
193+ server_name : SERVER_NAME . to_string ( ) ,
194+ url : REFRESHABLE_SERVER_URL . to_string ( ) ,
195+ client_id : "test-client-id" . to_string ( ) ,
196+ token_response : WrappedOAuthTokenResponse ( response) ,
197+ expires_at : Some ( 0 ) ,
198+ } ;
199+ save_oauth_tokens ( SERVER_NAME , & tokens, OAuthCredentialsStoreMode :: File ) ?;
200+
201+ let status = auth_status ( REFRESHABLE_SERVER_URL ) . await ?;
202+ assert_eq ! ( status, McpAuthStatus :: OAuth ) ;
203+ Ok ( ( ) )
204+ }
205+
206+ async fn auth_status ( server_url : & str ) -> anyhow:: Result < McpAuthStatus > {
207+ determine_streamable_http_auth_status (
158208 SERVER_NAME ,
159- UNREFRESHABLE_SERVER_URL ,
209+ server_url ,
160210 /*bearer_token_env_var*/ None ,
161211 /*http_headers*/ None ,
162212 /*env_http_headers*/ None ,
163213 OAuthCredentialsStoreMode :: File ,
164214 )
165- . await ?;
166-
167- assert_eq ! ( status, McpAuthStatus :: NotLoggedIn ) ;
168- Ok ( ( ) )
215+ . await
169216}
170217
171218#[ tokio:: test( flavor = "multi_thread" , worker_threads = 1 ) ]
0 commit comments