fix(config): correct cross-platform paths and v8 config updates
- Extend OAuth directory recovery to Windows and Linux - Align credential path resolution and log directory selection with the core - Update v8 TLS and logging fields without requiring a version marker - Support flow-style YAML mappings and preserve null legacy TLS fields - Add regression tests for path recovery and config updates
This commit is contained in:
1 parent
6dd8646afc
commit
7c5b65a153
6 files changed
+445
-172
No files matched your search
@@ -837,26 +837,78 @@ pub(crate) fn fixed_oauth_dir() -> Result<PathBuf, String> {
|
||||
Ok(core_base_dir()?.join(OAUTH_DIR_NAME))
|
||||
}
|
||||
|
||||
pub(crate) fn auth_dir_path_for_core(auth_dir: &str, install_dir: &Path) -> PathBuf {
|
||||
if auth_dir.trim() == DEFAULT_AUTH_DIR {
|
||||
return install_dir
|
||||
.parent()
|
||||
.map(|parent| parent.join(OAUTH_DIR_NAME))
|
||||
.unwrap_or_else(|| install_dir.join(auth_dir));
|
||||
}
|
||||
let auth_dir = PathBuf::from(auth_dir);
|
||||
if auth_dir.is_absolute() {
|
||||
auth_dir
|
||||
pub(crate) fn auth_dir_path_for_core(auth_dir: &str, install_dir: &Path) -> Result<PathBuf, String> {
|
||||
let variable = if cfg!(windows) { "USERPROFILE" } else { "HOME" };
|
||||
let user_home = env::var_os(variable).filter(|value| !value.is_empty()).map(PathBuf::from);
|
||||
auth_dir_path_for_core_with_home(auth_dir, install_dir, user_home.as_deref())
|
||||
}
|
||||
|
||||
pub(crate) fn auth_dir_path_for_core_with_home(
|
||||
auth_dir: &str,
|
||||
install_dir: &Path,
|
||||
user_home: Option<&Path>,
|
||||
) -> Result<PathBuf, String> {
|
||||
let auth_dir = if auth_dir.is_empty() { "~/.cli-proxy-api" } else { auth_dir };
|
||||
let path = if let Some(remainder) = auth_dir.strip_prefix('~') {
|
||||
let home = user_home.ok_or_else(|| "Unable to determine user home for credentials directory".to_string())?;
|
||||
home.join(remainder.trim_start_matches(['/', '\\']).replace('\\', "/"))
|
||||
} else {
|
||||
install_dir.join(auth_dir)
|
||||
PathBuf::from(auth_dir)
|
||||
};
|
||||
Ok(resolve_core_relative_path(&path, install_dir))
|
||||
}
|
||||
|
||||
fn resolve_core_relative_path(path: &Path, install_dir: &Path) -> PathBuf {
|
||||
normalize_path_lexically(&if path.is_absolute() {
|
||||
path.to_path_buf()
|
||||
} else {
|
||||
install_dir.join(path)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn core_logs_dir_path(auth_dir: &str, install_dir: &Path) -> Result<PathBuf, String> {
|
||||
let writable_base = ["WRITABLE_PATH", "writable_path"].into_iter().find_map(|key| {
|
||||
env::var(key).ok().filter(|value| !value.trim().is_empty())
|
||||
});
|
||||
core_logs_dir_path_with_base(auth_dir, install_dir, writable_base.as_deref())
|
||||
}
|
||||
|
||||
pub(crate) fn core_logs_dir_path_with_base(
|
||||
auth_dir: &str,
|
||||
install_dir: &Path,
|
||||
writable_base: Option<&str>,
|
||||
) -> Result<PathBuf, String> {
|
||||
if let Some(base) = writable_base.map(str::trim).filter(|base| !base.is_empty()) {
|
||||
return Ok(resolve_core_relative_path(&Path::new(base).join("logs"), install_dir));
|
||||
}
|
||||
let local_logs = install_dir.join("logs");
|
||||
if directory_is_writable(&local_logs) {
|
||||
return Ok(normalize_path_lexically(&local_logs));
|
||||
}
|
||||
Ok(auth_dir_path_for_core(auth_dir, install_dir)?.join("logs"))
|
||||
}
|
||||
|
||||
pub(crate) fn core_logs_dir_path(auth_dir: &str, install_dir: &Path) -> PathBuf {
|
||||
auth_dir_path_for_core(auth_dir, install_dir).join("logs")
|
||||
fn directory_is_writable(path: &Path) -> bool {
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
static SEQUENCE: AtomicU64 = AtomicU64::new(0);
|
||||
if !path.is_dir() {
|
||||
return false;
|
||||
}
|
||||
let probe = path.join(format!(
|
||||
".cpa-gui-log-probe-{}-{}-{}",
|
||||
std::process::id(),
|
||||
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_nanos(),
|
||||
SEQUENCE.fetch_add(1, Ordering::Relaxed)
|
||||
));
|
||||
let Ok(file) = File::options().write(true).create_new(true).open(&probe) else {
|
||||
return false;
|
||||
};
|
||||
drop(file);
|
||||
let _ = fs::remove_file(probe);
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
fn normalize_path_lexically(path: &Path) -> PathBuf {
|
||||
let mut normalized = PathBuf::new();
|
||||
for component in path.components() {
|
||||
@@ -1063,7 +1115,7 @@ pub(crate) fn migrate_auth_dir_from_macos_app_bundle(
|
||||
install_dir: &Path,
|
||||
persistent_auth_dir: &Path,
|
||||
) -> Result<bool, String> {
|
||||
let source = normalize_path_lexically(&auth_dir_path_for_core(&config.auth_dir, install_dir));
|
||||
let source = auth_dir_path_for_core(&config.auth_dir, install_dir)?;
|
||||
if !auth_dir_is_inside_macos_app_bundle(&source) {
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -1080,7 +1132,6 @@ pub(crate) fn migrate_auth_dir_from_macos_app_bundle(
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
fn auth_directory_has_json_files(path: &Path) -> Result<bool, String> {
|
||||
let entries = match fs::read_dir(path) {
|
||||
Ok(entries) => entries,
|
||||
@@ -1110,14 +1161,12 @@ fn auth_directory_has_json_files(path: &Path) -> Result<bool, String> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
pub(crate) fn recover_macos_relative_oauth_dir(
|
||||
pub(crate) fn recover_relative_oauth_dir(
|
||||
config: &mut GuiConfigFile,
|
||||
install_dir: &Path,
|
||||
persistent_auth_dir: &Path,
|
||||
) -> Result<bool, String> {
|
||||
let current_auth_dir =
|
||||
normalize_path_lexically(&auth_dir_path_for_core(&config.auth_dir, install_dir));
|
||||
let current_auth_dir = auth_dir_path_for_core(&config.auth_dir, install_dir)?;
|
||||
if current_auth_dir != normalize_path_lexically(&install_dir.join(OAUTH_DIR_NAME)) {
|
||||
return Ok(false);
|
||||
}
|
||||
@@ -1130,23 +1179,18 @@ pub(crate) fn recover_macos_relative_oauth_dir(
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn migrate_packaged_macos_auth_dir(config: &mut GuiConfigFile) -> Result<bool, String> {
|
||||
let install_dir = core_install_dir()?;
|
||||
let persistent_auth_dir = fixed_oauth_dir()?;
|
||||
migrate_macos_auth_dir_at(config, &install_dir, &persistent_auth_dir)
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
pub(crate) fn migrate_macos_auth_dir_at(
|
||||
pub(crate) fn migrate_auth_dir_at(
|
||||
config: &mut GuiConfigFile,
|
||||
install_dir: &Path,
|
||||
persistent_auth_dir: &Path,
|
||||
) -> Result<bool, String> {
|
||||
let mut candidate = config.clone();
|
||||
#[cfg(target_os = "macos")]
|
||||
let migrated =
|
||||
migrate_auth_dir_from_macos_app_bundle(&mut candidate, install_dir, persistent_auth_dir)?;
|
||||
let recovered = recover_macos_relative_oauth_dir(&mut candidate, install_dir, persistent_auth_dir)?;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let migrated = false;
|
||||
let recovered = recover_relative_oauth_dir(&mut candidate, install_dir, persistent_auth_dir)?;
|
||||
if migrated || recovered {
|
||||
patch_core_auth_dir_at(&install_dir.join(CORE_CONFIG_FILE), &candidate.auth_dir)
|
||||
.map_err(|error| format!("Failed to update kernel OAuth directory: {error}"))?;
|
||||
@@ -1411,7 +1455,7 @@ pub(crate) fn load_or_create_gui_config() -> Result<GuiConfigFile, String> {
|
||||
}
|
||||
if !config.auth_dir.trim().is_empty() {
|
||||
let install_dir = core_install_dir()?;
|
||||
let auth_dir = auth_dir_path_for_core(&config.auth_dir, &install_dir);
|
||||
let auth_dir = auth_dir_path_for_core(&config.auth_dir, &install_dir)?;
|
||||
fs::create_dir_all(&auth_dir)
|
||||
.map_err(|error| format!("Failed to create credentials directory {}: {error}", path_to_string(&auth_dir)))?;
|
||||
}
|
||||
@@ -1651,6 +1695,16 @@ pub(crate) fn is_loopback_host(host: &str) -> bool {
|
||||
}
|
||||
|
||||
pub(crate) fn sanitize_gui_config(config: &mut GuiConfigFile) -> Result<bool, String> {
|
||||
let install_dir = core_install_dir()?;
|
||||
let persistent_auth_dir = fixed_oauth_dir()?;
|
||||
sanitize_gui_config_at(config, &install_dir, &persistent_auth_dir)
|
||||
}
|
||||
|
||||
pub(crate) fn sanitize_gui_config_at(
|
||||
config: &mut GuiConfigFile,
|
||||
install_dir: &Path,
|
||||
persistent_auth_dir: &Path,
|
||||
) -> Result<bool, String> {
|
||||
let mut changed = false;
|
||||
let normalized_locale = normalize_app_locale(&config.locale);
|
||||
if config.locale != normalized_locale {
|
||||
@@ -1681,18 +1735,14 @@ pub(crate) fn sanitize_gui_config(config: &mut GuiConfigFile) -> Result<bool, St
|
||||
config.allow_lan = allow_lan;
|
||||
changed = true;
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
match migrate_packaged_macos_auth_dir(config) {
|
||||
Ok(migrated) => changed |= migrated,
|
||||
Err(error) => {
|
||||
eprintln!("Failed to recover macOS OAuth directory; keeping existing configuration: {error}");
|
||||
}
|
||||
match migrate_auth_dir_at(config, install_dir, persistent_auth_dir) {
|
||||
Ok(migrated) => changed |= migrated,
|
||||
Err(error) => {
|
||||
eprintln!("Failed to recover OAuth directory; keeping existing configuration: {error}");
|
||||
}
|
||||
}
|
||||
let legacy_default_auth_dir = fixed_oauth_dir()?;
|
||||
if config.auth_dir.trim().is_empty()
|
||||
|| Path::new(config.auth_dir.trim()) == legacy_default_auth_dir
|
||||
|| Path::new(config.auth_dir.trim()) == persistent_auth_dir
|
||||
{
|
||||
config.auth_dir = DEFAULT_AUTH_DIR.to_string();
|
||||
changed = true;
|
||||
@@ -1958,7 +2008,7 @@ pub(crate) fn validate_gui_config(config: &GuiConfigFile) -> Result<(), String>
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let auth_dir = auth_dir_path_for_core(&config.auth_dir, &core_install_dir()?);
|
||||
let auth_dir = auth_dir_path_for_core(&config.auth_dir, &core_install_dir()?)?;
|
||||
if auth_dir_is_inside_macos_app_bundle(&auth_dir) {
|
||||
return Err("OAuth credentials directory cannot be inside a macOS application bundle".to_string());
|
||||
}
|
||||
|
||||
@@ -185,23 +185,29 @@ pub(crate) fn patch_core_tls_settings_yaml(
|
||||
settings: &CoreTlsSettings,
|
||||
) -> Result<Option<String>, String> {
|
||||
patch_core_yaml_document(content, |document| {
|
||||
let v8 = core_config_uses_v8(document);
|
||||
let root = document.as_mapping()
|
||||
.ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?;
|
||||
let v8 = core_config_uses_v8(document)
|
||||
|| nested_yaml_value(root, &["server", "tls"]).is_some();
|
||||
let has_legacy_mapping = yaml_mapping_value(root, "tls")
|
||||
.is_some_and(serde_norway::Value::is_mapping);
|
||||
let mut changed = false;
|
||||
changed |= set_core_yaml_path_value(
|
||||
document,
|
||||
if v8 { &["server", "tls", "enable"] } else { &["tls", "enable"] },
|
||||
serde_norway::Value::Bool(settings.enabled),
|
||||
)?;
|
||||
changed |= set_core_yaml_path_value(
|
||||
document,
|
||||
if v8 { &["server", "tls", "cert"] } else { &["tls", "cert"] },
|
||||
serde_norway::Value::String(settings.cert.clone()),
|
||||
)?;
|
||||
changed |= set_core_yaml_path_value(
|
||||
document,
|
||||
if v8 { &["server", "tls", "key"] } else { &["tls", "key"] },
|
||||
serde_norway::Value::String(settings.key.clone()),
|
||||
)?;
|
||||
for (key, value) in [
|
||||
("enable", serde_norway::Value::Bool(settings.enabled)),
|
||||
("cert", serde_norway::Value::String(settings.cert.clone())),
|
||||
("key", serde_norway::Value::String(settings.key.clone())),
|
||||
] {
|
||||
let legacy_path = ["tls", key];
|
||||
let v8_path = ["server", "tls", key];
|
||||
changed |= set_core_yaml_path_value(
|
||||
document,
|
||||
if v8 { &v8_path } else { &legacy_path },
|
||||
value.clone(),
|
||||
)?;
|
||||
if v8 && has_legacy_mapping {
|
||||
changed |= set_core_yaml_path_value(document, &["tls", key], value)?;
|
||||
}
|
||||
}
|
||||
Ok(changed)
|
||||
})
|
||||
}
|
||||
@@ -431,7 +437,6 @@ pub(crate) fn patch_core_management_secret_key(secret_key: &str) -> Result<(), S
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
pub(crate) fn patch_core_auth_dir_at(config_path: &Path, auth_dir: &str) -> Result<(), String> {
|
||||
patch_existing_core_config_at(config_path, |document| {
|
||||
set_core_yaml_auth_dir(document, auth_dir)
|
||||
@@ -442,26 +447,12 @@ pub(crate) fn set_core_yaml_auth_dir(
|
||||
document: &mut serde_norway::Value,
|
||||
auth_dir: &str,
|
||||
) -> Result<bool, String> {
|
||||
let root = document
|
||||
.as_mapping()
|
||||
.ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?;
|
||||
let nested = core_config_uses_v8(document)
|
||||
|| nested_yaml_value(root, &["oauth", "auth-dir"]).is_some();
|
||||
let has_legacy = yaml_mapping_value(root, "auth-dir").is_some();
|
||||
let value = serde_norway::Value::String(auth_dir.to_string());
|
||||
let mut changed = set_core_yaml_path_value(
|
||||
set_core_yaml_schema_value(
|
||||
document,
|
||||
if nested {
|
||||
&["oauth", "auth-dir"]
|
||||
} else {
|
||||
&["auth-dir"]
|
||||
},
|
||||
value.clone(),
|
||||
)?;
|
||||
if nested && has_legacy {
|
||||
changed |= set_core_yaml_path_value(document, &["auth-dir"], value)?;
|
||||
}
|
||||
Ok(changed)
|
||||
&["auth-dir"],
|
||||
&["oauth", "auth-dir"],
|
||||
serde_norway::Value::String(auth_dir.to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn patch_core_plugins_enabled(enabled: bool) -> Result<(), String> {
|
||||
@@ -477,63 +468,64 @@ pub(crate) fn patch_core_plugins_enabled(enabled: bool) -> Result<(), String> {
|
||||
|
||||
pub(crate) fn patch_core_request_log(enabled: bool) -> Result<(), String> {
|
||||
patch_existing_core_config(|document| {
|
||||
let v8 = core_config_uses_v8(document);
|
||||
set_core_yaml_path_value(
|
||||
set_core_yaml_schema_value(
|
||||
document,
|
||||
if v8 { &["observability", "logs", "request-log"] } else { &["request-log"] },
|
||||
&["request-log"],
|
||||
&["observability", "logs", "request-log"],
|
||||
serde_norway::Value::Bool(enabled),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn patch_core_logging_settings(settings: &CoreConfigSettings) -> Result<(), String> {
|
||||
let settings = settings.clone();
|
||||
patch_existing_core_config(move |document| {
|
||||
let v8 = core_config_uses_v8(document);
|
||||
let mut changed = false;
|
||||
for (key, value) in [
|
||||
("debug", serde_norway::Value::Bool(settings.debug)),
|
||||
(
|
||||
"commercial-mode",
|
||||
serde_norway::Value::Bool(settings.commercial_mode),
|
||||
),
|
||||
(
|
||||
"logging-to-file",
|
||||
serde_norway::Value::Bool(settings.logging_to_file),
|
||||
),
|
||||
(
|
||||
"logs-max-total-size-mb",
|
||||
serde_norway::to_value(settings.logs_max_total_size_mb)
|
||||
.map_err(|err| format!("Failed to serialize log size limit: {err}"))?,
|
||||
),
|
||||
(
|
||||
"error-logs-max-files",
|
||||
serde_norway::to_value(settings.error_logs_max_files)
|
||||
.map_err(|err| format!("Failed to serialize error log retention count: {err}"))?,
|
||||
),
|
||||
(
|
||||
"usage-statistics-enabled",
|
||||
serde_norway::Value::Bool(settings.usage_statistics_enabled),
|
||||
),
|
||||
(
|
||||
"redis-usage-queue-retention-seconds",
|
||||
serde_norway::to_value(settings.redis_usage_queue_retention_seconds)
|
||||
.map_err(|err| format!("Failed to serialize Redis usage queue retention: {err}"))?,
|
||||
),
|
||||
] {
|
||||
let path: &[&str] = if v8 {
|
||||
match key {
|
||||
"commercial-mode" => &["server", "commercial-mode"],
|
||||
"usage-statistics-enabled" | "redis-usage-queue-retention-seconds" => &["observability", "usage", key],
|
||||
_ => &["observability", "logs", key],
|
||||
}
|
||||
} else {
|
||||
&[key]
|
||||
};
|
||||
changed |= set_core_yaml_path_value(document, path, value)?;
|
||||
}
|
||||
Ok(changed)
|
||||
})
|
||||
patch_existing_core_config(|document| apply_core_logging_settings(document, settings))
|
||||
}
|
||||
|
||||
pub(crate) fn apply_core_logging_settings(
|
||||
document: &mut serde_norway::Value,
|
||||
settings: &CoreConfigSettings,
|
||||
) -> Result<bool, String> {
|
||||
let mut changed = false;
|
||||
for (key, value) in [
|
||||
("debug", serde_norway::Value::Bool(settings.debug)),
|
||||
(
|
||||
"commercial-mode",
|
||||
serde_norway::Value::Bool(settings.commercial_mode),
|
||||
),
|
||||
(
|
||||
"logging-to-file",
|
||||
serde_norway::Value::Bool(settings.logging_to_file),
|
||||
),
|
||||
(
|
||||
"logs-max-total-size-mb",
|
||||
serde_norway::to_value(settings.logs_max_total_size_mb)
|
||||
.map_err(|err| format!("Failed to serialize log size limit: {err}"))?,
|
||||
),
|
||||
(
|
||||
"error-logs-max-files",
|
||||
serde_norway::to_value(settings.error_logs_max_files)
|
||||
.map_err(|err| format!("Failed to serialize error log retention count: {err}"))?,
|
||||
),
|
||||
(
|
||||
"usage-statistics-enabled",
|
||||
serde_norway::Value::Bool(settings.usage_statistics_enabled),
|
||||
),
|
||||
(
|
||||
"redis-usage-queue-retention-seconds",
|
||||
serde_norway::to_value(settings.redis_usage_queue_retention_seconds)
|
||||
.map_err(|err| format!("Failed to serialize Redis usage queue retention: {err}"))?,
|
||||
),
|
||||
] {
|
||||
let path: &[&str] = match key {
|
||||
"commercial-mode" => &["server", "commercial-mode"],
|
||||
"usage-statistics-enabled" | "redis-usage-queue-retention-seconds" => {
|
||||
&["observability", "usage", key]
|
||||
}
|
||||
_ => &["observability", "logs", key],
|
||||
};
|
||||
changed |= set_core_yaml_schema_value(document, &[key], path, value)?;
|
||||
}
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
pub(crate) fn patch_core_routing_strategy(strategy: &str) -> Result<(), String> {
|
||||
@@ -693,7 +685,7 @@ pub(crate) fn render_yaml_value_changes(
|
||||
}
|
||||
for (parent_path, entries) in missing_nested_groups {
|
||||
if let Some(updated_content) =
|
||||
insert_yaml_block_mapping_values_at_path(&editable_content, &parent_path, &entries)?
|
||||
insert_yaml_mapping_values_at_path(&editable_content, &parent_path, &entries)?
|
||||
{
|
||||
editable_content = updated_content;
|
||||
} else {
|
||||
@@ -894,11 +886,38 @@ pub(crate) fn yaml_value_at_path<'a>(
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn insert_yaml_block_mapping_values_at_path(
|
||||
pub(crate) fn insert_yaml_mapping_values_at_path(
|
||||
content: &str,
|
||||
parent_path: &[String],
|
||||
entries: &[(String, serde_norway::Value)],
|
||||
) -> Result<Option<String>, String> {
|
||||
let file = content.parse::<yaml_edit::YamlFile>()
|
||||
.map_err(|error| format!("Failed to parse editable kernel configuration: {error}"))?;
|
||||
let document = file.document()
|
||||
.ok_or_else(|| "Kernel configuration has no YAML document".to_string())?;
|
||||
let root = document.as_mapping()
|
||||
.ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?;
|
||||
let node = yaml_edit_node_at_path(&root, parent_path);
|
||||
if let Some(mapping) = node.as_ref().and_then(|node| node.as_mapping())
|
||||
.filter(|mapping| mapping.is_flow_style())
|
||||
{
|
||||
let range = mapping.byte_range();
|
||||
let start = range.start as usize;
|
||||
let opening = content[start..range.end as usize].find('{')
|
||||
.ok_or_else(|| "Flow mapping has no opening brace".to_string())?;
|
||||
let insertion_point = start + opening + 1;
|
||||
let fields = entries.iter().map(|(key, value)| {
|
||||
serde_json::to_string(value)
|
||||
.map(|value| format!("{}: {value}", render_yaml_mapping_key(key)))
|
||||
.map_err(|error| format!("Failed to serialize kernel configuration value: {error}"))
|
||||
}).collect::<Result<Vec<_>, _>>()?.join(", ");
|
||||
let separator = if mapping.is_empty() { " " } else { ", " };
|
||||
return Ok(Some(format!(
|
||||
"{} {fields}{separator}{}",
|
||||
&content[..insertion_point],
|
||||
&content[insertion_point..]
|
||||
)));
|
||||
}
|
||||
let mut offset = 0;
|
||||
let mut parent_end = None;
|
||||
let mut parent_indent = 0;
|
||||
@@ -1243,8 +1262,19 @@ pub(crate) fn set_core_yaml_schema_value(
|
||||
v8_path: &[&str],
|
||||
value: serde_norway::Value,
|
||||
) -> Result<bool, String> {
|
||||
let path = if core_config_uses_v8(document) { v8_path } else { legacy_path };
|
||||
set_core_yaml_path_value(document, path, value)
|
||||
let root = document.as_mapping()
|
||||
.ok_or_else(|| "Kernel configuration root must be a YAML mapping".to_string())?;
|
||||
let v8 = core_config_uses_v8(document) || nested_yaml_value(root, v8_path).is_some();
|
||||
let has_legacy = nested_yaml_value(root, legacy_path).is_some();
|
||||
let mut changed = set_core_yaml_path_value(
|
||||
document,
|
||||
if v8 { v8_path } else { legacy_path },
|
||||
value.clone(),
|
||||
)?;
|
||||
if v8 && has_legacy {
|
||||
changed |= set_core_yaml_path_value(document, legacy_path, value)?;
|
||||
}
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
pub(crate) fn patch_core_api_keys_yaml(
|
||||
|
||||
@@ -1388,8 +1388,8 @@ impl std::fmt::Display for CoreStartupFailure {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn core_start_log_path(install_dir: &Path, auth_dir: &str) -> PathBuf {
|
||||
core_logs_dir_path(auth_dir, install_dir).join("core-start-output.log")
|
||||
pub(crate) fn core_start_log_path(install_dir: &Path, auth_dir: &str) -> Result<PathBuf, String> {
|
||||
Ok(core_logs_dir_path(auth_dir, install_dir)?.join("core-start-output.log"))
|
||||
}
|
||||
|
||||
pub(crate) fn core_start_stdio(log_path: &Path) -> io::Result<(Stdio, Stdio)> {
|
||||
@@ -1510,7 +1510,7 @@ pub(crate) fn start_core_process_inner(
|
||||
let gui_config = &resolved_config;
|
||||
let install_dir = core_install_dir()?;
|
||||
if !gui_config.auth_dir.trim().is_empty() {
|
||||
let auth_dir = auth_dir_path_for_core(&gui_config.auth_dir, &install_dir);
|
||||
let auth_dir = auth_dir_path_for_core(&gui_config.auth_dir, &install_dir)?;
|
||||
fs::create_dir_all(&auth_dir)
|
||||
.map_err(|error| format!("Failed to create credentials directory {}: {error}", path_to_string(&auth_dir)))?;
|
||||
}
|
||||
@@ -1534,7 +1534,7 @@ pub(crate) fn start_core_process_inner(
|
||||
|
||||
let config_path = merge_core_config_for_start(&install_dir, gui_config)?;
|
||||
let config_path = path_to_string(&config_path);
|
||||
let log_path = core_start_log_path(&install_dir, &gui_config.auth_dir);
|
||||
let log_path = core_start_log_path(&install_dir, &gui_config.auth_dir)?;
|
||||
let start_once = || {
|
||||
start_core_process_once(
|
||||
process_state,
|
||||
|
||||
@@ -133,7 +133,7 @@ pub(crate) fn open_auth_files_directory(
|
||||
) -> Result<(), String> {
|
||||
let config = gui_config_state.snapshot()?;
|
||||
let install_dir = core_install_dir()?;
|
||||
let auth_dir = auth_dir_path_for_core(&config.auth_dir, &install_dir);
|
||||
let auth_dir = auth_dir_path_for_core(&config.auth_dir, &install_dir)?;
|
||||
fs::create_dir_all(&auth_dir)
|
||||
.map_err(|error| format!("Failed to create credentials directory {}: {error}", path_to_string(&auth_dir)))?;
|
||||
open_directory_in_file_manager(&auth_dir)
|
||||
@@ -145,7 +145,7 @@ pub(crate) fn open_core_logs_directory(
|
||||
) -> Result<(), String> {
|
||||
let config = gui_config_state.snapshot()?;
|
||||
let install_dir = core_install_dir()?;
|
||||
let logs_dir = core_logs_dir_path(&config.auth_dir, &install_dir);
|
||||
let logs_dir = core_logs_dir_path(&config.auth_dir, &install_dir)?;
|
||||
fs::create_dir_all(&logs_dir)
|
||||
.map_err(|error| format!("Failed to create log directory {}: {error}", path_to_string(&logs_dir)))?;
|
||||
|
||||
@@ -474,7 +474,7 @@ mod tests {
|
||||
let install_dir = base_dir.join("cpa-core");
|
||||
|
||||
assert_eq!(
|
||||
core_logs_dir_path("../oauth", &install_dir),
|
||||
core_logs_dir_path("../oauth", &install_dir).unwrap(),
|
||||
base_dir.join("oauth").join("logs")
|
||||
);
|
||||
}
|
||||
@@ -485,7 +485,7 @@ mod tests {
|
||||
let auth_dir = PathBuf::from("custom-auth");
|
||||
|
||||
assert_eq!(
|
||||
core_logs_dir_path(auth_dir.to_str().unwrap(), &install_dir),
|
||||
core_logs_dir_path(auth_dir.to_str().unwrap(), &install_dir).unwrap(),
|
||||
install_dir.join(auth_dir).join("logs")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ fn default_auth_directory_is_relative_and_legacy_absolute_value_is_migrated() {
|
||||
let base_dir = agent_test_home("relative-default-auth-dir");
|
||||
let install_dir = base_dir.join("cpa-core");
|
||||
assert_eq!(
|
||||
auth_dir_path_for_core(DEFAULT_AUTH_DIR, &install_dir),
|
||||
auth_dir_path_for_core(DEFAULT_AUTH_DIR, &install_dir).unwrap(),
|
||||
base_dir.join(OAUTH_DIR_NAME)
|
||||
);
|
||||
|
||||
@@ -205,8 +205,161 @@ fn default_auth_directory_is_relative_and_legacy_absolute_value_is_migrated() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_relative_oauth_dir_recovers_existing_persistent_credentials() {
|
||||
let root = agent_test_home("macos-relative-oauth-dir");
|
||||
fn path_configuration_expands_auth_home() {
|
||||
let variable = if cfg!(windows) { "USERPROFILE" } else { "HOME" };
|
||||
let user_home = PathBuf::from(env::var_os(variable).unwrap());
|
||||
let install_dir = agent_test_home("auth-home-path").join("cpa-core");
|
||||
assert_eq!(
|
||||
auth_dir_path_for_core("~/.cli-proxy-api", &install_dir).unwrap(),
|
||||
user_home.join(".cli-proxy-api")
|
||||
);
|
||||
fs::remove_dir_all(install_dir.parent().unwrap()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_configuration_uses_existing_core_logs() {
|
||||
let root = agent_test_home("existing-core-logs");
|
||||
let install_dir = root.join("cpa-core");
|
||||
let logs_dir = install_dir.join("logs");
|
||||
fs::create_dir_all(&logs_dir).unwrap();
|
||||
fs::write(logs_dir.join("main.log"), b"existing-log").unwrap();
|
||||
assert_eq!(core_logs_dir_path_with_base(DEFAULT_AUTH_DIR, &install_dir, None).unwrap(), logs_dir);
|
||||
assert_eq!(fs::read(logs_dir.join("main.log")).unwrap(), b"existing-log");
|
||||
assert_eq!(fs::read_dir(&logs_dir).unwrap().count(), 1);
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_configuration_respects_log_base_and_fallbacks() {
|
||||
let root = agent_test_home("core-log-base");
|
||||
let install_dir = root.join("cpa-core");
|
||||
fs::create_dir_all(&install_dir).unwrap();
|
||||
let fallback = root.join("oauth").join("logs");
|
||||
assert_eq!(
|
||||
core_logs_dir_path_with_base(DEFAULT_AUTH_DIR, &install_dir, None).unwrap(),
|
||||
fallback
|
||||
);
|
||||
fs::write(install_dir.join("logs"), b"not a directory").unwrap();
|
||||
assert_eq!(
|
||||
core_logs_dir_path_with_base(DEFAULT_AUTH_DIR, &install_dir, None).unwrap(),
|
||||
fallback
|
||||
);
|
||||
fs::remove_file(install_dir.join("logs")).unwrap();
|
||||
fs::create_dir(install_dir.join("logs")).unwrap();
|
||||
for base in [" ../shared logs/./ ".to_string(), path_to_string(&root.join("shared logs"))] {
|
||||
assert_eq!(
|
||||
core_logs_dir_path_with_base(DEFAULT_AUTH_DIR, &install_dir, Some(&base)).unwrap(),
|
||||
root.join("shared logs").join("logs")
|
||||
);
|
||||
}
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_configuration_normalizes_auth_paths_and_rejects_missing_home() {
|
||||
let root = agent_test_home("core-auth-normalization");
|
||||
let install_dir = root.join("cpa-core");
|
||||
let user_home = root.join("home");
|
||||
for auth_dir in ["~/.cli-proxy-api", r"~\.cli-proxy-api", ""] {
|
||||
assert_eq!(
|
||||
auth_dir_path_for_core_with_home(auth_dir, &install_dir, Some(&user_home)).unwrap(),
|
||||
user_home.join(".cli-proxy-api")
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
auth_dir_path_for_core_with_home("~", &install_dir, Some(&user_home)).unwrap(),
|
||||
user_home
|
||||
);
|
||||
assert_eq!(
|
||||
auth_dir_path_for_core("missing/../oauth/./", &install_dir).unwrap(),
|
||||
install_dir.join("oauth")
|
||||
);
|
||||
assert!(auth_dir_path_for_core_with_home("~/oauth", &install_dir, None).is_err());
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_configuration_writes_effective_tls_fields_without_version_marker() {
|
||||
let settings = CoreTlsSettings {
|
||||
enabled: true,
|
||||
cert: "certs/new.crt".to_string(),
|
||||
key: "certs/new.key".to_string(),
|
||||
};
|
||||
for input in [
|
||||
"server:\n tls: {enable: true, cert: old.crt, key: old.key}\n",
|
||||
"server: {tls: {enable: false}}\n",
|
||||
"tls: {enable: false, cert: legacy.crt, key: legacy.key}\nserver: {tls: {enable: false}}\n",
|
||||
] {
|
||||
let patched = patch_core_tls_settings_yaml(input, &settings).unwrap().unwrap();
|
||||
let document: serde_norway::Value = serde_norway::from_str(&patched).unwrap();
|
||||
let effective = core_tls_settings_from_value(&document).unwrap();
|
||||
assert!(effective.enabled);
|
||||
assert_eq!(effective.cert, settings.cert);
|
||||
assert_eq!(effective.key, settings.key);
|
||||
if let Some(legacy) = document.get("tls") {
|
||||
assert_eq!(legacy["cert"].as_str(), Some(settings.cert.as_str()));
|
||||
}
|
||||
assert!(patch_core_tls_settings_yaml(&patched, &settings).unwrap().is_none());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tls_patch_preserves_null_legacy_fields_with_v8_settings() {
|
||||
let settings = CoreTlsSettings {
|
||||
enabled: false,
|
||||
cert: "certs/new.crt".to_string(),
|
||||
key: "certs/new.key".to_string(),
|
||||
};
|
||||
for version_marker in ["config-version: 8\n", ""] {
|
||||
let input = format!(
|
||||
"{version_marker}tls: null\nserver:\n tls:\n enable: true\n cert: old.crt\n key: old.key\n"
|
||||
);
|
||||
let patched = patch_core_tls_settings_yaml(&input, &settings)
|
||||
.unwrap()
|
||||
.expect("TLS settings should change");
|
||||
let document: serde_norway::Value = serde_norway::from_str(&patched).unwrap();
|
||||
assert_eq!(document.get("tls"), Some(&serde_norway::Value::Null));
|
||||
let effective = core_tls_settings_from_value(&document).unwrap();
|
||||
assert_eq!(effective.enabled, settings.enabled);
|
||||
assert_eq!(effective.cert, settings.cert);
|
||||
assert_eq!(effective.key, settings.key);
|
||||
assert!(patch_core_tls_settings_yaml(&patched, &settings)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_configuration_writes_effective_logging_fields_without_version_marker() {
|
||||
let config = GuiConfigFile {
|
||||
logging_to_file: true,
|
||||
logs_max_total_size_mb: 123,
|
||||
..GuiConfigFile::default()
|
||||
};
|
||||
let settings = CoreConfigSettings::from(&config);
|
||||
for input in [
|
||||
"observability:\n logs: {logging-to-file: false, logs-max-total-size-mb: 0}\n",
|
||||
"logging-to-file: false\nobservability:\n logs: {logging-to-file: false}\n",
|
||||
] {
|
||||
for patched in [
|
||||
apply_gui_managed_settings(input, &config).unwrap(),
|
||||
patch_core_yaml_document(input, |document| apply_core_logging_settings(document, &settings))
|
||||
.unwrap().unwrap(),
|
||||
] {
|
||||
let document: serde_norway::Value = serde_norway::from_str(&patched).unwrap();
|
||||
let effective = core_config_settings_from_value(&document).unwrap();
|
||||
assert!(effective.logging_to_file);
|
||||
assert_eq!(effective.logs_max_total_size_mb, 123);
|
||||
if let Some(legacy) = document.get("logging-to-file") {
|
||||
assert_eq!(legacy.as_bool(), Some(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relative_oauth_dir_recovers_existing_persistent_credentials() {
|
||||
let root = agent_test_home("relative-oauth-dir");
|
||||
let install_dir = root.join("cpa-core");
|
||||
let persistent_auth_dir = root.join(OAUTH_DIR_NAME);
|
||||
let misplaced_auth_dir = install_dir.join(OAUTH_DIR_NAME);
|
||||
@@ -224,11 +377,11 @@ fn macos_relative_oauth_dir_recovers_existing_persistent_credentials() {
|
||||
};
|
||||
|
||||
assert!(
|
||||
recover_macos_relative_oauth_dir(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
recover_relative_oauth_dir(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
);
|
||||
assert_eq!(config.auth_dir, DEFAULT_AUTH_DIR);
|
||||
assert_eq!(
|
||||
auth_dir_path_for_core(&config.auth_dir, &install_dir),
|
||||
auth_dir_path_for_core(&config.auth_dir, &install_dir).unwrap(),
|
||||
persistent_auth_dir
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -241,8 +394,8 @@ fn macos_relative_oauth_dir_recovers_existing_persistent_credentials() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_oauth_recovery_accepts_equivalent_directory_paths() {
|
||||
let root = agent_test_home("macos-oauth-equivalent-paths");
|
||||
fn oauth_recovery_accepts_equivalent_directory_paths() {
|
||||
let root = agent_test_home("oauth-equivalent-paths");
|
||||
let install_dir = root.join("cpa-core");
|
||||
let persistent_auth_dir = root.join(OAUTH_DIR_NAME);
|
||||
fs::create_dir_all(&persistent_auth_dir).unwrap();
|
||||
@@ -252,13 +405,17 @@ fn macos_oauth_recovery_accepts_equivalent_directory_paths() {
|
||||
"oauth/./".to_string(),
|
||||
"../cpa-core/oauth".to_string(),
|
||||
path_to_string(&install_dir.join(OAUTH_DIR_NAME)),
|
||||
#[cfg(windows)]
|
||||
r".\oauth".to_string(),
|
||||
#[cfg(windows)]
|
||||
r"..\cpa-core\oauth".to_string(),
|
||||
] {
|
||||
let mut config = GuiConfigFile {
|
||||
auth_dir: auth_dir.clone(),
|
||||
..GuiConfigFile::default()
|
||||
};
|
||||
assert!(
|
||||
recover_macos_relative_oauth_dir(&mut config, &install_dir, &persistent_auth_dir).unwrap(),
|
||||
recover_relative_oauth_dir(&mut config, &install_dir, &persistent_auth_dir).unwrap(),
|
||||
"failed to recover {auth_dir}"
|
||||
);
|
||||
assert_eq!(config.auth_dir, DEFAULT_AUTH_DIR);
|
||||
@@ -267,8 +424,8 @@ fn macos_oauth_recovery_accepts_equivalent_directory_paths() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_oauth_recovery_preserves_credentials_behind_parent_components() {
|
||||
let root = agent_test_home("macos-oauth-parent-components");
|
||||
fn oauth_recovery_preserves_credentials_behind_parent_components() {
|
||||
let root = agent_test_home("oauth-parent-components");
|
||||
let install_dir = root.join("cpa-core");
|
||||
let active_auth_dir = install_dir.join(OAUTH_DIR_NAME);
|
||||
let persistent_auth_dir = root.join(OAUTH_DIR_NAME);
|
||||
@@ -281,10 +438,12 @@ fn macos_oauth_recovery_preserves_credentials_behind_parent_components() {
|
||||
for auth_dir in [
|
||||
"missing/../oauth".to_string(),
|
||||
path_to_string(&install_dir.join("missing").join("..").join(OAUTH_DIR_NAME)),
|
||||
#[cfg(windows)]
|
||||
r"missing\..\oauth".to_string(),
|
||||
] {
|
||||
#[cfg(unix)]
|
||||
assert_eq!(
|
||||
fs::read_dir(auth_dir_path_for_core(&auth_dir, &install_dir))
|
||||
fs::read_dir(install_dir.join(&auth_dir))
|
||||
.unwrap_err()
|
||||
.kind(),
|
||||
io::ErrorKind::NotFound
|
||||
@@ -294,7 +453,7 @@ fn macos_oauth_recovery_preserves_credentials_behind_parent_components() {
|
||||
..GuiConfigFile::default()
|
||||
};
|
||||
assert!(
|
||||
!migrate_macos_auth_dir_at(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
!migrate_auth_dir_at(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
);
|
||||
assert_eq!(config.auth_dir, auth_dir);
|
||||
}
|
||||
@@ -327,14 +486,14 @@ fn oauth_directory_write_updates_the_effective_v8_field_without_version_marker()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_oauth_recovery_survives_config_reload_and_v8_upgrade() {
|
||||
fn oauth_recovery_survives_config_reload_and_v8_upgrade() {
|
||||
for input in [
|
||||
"auth-dir: oauth\n",
|
||||
"config-version: 8\noauth:\n auth-dir: oauth\n",
|
||||
"oauth:\n auth-dir: ./oauth\n",
|
||||
"auth-dir: ../oauth\noauth: {auth-dir: oauth}\n",
|
||||
] {
|
||||
let root = agent_test_home("macos-oauth-recovery-reload");
|
||||
let root = agent_test_home("oauth-recovery-reload");
|
||||
let install_dir = root.join("cpa-core");
|
||||
let persistent_auth_dir = root.join(OAUTH_DIR_NAME);
|
||||
let core_config_path = install_dir.join(CORE_CONFIG_FILE);
|
||||
@@ -355,7 +514,7 @@ fn macos_oauth_recovery_survives_config_reload_and_v8_upgrade() {
|
||||
write_gui_config_to_path(&config, &gui_config_path).unwrap();
|
||||
|
||||
assert!(
|
||||
migrate_macos_auth_dir_at(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
sanitize_gui_config_at(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
);
|
||||
let patched = fs::read_to_string(&core_config_path).unwrap();
|
||||
assert!(patched.contains("# user comment"));
|
||||
@@ -373,7 +532,7 @@ fn macos_oauth_recovery_survives_config_reload_and_v8_upgrade() {
|
||||
toml::from_str(&fs::read_to_string(&gui_config_path).unwrap()).unwrap();
|
||||
assert_eq!(reloaded.auth_dir, DEFAULT_AUTH_DIR);
|
||||
assert!(
|
||||
!migrate_macos_auth_dir_at(&mut reloaded, &install_dir, &persistent_auth_dir).unwrap()
|
||||
!sanitize_gui_config_at(&mut reloaded, &install_dir, &persistent_auth_dir).unwrap()
|
||||
);
|
||||
assert_eq!(fs::read_to_string(&core_config_path).unwrap(), patched);
|
||||
|
||||
@@ -391,7 +550,7 @@ fn macos_oauth_recovery_survives_config_reload_and_v8_upgrade() {
|
||||
let effective = core_config_settings_from_value(&started_document).unwrap();
|
||||
assert_eq!(effective.auth_dir, DEFAULT_AUTH_DIR);
|
||||
assert_eq!(
|
||||
auth_dir_path_for_core(&effective.auth_dir, &install_dir),
|
||||
auth_dir_path_for_core(&effective.auth_dir, &install_dir).unwrap(),
|
||||
persistent_auth_dir
|
||||
);
|
||||
assert_eq!(fs::read(&credential_path).unwrap(), b"existing-credential");
|
||||
@@ -401,9 +560,9 @@ fn macos_oauth_recovery_survives_config_reload_and_v8_upgrade() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_oauth_recovery_preserves_config_when_inspection_or_patch_fails() {
|
||||
fn oauth_recovery_preserves_config_when_inspection_or_patch_fails() {
|
||||
for failure in ["invalid-yaml", "unreadable-config", "invalid-auth-directory"] {
|
||||
let root = agent_test_home("macos-oauth-recovery-failure");
|
||||
let root = agent_test_home("oauth-recovery-failure");
|
||||
let install_dir = root.join("cpa-core");
|
||||
let persistent_auth_dir = root.join(OAUTH_DIR_NAME);
|
||||
let core_config_path = install_dir.join(CORE_CONFIG_FILE);
|
||||
@@ -426,7 +585,12 @@ fn macos_oauth_recovery_preserves_config_when_inspection_or_patch_fails() {
|
||||
let original_config = toml::to_string(&config).unwrap();
|
||||
let original_yaml = fs::read(&core_config_path).ok();
|
||||
|
||||
assert!(migrate_macos_auth_dir_at(&mut config, &install_dir, &persistent_auth_dir).is_err());
|
||||
assert!(migrate_auth_dir_at(&mut config, &install_dir, &persistent_auth_dir).is_err());
|
||||
assert_eq!(toml::to_string(&config).unwrap(), original_config);
|
||||
assert_eq!(fs::read(&core_config_path).ok(), original_yaml);
|
||||
assert!(
|
||||
!sanitize_gui_config_at(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
);
|
||||
assert_eq!(toml::to_string(&config).unwrap(), original_config);
|
||||
assert_eq!(fs::read(&core_config_path).ok(), original_yaml);
|
||||
assert_eq!(
|
||||
@@ -438,8 +602,8 @@ fn macos_oauth_recovery_preserves_config_when_inspection_or_patch_fails() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_oauth_recovery_requires_existing_persistent_credentials() {
|
||||
let root = agent_test_home("macos-oauth-recovery-empty");
|
||||
fn oauth_recovery_requires_existing_persistent_credentials() {
|
||||
let root = agent_test_home("oauth-recovery-empty");
|
||||
let install_dir = root.join("cpa-core");
|
||||
let persistent_auth_dir = root.join(OAUTH_DIR_NAME);
|
||||
let mut config = GuiConfigFile {
|
||||
@@ -447,13 +611,13 @@ fn macos_oauth_recovery_requires_existing_persistent_credentials() {
|
||||
..GuiConfigFile::default()
|
||||
};
|
||||
assert!(
|
||||
!migrate_macos_auth_dir_at(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
!migrate_auth_dir_at(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
);
|
||||
fs::create_dir_all(persistent_auth_dir.join("nested")).unwrap();
|
||||
fs::write(persistent_auth_dir.join("core.log"), b"log").unwrap();
|
||||
fs::write(persistent_auth_dir.join("nested").join("account.json"), b"nested").unwrap();
|
||||
assert!(
|
||||
!migrate_macos_auth_dir_at(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
!migrate_auth_dir_at(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
);
|
||||
assert_eq!(config.auth_dir, OAUTH_DIR_NAME);
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
@@ -461,8 +625,8 @@ fn macos_oauth_recovery_requires_existing_persistent_credentials() {
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn macos_oauth_recovery_preserves_active_linked_credentials() {
|
||||
let root = agent_test_home("macos-oauth-recovery-symlink");
|
||||
fn oauth_recovery_preserves_active_linked_credentials() {
|
||||
let root = agent_test_home("oauth-recovery-symlink");
|
||||
let install_dir = root.join("cpa-core");
|
||||
let active_auth_dir = install_dir.join(OAUTH_DIR_NAME);
|
||||
let persistent_auth_dir = root.join(OAUTH_DIR_NAME);
|
||||
@@ -476,7 +640,7 @@ fn macos_oauth_recovery_preserves_active_linked_credentials() {
|
||||
..GuiConfigFile::default()
|
||||
};
|
||||
assert!(
|
||||
!migrate_macos_auth_dir_at(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
!migrate_auth_dir_at(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
);
|
||||
assert_eq!(config.auth_dir, OAUTH_DIR_NAME);
|
||||
assert_eq!(fs::read(active_auth_dir.join("linked.json")).unwrap(), b"existing-credential");
|
||||
@@ -484,8 +648,8 @@ fn macos_oauth_recovery_preserves_active_linked_credentials() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_relative_oauth_dir_preserves_populated_or_custom_paths() {
|
||||
let root = agent_test_home("macos-relative-oauth-dir-custom");
|
||||
fn relative_oauth_dir_preserves_populated_or_custom_paths() {
|
||||
let root = agent_test_home("relative-oauth-dir-custom");
|
||||
let install_dir = root.join("cpa-core");
|
||||
let persistent_auth_dir = root.join(OAUTH_DIR_NAME);
|
||||
let active_auth_dir = install_dir.join(OAUTH_DIR_NAME);
|
||||
@@ -499,19 +663,48 @@ fn macos_relative_oauth_dir_preserves_populated_or_custom_paths() {
|
||||
};
|
||||
|
||||
assert!(
|
||||
!recover_macos_relative_oauth_dir(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
!recover_relative_oauth_dir(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
);
|
||||
assert_eq!(config.auth_dir, OAUTH_DIR_NAME);
|
||||
fs::remove_file(active_auth_dir.join("current.json")).unwrap();
|
||||
config.auth_dir = "other/oauth".to_string();
|
||||
assert!(
|
||||
!recover_macos_relative_oauth_dir(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
!recover_relative_oauth_dir(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
);
|
||||
assert_eq!(config.auth_dir, "other/oauth");
|
||||
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
fn oauth_recovery_preserves_app_named_custom_directories_on_other_platforms() {
|
||||
let root = agent_test_home("oauth-custom-app-directory");
|
||||
let install_dir = root.join("cpa-core");
|
||||
let persistent_auth_dir = root.join(OAUTH_DIR_NAME);
|
||||
let custom_auth_dir = root.join("Custom.app").join("Contents").join(OAUTH_DIR_NAME);
|
||||
fs::create_dir_all(&custom_auth_dir).unwrap();
|
||||
fs::create_dir_all(&persistent_auth_dir).unwrap();
|
||||
fs::write(custom_auth_dir.join("current.json"), b"current-credential").unwrap();
|
||||
fs::write(persistent_auth_dir.join("old.json"), b"old-credential").unwrap();
|
||||
let configured_auth_dir = path_to_string(&custom_auth_dir);
|
||||
let mut config = GuiConfigFile {
|
||||
auth_dir: configured_auth_dir.clone(),
|
||||
..GuiConfigFile::default()
|
||||
};
|
||||
|
||||
assert!(
|
||||
!sanitize_gui_config_at(&mut config, &install_dir, &persistent_auth_dir).unwrap()
|
||||
);
|
||||
assert_eq!(config.auth_dir, configured_auth_dir);
|
||||
assert!(!persistent_auth_dir.join("current.json").exists());
|
||||
assert_eq!(
|
||||
fs::read(custom_auth_dir.join("current.json")).unwrap(),
|
||||
b"current-credential"
|
||||
);
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn packaged_macos_auth_directory_is_copied_before_config_is_repointed() {
|
||||
let root = agent_test_home("packaged-macos-auth-migration");
|
||||
|
||||
@@ -930,14 +930,14 @@ fn core_start_log_path_follows_the_managed_logs_directory() {
|
||||
let install_dir = base_dir.join("cpa-core");
|
||||
|
||||
assert_eq!(
|
||||
core_start_log_path(&install_dir, DEFAULT_AUTH_DIR),
|
||||
core_start_log_path(&install_dir, DEFAULT_AUTH_DIR).unwrap(),
|
||||
base_dir
|
||||
.join("oauth")
|
||||
.join("logs")
|
||||
.join("core-start-output.log")
|
||||
);
|
||||
assert_eq!(
|
||||
core_start_log_path(&install_dir, "custom-auth"),
|
||||
core_start_log_path(&install_dir, "custom-auth").unwrap(),
|
||||
install_dir
|
||||
.join("custom-auth")
|
||||
.join("logs")
|
||||
|
||||
Reference in new issue
Block a user