use std::{ffi::OsStr, path::Path, sync::Arc};
use relative_path::RelativePath;
use zenops_safe_relative_path::{
SafeRelativePath, SafeRelativePathBuf, SinglePathComponent, error::Error,
};
#[test]
fn test_basic_paths() {
assert_eq!(
SafeRelativePath::from_relative_path("foo/bar")
.unwrap()
.as_str(),
"foo/bar"
);
assert_eq!(
SafeRelativePath::from_relative_path("baz")
.unwrap()
.as_str(),
"baz"
);
}
#[test]
fn from_relative_path_rejects_parent_traversal() {
let err = SafeRelativePath::from_relative_path("../escape").unwrap_err();
assert_eq!(err, Error::PathGoesOutsideParent("../escape".into()));
let err = SafeRelativePath::from_relative_path("foo/../../bar").unwrap_err();
assert_eq!(err, Error::PathGoesOutsideParent("foo/../../bar".into()));
}
#[test]
fn try_join_succeeds_for_safe_path_and_fails_for_traversal() {
let base = SafeRelativePath::from_relative_path("base").unwrap();
let joined = base.try_join("nested/file").unwrap();
assert_eq!(joined.as_str(), "base/nested/file");
let err = base.try_join("../escape").unwrap_err();
assert_eq!(err, Error::PathGoesOutsideParent("../escape".into()));
}
#[test]
fn safe_parent_walks_up_one_component_at_a_time() {
let p = SafeRelativePath::from_relative_path("a/b/c").unwrap();
assert_eq!(p.safe_parent().unwrap().as_str(), "a/b");
assert_eq!(
p.safe_parent().unwrap().safe_parent().unwrap().as_str(),
"a",
);
let root = SafeRelativePath::from_relative_path("").unwrap();
assert!(root.safe_parent().is_none());
}
#[test]
fn to_full_path_appends_relative_to_base() {
let p = SafeRelativePath::from_relative_path("sub/file.txt").unwrap();
assert_eq!(
p.to_full_path("/tmp/base"),
Path::new("/tmp/base/sub/file.txt"),
);
}
#[test]
fn normalize_safe_collapses_current_dir_segments() {
let p = SafeRelativePath::from_relative_path("./foo/./bar").unwrap();
assert_eq!(p.normalize_safe().as_str(), "foo/bar");
}
#[test]
fn safe_relative_path_buf_round_trips_via_from_str_and_display() {
let buf: SafeRelativePathBuf = "foo/bar".parse().unwrap();
assert_eq!(format!("{buf}"), "foo/bar");
assert_eq!(format!("{buf:?}"), "\"foo/bar\"");
let err = "../nope".parse::<SafeRelativePathBuf>().unwrap_err();
assert_eq!(err, Error::PathGoesOutsideParent("../nope".into()));
}
#[test]
fn safe_relative_path_buf_asref_variants_all_point_at_same_bytes() {
let buf: SafeRelativePathBuf = SafeRelativePathBuf::from_relative_path("hello/world").unwrap();
let rel: &RelativePath = buf.as_ref();
assert_eq!(rel.as_str(), "hello/world");
let os: &OsStr = buf.as_ref();
assert_eq!(os, OsStr::new("hello/world"));
let borrowed: &SafeRelativePath = buf.as_ref();
assert_eq!(borrowed.as_str(), "hello/world");
assert_eq!((*buf).as_str(), "hello/world");
}
#[test]
fn safe_relative_path_asref_relative_path_and_debug() {
let p = SafeRelativePath::from_relative_path("a/b").unwrap();
let rel: &RelativePath = p.as_ref();
assert_eq!(rel.as_str(), "a/b");
assert_eq!(format!("{p:?}"), "\"a/b\"");
}
#[test]
fn arc_safe_relative_path_conversions_preserve_contents() {
let buf = SafeRelativePathBuf::from_relative_path("one/two").unwrap();
let arc: Arc<SafeRelativePath> = buf.into();
assert_eq!(arc.as_str(), "one/two");
let p = SafeRelativePath::from_relative_path("alpha").unwrap();
let arc: Arc<SafeRelativePath> = p.into();
assert_eq!(arc.as_str(), "alpha");
}
#[test]
fn single_path_component_accepts_a_single_segment() {
let c = SinglePathComponent::try_new("name").unwrap();
assert_eq!(format!("{c}"), "name");
let p: &SafeRelativePath = c.as_ref();
assert_eq!(p.as_str(), "name");
assert_eq!((*c).as_str(), "name");
}
#[test]
fn single_path_component_rejects_multi_component_paths() {
let err = SinglePathComponent::try_new("a/b").unwrap_err();
assert_eq!(err, Error::NotASinglePathComponent("a/b".to_string()));
}
#[test]
fn single_path_component_rejects_parent_traversal() {
let err = SinglePathComponent::try_new("..").unwrap_err();
assert_eq!(err, Error::PathGoesOutsideParent("..".into()));
}