mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
[codex] cover standalone CLI HTTP requests [ci changed_files]
This commit is contained in:
Generated
+3
@@ -2268,6 +2268,7 @@ dependencies = [
|
||||
"codex-execpolicy",
|
||||
"codex-features",
|
||||
"codex-git-utils",
|
||||
"codex-http-state",
|
||||
"codex-install-context",
|
||||
"codex-login",
|
||||
"codex-mcp",
|
||||
@@ -2379,11 +2380,13 @@ dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"clap",
|
||||
"codex-api",
|
||||
"codex-client",
|
||||
"codex-cloud-tasks-client",
|
||||
"codex-cloud-tasks-mock-client",
|
||||
"codex-core",
|
||||
"codex-git-utils",
|
||||
"codex-http-state",
|
||||
"codex-login",
|
||||
"codex-model-provider",
|
||||
"codex-tui",
|
||||
|
||||
@@ -39,6 +39,7 @@ codex-execpolicy = { workspace = true }
|
||||
codex-features = { workspace = true }
|
||||
codex-git-utils = { workspace = true }
|
||||
codex-install-context = { workspace = true }
|
||||
codex-http-state = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-memories-write = { workspace = true }
|
||||
codex-mcp = { workspace = true }
|
||||
|
||||
@@ -39,6 +39,8 @@ use codex_core::config::ConfigBuilder;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_core::config::find_codex_home;
|
||||
use codex_features::FEATURES;
|
||||
use codex_http_state::HttpStateContext;
|
||||
use codex_http_state::HttpStateSurface;
|
||||
use codex_install_context::CodexPackageLayout;
|
||||
use codex_install_context::InstallContext;
|
||||
use codex_install_context::InstallMethod;
|
||||
@@ -53,6 +55,7 @@ use codex_login::default_client::build_reqwest_client;
|
||||
use codex_login::default_client::default_headers;
|
||||
use codex_login::load_auth_dot_json;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_model_provider::with_native_integrity_state;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_terminal_detection::Multiplexer;
|
||||
use codex_terminal_detection::TerminalInfo;
|
||||
@@ -2261,7 +2264,9 @@ async fn websocket_reachability_check(
|
||||
));
|
||||
|
||||
let runtime_provider = create_model_provider(provider.clone(), auth_manager);
|
||||
let auth = runtime_provider.auth().await;
|
||||
let auth = runtime_provider
|
||||
.auth_for_surface(HttpStateSurface::CodexCli)
|
||||
.await;
|
||||
details.push(format!(
|
||||
"auth mode: {}",
|
||||
auth.as_ref().map(auth_mode_name).unwrap_or("none")
|
||||
@@ -2311,6 +2316,14 @@ async fn websocket_reachability_check(
|
||||
OPENAI_BETA_HEADER,
|
||||
HeaderValue::from_static(RESPONSES_WEBSOCKETS_V2_BETA_HEADER_VALUE),
|
||||
);
|
||||
let api_auth = with_native_integrity_state(
|
||||
api_auth,
|
||||
auth.as_ref(),
|
||||
Some(HttpStateContext::new(
|
||||
config.codex_home.to_path_buf(),
|
||||
HttpStateSurface::CodexCli,
|
||||
)),
|
||||
);
|
||||
let client = ResponsesWebsocketClient::new(api_provider, api_auth);
|
||||
match tokio::time::timeout(
|
||||
provider.websocket_connect_timeout(),
|
||||
|
||||
@@ -1642,7 +1642,7 @@ async fn load_exec_server_remote_auth_provider(
|
||||
let auth =
|
||||
CodexAuth::from_agent_identity_jwt(&agent_identity_jwt, Some(&config.chatgpt_base_url))
|
||||
.await?;
|
||||
return Ok(codex_model_provider::auth_provider_from_auth(&auth));
|
||||
return Ok(exec_server_remote_auth_provider(config, &auth));
|
||||
}
|
||||
|
||||
let auth = load_exec_server_remote_auth(
|
||||
@@ -1661,7 +1661,21 @@ async fn load_exec_server_remote_auth_provider(
|
||||
validate_api_key_remote_host(base_url)?;
|
||||
}
|
||||
|
||||
Ok(codex_model_provider::auth_provider_from_auth(&auth))
|
||||
Ok(exec_server_remote_auth_provider(config, &auth))
|
||||
}
|
||||
|
||||
fn exec_server_remote_auth_provider(
|
||||
config: &codex_core::config::Config,
|
||||
auth: &CodexAuth,
|
||||
) -> codex_api::SharedAuthProvider {
|
||||
codex_model_provider::with_native_integrity_state(
|
||||
codex_model_provider::auth_provider_from_auth(auth),
|
||||
Some(auth),
|
||||
Some(codex_http_state::HttpStateContext::new(
|
||||
config.codex_home.to_path_buf(),
|
||||
codex_http_state::HttpStateSurface::CodexExec,
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
fn is_supported_exec_server_remote_auth(auth: &CodexAuth) -> bool {
|
||||
@@ -1723,12 +1737,15 @@ async fn load_exec_server_remote_auth(
|
||||
let auth_manager =
|
||||
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ true).await;
|
||||
|
||||
let auth = match auth_manager.auth().await {
|
||||
let auth = match auth_manager
|
||||
.auth_for_surface(codex_http_state::HttpStateSurface::CodexExec)
|
||||
.await
|
||||
{
|
||||
Some(auth) => auth,
|
||||
None => {
|
||||
auth_manager.reload().await;
|
||||
auth_manager
|
||||
.auth()
|
||||
.auth_for_surface(codex_http_state::HttpStateSurface::CodexExec)
|
||||
.await
|
||||
.ok_or_else(|| anyhow::anyhow!(missing_auth_error))?
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@ workspace = true
|
||||
anyhow = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
codex-api = { workspace = true }
|
||||
codex-client = { workspace = true }
|
||||
codex-cloud-tasks-client = { workspace = true }
|
||||
# TODO: codex-cloud-tasks-mock-client should be in dev-dependencies.
|
||||
codex-cloud-tasks-mock-client = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-git-utils = { workspace = true }
|
||||
codex-http-state = { workspace = true }
|
||||
codex-login = { path = "../login" }
|
||||
codex-model-provider = { workspace = true }
|
||||
codex-tui = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use codex_client::build_reqwest_client_with_custom_ca;
|
||||
use reqwest::header::CONTENT_TYPE;
|
||||
use reqwest::header::HeaderMap;
|
||||
use std::collections::HashMap;
|
||||
use tracing::info;
|
||||
use tracing::warn;
|
||||
@@ -24,7 +23,7 @@ pub struct AutodetectSelection {
|
||||
|
||||
pub async fn autodetect_environment_id(
|
||||
base_url: &str,
|
||||
headers: &HeaderMap,
|
||||
request_auth: &crate::util::ChatGptRequestAuth,
|
||||
desired_label: Option<String>,
|
||||
) -> anyhow::Result<AutodetectSelection> {
|
||||
// 1) Try repo-specific environments based on local git origins (GitHub only, like VSCode)
|
||||
@@ -45,7 +44,7 @@ pub async fn autodetect_environment_id(
|
||||
)
|
||||
};
|
||||
crate::append_error_log(format!("env: GET {url}"));
|
||||
match get_json::<Vec<CodeEnvironment>>(&url, headers).await {
|
||||
match get_json::<Vec<CodeEnvironment>>(&url, request_auth).await {
|
||||
Ok(mut list) => {
|
||||
crate::append_error_log(format!(
|
||||
"env: by-repo returned {} env(s) for {owner}/{repo}",
|
||||
@@ -75,7 +74,9 @@ pub async fn autodetect_environment_id(
|
||||
crate::append_error_log(format!("env: GET {list_url}"));
|
||||
// Fetch and log the full environments JSON for debugging
|
||||
let http = build_reqwest_client_with_custom_ca(reqwest::Client::builder())?;
|
||||
let headers = request_auth.headers_for_url(&list_url);
|
||||
let res = http.get(&list_url).headers(headers.clone()).send().await?;
|
||||
request_auth.observe_response_headers(&list_url, &headers, res.headers());
|
||||
let status = res.status();
|
||||
let ct = res
|
||||
.headers()
|
||||
@@ -146,10 +147,12 @@ fn pick_environment_row(
|
||||
|
||||
async fn get_json<T: serde::de::DeserializeOwned>(
|
||||
url: &str,
|
||||
headers: &HeaderMap,
|
||||
request_auth: &crate::util::ChatGptRequestAuth,
|
||||
) -> anyhow::Result<T> {
|
||||
let http = build_reqwest_client_with_custom_ca(reqwest::Client::builder())?;
|
||||
let headers = request_auth.headers_for_url(url);
|
||||
let res = http.get(url).headers(headers.clone()).send().await?;
|
||||
request_auth.observe_response_headers(url, &headers, res.headers());
|
||||
let status = res.status();
|
||||
let ct = res
|
||||
.headers()
|
||||
@@ -255,7 +258,7 @@ fn parse_owner_repo(url: &str) -> Option<(String, String)> {
|
||||
/// Returns a de-duplicated, sorted set suitable for the TUI modal.
|
||||
pub async fn list_environments(
|
||||
base_url: &str,
|
||||
headers: &HeaderMap,
|
||||
request_auth: &crate::util::ChatGptRequestAuth,
|
||||
) -> anyhow::Result<Vec<crate::app::EnvironmentRow>> {
|
||||
let mut map: HashMap<String, crate::app::EnvironmentRow> = HashMap::new();
|
||||
|
||||
@@ -274,7 +277,7 @@ pub async fn list_environments(
|
||||
base_url, "github", owner, repo
|
||||
)
|
||||
};
|
||||
match get_json::<Vec<CodeEnvironment>>(&url, headers).await {
|
||||
match get_json::<Vec<CodeEnvironment>>(&url, request_auth).await {
|
||||
Ok(list) => {
|
||||
info!("env_tui: by-repo {}:{} -> {} envs", owner, repo, list.len());
|
||||
for e in list {
|
||||
@@ -312,7 +315,7 @@ pub async fn list_environments(
|
||||
} else {
|
||||
format!("{base_url}/api/codex/environments")
|
||||
};
|
||||
match get_json::<Vec<CodeEnvironment>>(&list_url, headers).await {
|
||||
match get_json::<Vec<CodeEnvironment>>(&list_url, request_auth).await {
|
||||
Ok(list) => {
|
||||
info!("env_tui: global list -> {} envs", list.len());
|
||||
for e in list {
|
||||
|
||||
@@ -70,7 +70,11 @@ async fn init_backend(user_agent_suffix: &str) -> anyhow::Result<BackendContext>
|
||||
|
||||
let auth_manager = util::load_auth_manager(Some(base_url.clone())).await;
|
||||
let auth = match auth_manager.as_ref() {
|
||||
Some(manager) => manager.auth().await,
|
||||
Some(manager) => {
|
||||
manager
|
||||
.auth_for_surface(codex_http_state::HttpStateSurface::CodexCli)
|
||||
.await
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let auth = match auth {
|
||||
@@ -94,7 +98,17 @@ async fn init_backend(user_agent_suffix: &str) -> anyhow::Result<BackendContext>
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let auth_provider = codex_model_provider::auth_provider_from_auth(&auth);
|
||||
let http_state = auth_manager.as_ref().map(|manager| {
|
||||
codex_http_state::HttpStateContext::new(
|
||||
manager.codex_home().to_path_buf(),
|
||||
codex_http_state::HttpStateSurface::CodexCli,
|
||||
)
|
||||
});
|
||||
let auth_provider = codex_model_provider::with_native_integrity_state(
|
||||
codex_model_provider::auth_provider_from_auth(&auth),
|
||||
Some(&auth),
|
||||
http_state,
|
||||
);
|
||||
http = http.with_auth_provider(auth_provider);
|
||||
if let Some(acc) = auth.get_account_id() {
|
||||
append_error_log(format!("auth: set ChatGPT-Account-Id header: {acc}"));
|
||||
@@ -185,7 +199,7 @@ async fn resolve_environment_id(ctx: &BackendContext, requested: &str) -> anyhow
|
||||
return Err(anyhow!("environment id must not be empty"));
|
||||
}
|
||||
let normalized = util::normalize_base_url(&ctx.base_url);
|
||||
let headers = util::build_chatgpt_headers().await;
|
||||
let headers = util::build_chatgpt_request_auth().await;
|
||||
let environments = crate::env_detect::list_environments(&normalized, &headers).await?;
|
||||
if environments.is_empty() {
|
||||
return Err(anyhow!(
|
||||
@@ -841,7 +855,7 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> an
|
||||
&std::env::var("CODEX_CLOUD_TASKS_BASE_URL")
|
||||
.unwrap_or_else(|_| "https://chatgpt.com/backend-api".to_string()),
|
||||
);
|
||||
let headers = util::build_chatgpt_headers().await;
|
||||
let headers = util::build_chatgpt_request_auth().await;
|
||||
let res = crate::env_detect::list_environments(&base_url, &headers).await;
|
||||
let _ = tx.send(app::AppEvent::EnvironmentsLoaded(res));
|
||||
});
|
||||
@@ -857,7 +871,7 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> an
|
||||
.unwrap_or_else(|_| "https://chatgpt.com/backend-api".to_string()),
|
||||
);
|
||||
// Build headers: UA + ChatGPT auth if available
|
||||
let headers = util::build_chatgpt_headers().await;
|
||||
let headers = util::build_chatgpt_request_auth().await;
|
||||
|
||||
// Run autodetect. If it fails, we keep using "All".
|
||||
let res = crate::env_detect::autodetect_environment_id(
|
||||
@@ -1082,7 +1096,7 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> an
|
||||
&std::env::var("CODEX_CLOUD_TASKS_BASE_URL")
|
||||
.unwrap_or_else(|_| "https://chatgpt.com/backend-api".to_string()),
|
||||
);
|
||||
let headers = crate::util::build_chatgpt_headers().await;
|
||||
let headers = crate::util::build_chatgpt_request_auth().await;
|
||||
let res = crate::env_detect::list_environments(&base_url, &headers).await;
|
||||
let _ = tx.send(app::AppEvent::EnvironmentsLoaded(res));
|
||||
});
|
||||
@@ -1465,7 +1479,7 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> an
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let base_url = crate::util::normalize_base_url(&std::env::var("CODEX_CLOUD_TASKS_BASE_URL").unwrap_or_else(|_| "https://chatgpt.com/backend-api".to_string()));
|
||||
let headers = crate::util::build_chatgpt_headers().await;
|
||||
let headers = crate::util::build_chatgpt_request_auth().await;
|
||||
let res = crate::env_detect::list_environments(&base_url, &headers).await;
|
||||
let _ = tx.send(app::AppEvent::EnvironmentsLoaded(res));
|
||||
});
|
||||
@@ -1654,7 +1668,7 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> an
|
||||
&std::env::var("CODEX_CLOUD_TASKS_BASE_URL")
|
||||
.unwrap_or_else(|_| "https://chatgpt.com/backend-api".to_string()),
|
||||
);
|
||||
let headers = crate::util::build_chatgpt_headers().await;
|
||||
let headers = crate::util::build_chatgpt_request_auth().await;
|
||||
let res = crate::env_detect::list_environments(&base_url, &headers).await;
|
||||
let _ = tx.send(app::AppEvent::EnvironmentsLoaded(res));
|
||||
});
|
||||
@@ -1830,7 +1844,7 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> an
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let base_url = crate::util::normalize_base_url(&std::env::var("CODEX_CLOUD_TASKS_BASE_URL").unwrap_or_else(|_| "https://chatgpt.com/backend-api".to_string()));
|
||||
let headers = crate::util::build_chatgpt_headers().await;
|
||||
let headers = crate::util::build_chatgpt_request_auth().await;
|
||||
let res = crate::env_detect::list_environments(&base_url, &headers).await;
|
||||
let _ = tx.send(app::AppEvent::EnvironmentsLoaded(res));
|
||||
});
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
use chrono::DateTime;
|
||||
use chrono::Local;
|
||||
use chrono::Utc;
|
||||
use codex_api::SharedAuthProvider;
|
||||
use codex_http_state::HttpStateContext;
|
||||
use codex_http_state::HttpStateSurface;
|
||||
use reqwest::header::HeaderMap;
|
||||
use reqwest::header::HeaderValue;
|
||||
use reqwest::header::USER_AGENT;
|
||||
|
||||
use codex_core::config::Config;
|
||||
use codex_login::AuthManager;
|
||||
@@ -55,26 +60,58 @@ pub async fn load_auth_manager(chatgpt_base_url: Option<String>) -> Option<AuthM
|
||||
)
|
||||
}
|
||||
|
||||
/// Build headers for ChatGPT-backed requests: `User-Agent`, optional `Authorization`,
|
||||
/// and optional `ChatGPT-Account-Id`.
|
||||
pub async fn build_chatgpt_headers() -> HeaderMap {
|
||||
use reqwest::header::HeaderValue;
|
||||
use reqwest::header::USER_AGENT;
|
||||
#[derive(Clone)]
|
||||
pub struct ChatGptRequestAuth {
|
||||
user_agent: HeaderValue,
|
||||
auth_provider: Option<SharedAuthProvider>,
|
||||
}
|
||||
|
||||
impl ChatGptRequestAuth {
|
||||
pub fn headers_for_url(&self, request_url: &str) -> HeaderMap {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(USER_AGENT, self.user_agent.clone());
|
||||
if let Some(auth_provider) = &self.auth_provider {
|
||||
auth_provider.add_auth_headers_for_url(request_url, &mut headers);
|
||||
}
|
||||
headers
|
||||
}
|
||||
|
||||
pub fn observe_response_headers(
|
||||
&self,
|
||||
request_url: &str,
|
||||
request_headers: &HeaderMap,
|
||||
response_headers: &HeaderMap,
|
||||
) {
|
||||
if let Some(auth_provider) = &self.auth_provider {
|
||||
auth_provider.observe_response_headers(request_url, request_headers, response_headers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build request auth for ChatGPT-backed cloud-task requests.
|
||||
pub async fn build_chatgpt_request_auth() -> ChatGptRequestAuth {
|
||||
set_user_agent_suffix("codex_cloud_tasks_tui");
|
||||
let ua = codex_login::default_client::get_codex_user_agent();
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
USER_AGENT,
|
||||
HeaderValue::from_str(&ua).unwrap_or(HeaderValue::from_static("codex-cli")),
|
||||
);
|
||||
if let Some(am) = load_auth_manager(/*chatgpt_base_url*/ None).await
|
||||
&& let Some(auth) = am.auth().await
|
||||
let auth_manager = load_auth_manager(/*chatgpt_base_url*/ None).await;
|
||||
let auth_provider = if let Some(am) = auth_manager
|
||||
&& let Some(auth) = am.auth_for_surface(HttpStateSurface::CodexCli).await
|
||||
&& auth.uses_codex_backend()
|
||||
{
|
||||
headers.extend(codex_model_provider::auth_provider_from_auth(&auth).to_auth_headers());
|
||||
Some(codex_model_provider::with_native_integrity_state(
|
||||
codex_model_provider::auth_provider_from_auth(&auth),
|
||||
Some(&auth),
|
||||
Some(HttpStateContext::new(
|
||||
am.codex_home().to_path_buf(),
|
||||
HttpStateSurface::CodexCli,
|
||||
)),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
ChatGptRequestAuth {
|
||||
user_agent: HeaderValue::from_str(&ua).unwrap_or(HeaderValue::from_static("codex-cli")),
|
||||
auth_provider,
|
||||
}
|
||||
headers
|
||||
}
|
||||
|
||||
/// Construct a browser-friendly task URL for the given backend base URL.
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::time::Duration;
|
||||
|
||||
use codex_api::SharedAuthProvider;
|
||||
use reqwest::StatusCode;
|
||||
use reqwest::header::HeaderMap;
|
||||
use serde::Deserialize;
|
||||
use tokio::time::sleep;
|
||||
use tokio_tungstenite::connect_async;
|
||||
@@ -48,15 +49,24 @@ impl EnvironmentRegistryClient {
|
||||
&self,
|
||||
environment_id: &str,
|
||||
) -> Result<EnvironmentRegistryRegistrationResponse, ExecServerError> {
|
||||
let request_url = endpoint_url(
|
||||
&self.base_url,
|
||||
&format!("/cloud/environment/{environment_id}/register"),
|
||||
);
|
||||
let mut request_headers = HeaderMap::new();
|
||||
self.auth_provider
|
||||
.add_auth_headers_for_url(&request_url, &mut request_headers);
|
||||
let response = self
|
||||
.http
|
||||
.post(endpoint_url(
|
||||
&self.base_url,
|
||||
&format!("/cloud/environment/{environment_id}/register"),
|
||||
))
|
||||
.headers(self.auth_provider.to_auth_headers())
|
||||
.post(&request_url)
|
||||
.headers(request_headers.clone())
|
||||
.send()
|
||||
.await?;
|
||||
self.auth_provider.observe_response_headers(
|
||||
&request_url,
|
||||
&request_headers,
|
||||
response.headers(),
|
||||
);
|
||||
self.parse_json_response(response).await
|
||||
}
|
||||
|
||||
@@ -244,6 +254,7 @@ fn preview_error_body(body: &str) -> Option<String> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex as StdMutex;
|
||||
|
||||
use codex_api::AuthProvider;
|
||||
use http::HeaderMap;
|
||||
@@ -278,6 +289,43 @@ mod tests {
|
||||
Arc::new(StaticRegistryAuthProvider)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct RecordingRegistryAuthProvider {
|
||||
observed_updates: StdMutex<Vec<(String, String, String)>>,
|
||||
}
|
||||
|
||||
impl AuthProvider for RecordingRegistryAuthProvider {
|
||||
fn add_auth_headers(&self, _headers: &mut HeaderMap) {}
|
||||
|
||||
fn add_auth_headers_for_url(&self, _request_url: &str, headers: &mut HeaderMap) {
|
||||
let _ = headers.insert("x-test-state", HeaderValue::from_static("sent-state"));
|
||||
}
|
||||
|
||||
fn observe_response_headers(
|
||||
&self,
|
||||
request_url: &str,
|
||||
request_headers: &HeaderMap,
|
||||
response_headers: &HeaderMap,
|
||||
) {
|
||||
let sent_state = request_headers
|
||||
.get("x-test-state")
|
||||
.and_then(|value| value.to_str().ok());
|
||||
let update_state = response_headers
|
||||
.get("x-test-state-update")
|
||||
.and_then(|value| value.to_str().ok());
|
||||
if let (Some(sent_state), Some(update_state)) = (sent_state, update_state) {
|
||||
self.observed_updates
|
||||
.lock()
|
||||
.expect("observed updates lock should not be poisoned")
|
||||
.push((
|
||||
request_url.to_string(),
|
||||
sent_state.to_string(),
|
||||
update_state.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn register_environment_posts_with_auth_provider_headers() {
|
||||
let server = MockServer::start().await;
|
||||
@@ -349,6 +397,47 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn register_environment_uses_url_scoped_auth_and_observes_response_headers() {
|
||||
let server = MockServer::start().await;
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/cloud/environment/environment-requested/register"))
|
||||
.and(header("x-test-state", "sent-state"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(200)
|
||||
.insert_header("x-test-state-update", "next-state")
|
||||
.set_body_json(serde_json::json!({
|
||||
"environment_id": "env-1",
|
||||
"url": "wss://rendezvous.test/ws"
|
||||
})),
|
||||
)
|
||||
.mount(&server)
|
||||
.await;
|
||||
let auth = Arc::new(RecordingRegistryAuthProvider::default());
|
||||
let client =
|
||||
EnvironmentRegistryClient::new(server.uri(), auth.clone()).expect("registry client");
|
||||
|
||||
client
|
||||
.register_environment("environment-requested")
|
||||
.await
|
||||
.expect("register environment");
|
||||
|
||||
assert_eq!(
|
||||
*auth
|
||||
.observed_updates
|
||||
.lock()
|
||||
.expect("observed updates lock should not be poisoned"),
|
||||
vec![(
|
||||
format!(
|
||||
"{}/cloud/environment/environment-requested/register",
|
||||
server.uri()
|
||||
),
|
||||
"sent-state".to_string(),
|
||||
"next-state".to_string(),
|
||||
)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_output_redacts_auth_provider() {
|
||||
let config = RemoteEnvironmentConfig::new(
|
||||
|
||||
Reference in New Issue
Block a user