mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
910578792f
WIll make it easier to uprev when the new draft spec is supported. Also updates reqwest where needed for compatibility but doesn't update it everywhere since this is already a large diff. The new version of rmcp handles certain kinds of authentication failures differently, this patch includes support for identifying the failing scope in a WWW-Authenticate header.
171 lines
5.4 KiB
Rust
171 lines
5.4 KiB
Rust
mod streamable_http_test_support;
|
|
|
|
use pretty_assertions::assert_eq;
|
|
|
|
use streamable_http_test_support::arm_session_post_failure;
|
|
use streamable_http_test_support::call_echo_tool;
|
|
use streamable_http_test_support::create_client;
|
|
use streamable_http_test_support::expected_echo_result;
|
|
use streamable_http_test_support::spawn_streamable_http_server;
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
async fn streamable_http_404_session_expiry_recovers_and_retries_once() -> anyhow::Result<()> {
|
|
let (_server, base_url) = spawn_streamable_http_server().await?;
|
|
let client = create_client(&base_url).await?;
|
|
|
|
let warmup = call_echo_tool(&client, "warmup").await?;
|
|
assert_eq!(warmup, expected_echo_result("warmup"));
|
|
|
|
arm_session_post_failure(
|
|
&base_url,
|
|
/*status*/ 404,
|
|
/*remaining*/ 1,
|
|
/*www_authenticate_headers*/ &[],
|
|
)
|
|
.await?;
|
|
|
|
let recovered = call_echo_tool(&client, "recovered").await?;
|
|
assert_eq!(recovered, expected_echo_result("recovered"));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
async fn streamable_http_401_does_not_trigger_recovery() -> anyhow::Result<()> {
|
|
let (_server, base_url) = spawn_streamable_http_server().await?;
|
|
let client = create_client(&base_url).await?;
|
|
|
|
let warmup = call_echo_tool(&client, "warmup").await?;
|
|
assert_eq!(warmup, expected_echo_result("warmup"));
|
|
|
|
arm_session_post_failure(
|
|
&base_url,
|
|
/*status*/ 401,
|
|
/*remaining*/ 2,
|
|
/*www_authenticate_headers*/ &[],
|
|
)
|
|
.await?;
|
|
|
|
let first_error = call_echo_tool(&client, "unauthorized").await.unwrap_err();
|
|
assert!(first_error.to_string().contains("401"));
|
|
|
|
let second_error = call_echo_tool(&client, "still-unauthorized")
|
|
.await
|
|
.unwrap_err();
|
|
assert!(second_error.to_string().contains("401"));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
async fn streamable_http_403_scope_challenge_returns_insufficient_scope() -> anyhow::Result<()> {
|
|
let (_server, base_url) = spawn_streamable_http_server().await?;
|
|
let client = create_client(&base_url).await?;
|
|
|
|
let warmup = call_echo_tool(&client, "warmup").await?;
|
|
assert_eq!(warmup, expected_echo_result("warmup"));
|
|
|
|
arm_session_post_failure(
|
|
&base_url,
|
|
/*status*/ 403,
|
|
/*remaining*/ 1,
|
|
/*www_authenticate_headers*/
|
|
&[r#"Bearer error="insufficient_scope", scope="files:read files:write""#],
|
|
)
|
|
.await?;
|
|
|
|
let error = call_echo_tool(&client, "forbidden").await.unwrap_err();
|
|
assert!(
|
|
error.to_string().contains("Insufficient scope"),
|
|
"expected insufficient-scope transport error, got: {error:#}"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
async fn streamable_http_403_finds_bearer_challenge_in_later_header_value() -> anyhow::Result<()> {
|
|
let (_server, base_url) = spawn_streamable_http_server().await?;
|
|
let client = create_client(&base_url).await?;
|
|
|
|
let warmup = call_echo_tool(&client, "warmup").await?;
|
|
assert_eq!(warmup, expected_echo_result("warmup"));
|
|
|
|
arm_session_post_failure(
|
|
&base_url,
|
|
/*status*/ 403,
|
|
/*remaining*/ 1,
|
|
/*www_authenticate_headers*/
|
|
&[
|
|
r#"Basic realm="example""#,
|
|
r#"Bearer error="insufficient_scope", scope="files:read""#,
|
|
],
|
|
)
|
|
.await?;
|
|
|
|
let error = call_echo_tool(&client, "forbidden").await.unwrap_err();
|
|
assert!(
|
|
error.to_string().contains("Insufficient scope"),
|
|
"expected insufficient-scope transport error, got: {error:#}"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
async fn streamable_http_404_recovery_only_retries_once() -> anyhow::Result<()> {
|
|
let (_server, base_url) = spawn_streamable_http_server().await?;
|
|
let client = create_client(&base_url).await?;
|
|
|
|
let warmup = call_echo_tool(&client, "warmup").await?;
|
|
assert_eq!(warmup, expected_echo_result("warmup"));
|
|
|
|
arm_session_post_failure(
|
|
&base_url,
|
|
/*status*/ 404,
|
|
/*remaining*/ 2,
|
|
/*www_authenticate_headers*/ &[],
|
|
)
|
|
.await?;
|
|
|
|
let error = call_echo_tool(&client, "double-404").await.unwrap_err();
|
|
assert!(
|
|
error
|
|
.to_string()
|
|
.contains("handshaking with MCP server failed")
|
|
|| error.to_string().contains("Transport channel closed")
|
|
);
|
|
|
|
let recovered = call_echo_tool(&client, "after-double-404").await?;
|
|
assert_eq!(recovered, expected_echo_result("after-double-404"));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
async fn streamable_http_non_session_failure_does_not_trigger_recovery() -> anyhow::Result<()> {
|
|
let (_server, base_url) = spawn_streamable_http_server().await?;
|
|
let client = create_client(&base_url).await?;
|
|
|
|
let warmup = call_echo_tool(&client, "warmup").await?;
|
|
assert_eq!(warmup, expected_echo_result("warmup"));
|
|
|
|
arm_session_post_failure(
|
|
&base_url,
|
|
/*status*/ 500,
|
|
/*remaining*/ 2,
|
|
/*www_authenticate_headers*/ &[],
|
|
)
|
|
.await?;
|
|
|
|
let first_error = call_echo_tool(&client, "server-error").await.unwrap_err();
|
|
assert!(first_error.to_string().contains("500"));
|
|
|
|
let second_error = call_echo_tool(&client, "still-server-error")
|
|
.await
|
|
.unwrap_err();
|
|
assert!(second_error.to_string().contains("500"));
|
|
|
|
Ok(())
|
|
}
|