Test forced workspace allowlist login URL

Add app-server coverage for ChatGPT login URLs when config contains multiple forced workspaces.

Assert the interactive account/login/start response carries one comma-separated allowed_workspace_id query parameter, matching authapi's allowlist contract.
This commit is contained in:
rreichel3-oai
2026-05-11 19:07:37 -04:00
parent 43c04f0e8b
commit fa92d4ac98
@@ -44,6 +44,7 @@ use std::path::Path;
use std::time::Duration;
use tempfile::TempDir;
use tokio::time::timeout;
use url::Url;
use wiremock::Mock;
use wiremock::MockServer;
use wiremock::ResponseTemplate;
@@ -58,6 +59,7 @@ const LOGIN_ISSUER_ENV_VAR: &str = "CODEX_APP_SERVER_LOGIN_ISSUER";
struct CreateConfigTomlParams {
forced_method: Option<String>,
forced_workspace_id: Option<String>,
forced_workspace_ids: Option<Vec<String>>,
requires_openai_auth: Option<bool>,
base_url: Option<String>,
model_provider_id: Option<String>,
@@ -76,6 +78,13 @@ fn create_config_toml(codex_home: &Path, params: CreateConfigTomlParams) -> std:
};
let forced_workspace_line = if let Some(ws) = params.forced_workspace_id {
format!("forced_chatgpt_workspace_id = \"{ws}\"\n")
} else if let Some(workspaces) = params.forced_workspace_ids {
let workspaces = workspaces
.into_iter()
.map(|workspace_id| format!("\"{workspace_id}\""))
.collect::<Vec<_>>()
.join(", ");
format!("forced_chatgpt_workspace_id = [{workspaces}]\n")
} else {
String::new()
};
@@ -1454,6 +1463,45 @@ async fn login_account_chatgpt_includes_forced_workspace_query_param() -> Result
Ok(())
}
#[tokio::test]
// Serialize tests that launch the login server since it binds to a fixed port.
#[serial(login_port)]
async fn login_account_chatgpt_includes_forced_workspace_allowlist_query_param() -> Result<()> {
let codex_home = TempDir::new()?;
create_config_toml(
codex_home.path(),
CreateConfigTomlParams {
forced_workspace_ids: Some(vec!["ws-forced-a".to_string(), "ws-forced-b".to_string()]),
..Default::default()
},
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp.send_login_account_chatgpt_request().await?;
let resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let login: LoginAccountResponse = to_response(resp)?;
let LoginAccountResponse::Chatgpt { auth_url, .. } = login else {
bail!("unexpected login response: {login:?}");
};
let auth_url = Url::parse(&auth_url)?;
let allowed_workspace_ids = auth_url
.query_pairs()
.filter_map(|(key, value)| (key == "allowed_workspace_id").then(|| value.into_owned()))
.collect::<Vec<_>>();
assert_eq!(
allowed_workspace_ids,
vec!["ws-forced-a,ws-forced-b".to_string()]
);
Ok(())
}
#[tokio::test]
async fn get_account_no_auth() -> Result<()> {
let codex_home = TempDir::new()?;