Codex Handbook
config/src/loader/tests.rs 256 lines
use super::*;use codex_file_system::CopyOptions;use codex_file_system::CreateDirectoryOptions;use codex_file_system::ExecutorFileSystemFuture;use codex_file_system::FileMetadata;use codex_file_system::FileSystemReadStream;use codex_file_system::FileSystemSandboxContext;use codex_file_system::ReadDirectoryEntry;use codex_file_system::RemoveOptions;use codex_utils_path_uri::PathUri;use pretty_assertions::assert_eq;use tempfile::tempdir;struct TestFileSystem;impl ExecutorFileSystem for TestFileSystem {    fn canonicalize<'a>(        &'a self,        path: &'a PathUri,        _sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, PathUri> {        Box::pin(async move {            let path = path.to_abs_path()?;            let canonicalized = path.canonicalize()?;            Ok(PathUri::from_abs_path(&canonicalized))        })    }    fn read_file<'a>(        &'a self,        path: &'a PathUri,        _sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, Vec<u8>> {        Box::pin(async move {            let path = path.to_abs_path()?;            tokio::fs::read(path.as_path()).await        })    }    fn read_file_stream<'a>(        &'a self,        _path: &'a PathUri,        _sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, FileSystemReadStream> {        Box::pin(async {            Err(std::io::Error::new(                std::io::ErrorKind::Unsupported,                "test filesystem does not support streaming reads",            ))        })    }    fn write_file<'a>(        &'a self,        _path: &'a PathUri,        _contents: Vec<u8>,        _sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, ()> {        Box::pin(async move { unimplemented!("test filesystem only supports reads") })    }    fn create_directory<'a>(        &'a self,        _path: &'a PathUri,        _create_directory_options: CreateDirectoryOptions,        _sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, ()> {        Box::pin(async move { unimplemented!("test filesystem only supports reads") })    }    fn get_metadata<'a>(        &'a self,        _path: &'a PathUri,        _sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, FileMetadata> {        Box::pin(async move { unimplemented!("test filesystem only supports reads") })    }    fn read_directory<'a>(        &'a self,        _path: &'a PathUri,        _sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, Vec<ReadDirectoryEntry>> {        Box::pin(async move { unimplemented!("test filesystem only supports reads") })    }    fn remove<'a>(        &'a self,        _path: &'a PathUri,        _remove_options: RemoveOptions,        _sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, ()> {        Box::pin(async move { unimplemented!("test filesystem only supports reads") })    }    fn copy<'a>(        &'a self,        _source_path: &'a PathUri,        _destination_path: &'a PathUri,        _copy_options: CopyOptions,        _sandbox: Option<&'a FileSystemSandboxContext>,    ) -> ExecutorFileSystemFuture<'a, ()> {        Box::pin(async move { unimplemented!("test filesystem only supports reads") })    }}#[tokio::test]async fn profile_v2_rejects_matching_legacy_profile_in_base_user_config() {    let tmp = tempdir().expect("tempdir");    let selected_config = tmp.path().join("work.config.toml");    std::fs::write(        tmp.path().join(CONFIG_TOML_FILE),        r#"model = "gpt-main"[profiles.work]model = "gpt-work""#,    )    .expect("write default user config");    std::fs::write(&selected_config, r#"model = "gpt-work-v2""#)        .expect("write selected user config");    let mut overrides = LoaderOverrides::without_managed_config_for_tests();    overrides.user_config_path = Some(AbsolutePathBuf::resolve_path_against_base(        "work.config.toml",        tmp.path(),    ));    overrides.user_config_profile = Some("work".parse().expect("profile-v2 name"));    let err = load_config_layers_state(        &TestFileSystem,        tmp.path(),        /*cwd*/ None,        &[],        overrides,        &crate::NoopThreadConfigLoader,    )    .await    .expect_err("profile-v2 should reject a matching legacy profile in base user config");    assert_eq!(        err.kind(),        io::ErrorKind::InvalidData,        "a matching legacy profile should be a hard config error"    );    let message = err.to_string();    assert!(        message.contains("--profile `work` cannot be used"),        "unexpected error message: {message}"    );    assert!(        message.contains("config.toml"),        "unexpected error message: {message}"    );    assert!(        message.contains("[profiles.work]"),        "unexpected error message: {message}"    );    assert!(        message.contains("https://developers.openai.com/codex/config-advanced#profiles"),        "unexpected error message: {message}"    );}#[tokio::test]async fn profile_v2_rejects_matching_legacy_profile_selector_in_base_user_config() {    let tmp = tempdir().expect("tempdir");    let selected_config = tmp.path().join("work.config.toml");    std::fs::write(        tmp.path().join(CONFIG_TOML_FILE),        r#"profile = "work"model = "gpt-main""#,    )    .expect("write default user config");    std::fs::write(&selected_config, r#"model = "gpt-work-v2""#)        .expect("write selected user config");    let mut overrides = LoaderOverrides::without_managed_config_for_tests();    overrides.user_config_path = Some(AbsolutePathBuf::resolve_path_against_base(        "work.config.toml",        tmp.path(),    ));    overrides.user_config_profile = Some("work".parse().expect("profile-v2 name"));    let err = load_config_layers_state(        &TestFileSystem,        tmp.path(),        /*cwd*/ None,        &[],        overrides,        &crate::NoopThreadConfigLoader,    )    .await    .expect_err("profile-v2 should reject a matching legacy profile selector");    assert_eq!(        err.kind(),        io::ErrorKind::InvalidData,        "a matching legacy profile selector should be a hard config error"    );    let message = err.to_string();    assert!(        message.contains("--profile `work` cannot be used"),        "unexpected error message: {message}"    );    assert!(        message.contains("profile = \"work\""),        "unexpected error message: {message}"    );    assert!(        message.contains("work.config.toml"),        "unexpected error message: {message}"    );}#[tokio::test]async fn profile_v2_allows_unrelated_legacy_profiles_in_base_user_config() {    let tmp = tempdir().expect("tempdir");    let selected_config = tmp.path().join("work.config.toml");    std::fs::write(        tmp.path().join(CONFIG_TOML_FILE),        r#"model = "gpt-main"[profiles.dev]model = "gpt-dev""#,    )    .expect("write default user config");    std::fs::write(&selected_config, r#"model = "gpt-work-v2""#)        .expect("write selected user config");    let mut overrides = LoaderOverrides::without_managed_config_for_tests();    overrides.user_config_path = Some(AbsolutePathBuf::resolve_path_against_base(        "work.config.toml",        tmp.path(),    ));    overrides.user_config_profile = Some("work".parse().expect("profile-v2 name"));    load_config_layers_state(        &TestFileSystem,        tmp.path(),        /*cwd*/ None,        &[],        overrides,        &crate::NoopThreadConfigLoader,    )    .await    .expect("profile-v2 should allow unrelated legacy profiles in base user config");}