use super::*; use crate::ConfigLayerEntry; use crate::ConfigLayerSource; use crate::ConfigRequirements; use crate::config_toml::ConfigToml; use crate::diagnostics::TextPosition; use crate::diagnostics::TextRange; use pretty_assertions::assert_eq; use std::path::PathBuf; #[test] fn ignored_toml_field_errors_accept_non_file_source_names() { let source_name = "com.openai.codex:config_toml_base64"; let contents = r#" model = "gpt-5" unknown_key = true"#; let value = toml::from_str::(contents).expect("valid TOML"); let error = config_error_from_ignored_toml_value_fields_for_source_name::( source_name, contents, value, ) .expect("unknown field error"); assert_eq!( error, ConfigError::new( PathBuf::from(source_name), TextRange { start: TextPosition { line: 3, column: 1 }, end: TextPosition { line: 3, column: 11, }, }, "unknown configuration field `unknown_key`", ) ); } #[test] fn type_errors_take_precedence_over_ignored_fields() { let path = Path::new("/tmp/config.toml"); let contents = r#" model_context_window = "wide" unknown_key = true"#; let error = config_error_from_ignored_toml_fields::(path, contents).expect("type error"); assert_eq!( error, ConfigError::new( path.to_path_buf(), TextRange { start: TextPosition { line: 2, column: 24, }, end: TextPosition { line: 2, column: 29, }, }, "invalid type: string \"wide\", expected i64", ) ); } #[test] fn strict_config_rejects_unknown_feature_key() { let path = Path::new("/tmp/config.toml"); let contents = r#" [features] foo = true"#; let error = config_error_from_ignored_toml_fields::(path, contents) .expect("unknown feature error"); assert_eq!( error, ConfigError::new( path.to_path_buf(), TextRange { start: TextPosition { line: 3, column: 1 }, end: TextPosition { line: 3, column: 3 }, }, "unknown configuration field `features.foo`", ) ); } #[test] fn strict_config_accepts_tool_registry_config() { let path = Path::new("/tmp/config.toml"); for contents in [ "[features.tool_registry]\nerror_on_tool_collisions = true\n", "[profiles.work.features.tool_registry]\nerror_on_tool_collisions = true\n", "[features.tool_registry]\nturn_metadata_includes_tool_info = true\n", "[profiles.work.features.tool_registry]\nturn_metadata_includes_tool_info = true\n", ] { assert_eq!( config_error_from_ignored_toml_fields::(path, contents), None ); } assert!( config_error_from_ignored_toml_fields::( path, "[features.tool_registry]\nunknown = true\n", ) .is_some() ); } #[test] fn strict_config_rejects_unknown_profile_feature_key() { let path = Path::new("/tmp/config.toml"); let contents = r#" [profiles.work.features] foo = true"#; let error = config_error_from_ignored_toml_fields::(path, contents) .expect("unknown feature error"); assert_eq!( error, ConfigError::new( path.to_path_buf(), TextRange { start: TextPosition { line: 3, column: 1 }, end: TextPosition { line: 3, column: 3 }, }, "unknown configuration field `profiles.work.features.foo`", ) ); } #[test] fn strict_config_accepts_opaque_desktop_keys() { let path = Path::new("/tmp/config.toml"); let contents = r#" [desktop] appearanceTheme = "dark" [desktop.workspace] collapsed = true"#; let error = config_error_from_ignored_toml_fields::(path, contents); assert_eq!(error, None); } fn layer(source: ConfigLayerSource, contents: &str) -> ConfigLayerEntry { ConfigLayerEntry::new(source, toml::from_str(contents).unwrap()) } #[test] fn checks_merged_config_and_ignores_disabled_layers() { let layers = vec![ layer( ConfigLayerSource::EnterpriseManaged { id: "cfg".into(), name: "Defaults".into(), }, r#" [model_providers.custom] name = "Custom" wire_api = "responses" unknown_timeout = 1 [model_providers.custom.http_headers] custom_header = "accepted" "#, ), ConfigLayerEntry::new_disabled( ConfigLayerSource::SessionFlags, toml::from_str("disabled_unknown = true").unwrap(), "not trusted", ), layer( ConfigLayerSource::SessionFlags, r#" [model_providers.custom] unknown_timeout = 42 [profiles.work.features] include_view_image_tool = false "#, ), ]; let config = ConfigLayerStack::new( layers, ConfigRequirements::default(), ConfigRequirementsToml::default(), ) .unwrap(); assert_eq!( ignored_config_warning(&config, &[]).unwrap(), "Codex is ignoring 3 unrecognized configuration settings. Check for typos or deprecated settings.\n enterprise-managed (Defaults, cfg): `model_providers.custom.unknown_timeout` is ignored.\n session-flags: `model_providers.custom.unknown_timeout` is ignored.\n session-flags: `profiles.work.features.include_view_image_tool` is ignored. Use [features].view_image to configure the image tool." ); } #[test] fn removed_private_desktop_setting_has_migration_hint() { let config = ConfigLayerStack::new( vec![layer( ConfigLayerSource::SessionFlags, "[windows]\nsandbox_private_desktop = false", )], ConfigRequirements::default(), ConfigRequirementsToml::default(), ) .unwrap(); assert_eq!( ignored_config_warning(&config, &[]).unwrap(), "Codex is ignoring 1 unrecognized configuration setting. Check for typos or deprecated settings.\n session-flags: `windows.sandbox_private_desktop` is ignored. Remove windows.sandbox_private_desktop; legacy Windows sandboxes always use a private desktop." ); }