use std::io::Write as _;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use camino::Utf8Path;
use cap_std::{ambient_authority, fs_utf8::Dir as Utf8Dir};
use tempfile::tempdir;
use super::*;
#[cfg(windows)]
fn canonicalish(path: &Path) -> PathBuf {
match dunce::canonicalize(path) {
Ok(p) => p,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => path.to_path_buf(),
Err(err) => panic!("failed to canonicalize {path:?}: {err}"),
}
}
#[cfg(not(windows))]
fn canonicalish(path: &Path) -> PathBuf {
path.to_path_buf()
}
fn assert_first_error_path(errors: &[Arc<OrthoError>], expected: &Path) {
let Some(err) = errors.first() else {
panic!("expected at least one error when asserting path");
};
let path = match err.as_ref() {
OrthoError::File { path, .. } => path,
other => panic!("expected OrthoError::File, got {other:?}"),
};
assert_eq!(canonicalish(path), canonicalish(expected));
}
#[test]
fn load_first_partitioned_dedups_required_paths() {
let dir = tempdir().expect("create tempdir");
let required = dir.path().join("missing.toml");
let optional = dir.path().join("optional.toml");
let utf8_dir_path =
Utf8Path::from_path(dir.path()).expect("temporary directory path is not valid UTF-8");
let cap_dir = Utf8Dir::open_ambient_dir(utf8_dir_path, ambient_authority())
.expect("open temporary directory with cap-std");
let mut file = cap_dir
.create("optional.toml")
.expect("create optional.toml");
file.write_all(b"invalid = {")
.expect("write invalid optional config");
drop(file);
let discovery = ConfigDiscovery::builder("app")
.add_required_path(&required)
.add_required_path(&required)
.add_explicit_path(&optional)
.build();
let outcome = discovery.load_first_partitioned();
assert!(outcome.figment.is_none());
assert_eq!(outcome.required_errors.len(), 1);
assert_eq!(outcome.optional_errors.len(), 1);
assert_first_error_path(&outcome.required_errors, &required);
assert_first_error_path(&outcome.optional_errors, &optional);
}
#[cfg(windows)]
#[test]
fn normalized_key_lowercases_ascii_and_backslashes() {
let key = ConfigDiscovery::normalized_key(Path::new("C:/Config/FILE.TOML"));
let expected: Vec<u16> = "c:\\config\\file.toml".encode_utf16().collect();
assert_eq!(key, expected);
}
#[cfg(windows)]
#[test]
fn normalized_key_preserves_non_ascii_case() {
let key = ConfigDiscovery::normalized_key(Path::new("C:/Temp/CAFÉ.toml"));
let expected: Vec<u16> = "c:\\temp\\cafÉ.toml".encode_utf16().collect();
assert_eq!(key, expected);
}
#[cfg(windows)]
#[test]
fn normalized_key_keeps_unpaired_surrogates_distinct() {
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
let first = OsString::from_wide(&[0x63, 0x3A, 0x5C, 0xD800]);
let second = OsString::from_wide(&[0x63, 0x3A, 0x5C, 0xD801]);
assert_ne!(
ConfigDiscovery::normalized_key(Path::new(&first)),
ConfigDiscovery::normalized_key(Path::new(&second)),
);
}