use super::*;use codex_utils_absolute_path::AbsolutePathBufGuard;use pretty_assertions::assert_eq;#[cfg(windows)]use std::ffi::OsString;#[cfg(unix)]use std::os::unix::ffi::OsStringExt;#[cfg(windows)]use std::os::windows::ffi::OsStringExt;use std::path::PathBuf;#[test]fn file_uri_round_trips_an_absolute_path() { let path = AbsolutePathBuf::current_dir() .expect("current directory") .join("a path/file.rs"); let uri = PathUri::from_abs_path(&path); let uri_string = uri.to_string(); assert!(uri_string.starts_with("file:")); assert!(uri_string.ends_with("/a%20path/file.rs")); assert_eq!( PathUri::parse(&uri_string).expect("serialized URI should parse"), uri ); assert_eq!( uri.to_abs_path() .expect("local file URI should convert to a native path"), path );}#[test]fn non_native_uri_io_conversion_is_invalid_input() { #[cfg(unix)] let uri = PathUri::parse("file://server/share/file.txt").expect("valid file URI"); #[cfg(windows)] let uri = PathUri::parse("file:///usr/local/file.txt").expect("valid file URI"); let error = uri .to_abs_path() .expect_err("URI should not be host-native"); assert_eq!( (error.kind(), error.to_string()), ( io::ErrorKind::InvalidInput, format!("'{uri}' is invalid on '{}'", std::env::consts::OS), ) );}#[test]fn file_uri_parses_a_windows_path_on_any_host() { let uri = PathUri::parse("file:///C:/Users/Alice%20Smith/src/main.rs") .expect("Windows file URI should parse on every host"); assert_eq!(uri.encoded_path(), "/C:/Users/Alice%20Smith/src/main.rs"); assert_eq!(uri.basename(), Some("main.rs".to_string())); assert_eq!( uri.to_string(), "file:///C:/Users/Alice%20Smith/src/main.rs" );}#[test]fn infers_path_conventions_from_uri_shape() { for (uri, expected) in [ ("file:///", Some(PathConvention::Posix)), ("file:///home/alice/src", Some(PathConvention::Posix)), ("file:///C:/Users/Alice/src", Some(PathConvention::Windows)), ("file:///d:", Some(PathConvention::Windows)), ("file://server/share/src", Some(PathConvention::Windows)), // Opaque fallback for POSIX bytes `/tmp/null-\0-\xff-byte`. ( "file:///%00/bad/path/L3RtcC9udWxsLQAt_y1ieXRl", Some(PathConvention::Posix), ), // Opaque fallback for Windows UTF-16LE `\\.\COM1\`. ( "file:///%00/bad/path/XABcAC4AXABDAE8ATQAxAFwA", Some(PathConvention::Windows), ), ("file:///%00/bad/path/YQ", None), ] { let path = PathUri::parse(uri).expect("valid path URI"); assert_eq!(path.infer_path_convention(), expected, "inferring {uri}"); }}#[test]fn drive_shaped_posix_uri_is_intentionally_inferred_as_windows() { let path = PathUri::parse("file:///C:/actually/a/posix/path").expect("valid path URI"); // `/C:/...` is valid on POSIX, but treating this uncommon spelling as a // Windows drive lets callers render the overwhelmingly more common foreign // Windows URI without separately carrying its source convention. assert_eq!(path.infer_path_convention(), Some(PathConvention::Windows));}#[cfg(windows)]#[test]fn file_uri_falls_back_for_windows_prefixes_without_a_uri_representation() { for (native_path, expected_uri) in [ (r"\\.\COM1", "file:///%00/bad/path/XABcAC4AXABDAE8ATQAxAFwA"), ( r"\\?\Volume{00000000-0000-0000-0000-000000000000}\file.rs", "file:///%00/bad/path/XABcAD8AXABWAG8AbAB1AG0AZQB7ADAAMAAwADAAMAAwADAAMAAtADAAMAAwADAALQAwADAAMAAwAC0AMAAwADAAMAAtADAAMAAwADAAMAAwADAAMAAwADAAMAAwAH0AXABmAGkAbABlAC4AcgBzAA", ), ] { let path = AbsolutePathBuf::from_absolute_path_checked(native_path) .expect("Windows namespace path should be absolute"); let uri = PathUri::from_abs_path(&path); assert_eq!(uri.to_string(), expected_uri, "converting {native_path}"); assert_eq!( PathUri::parse(&uri.to_string()) .expect("fallback URI should parse") .to_abs_path() .expect("fallback URI should decode"), path, "round-tripping {native_path}" ); }}#[cfg(windows)]#[test]fn file_uri_fallback_round_trips_non_unicode_windows_paths() { let path_wide = r"C:\bad\" .encode_utf16() .chain([0xd800]) .collect::<Vec<_>>(); let path = PathBuf::from(OsString::from_wide(&path_wide)); let path = AbsolutePathBuf::from_absolute_path_checked(path).expect("absolute Windows path"); let uri = PathUri::from_abs_path(&path); let reparsed = PathUri::parse(&uri.to_string()).expect("fallback URI should parse"); assert!(uri.to_string().starts_with(BAD_PATH_URI_PREFIX)); assert_eq!( reparsed.to_abs_path().expect("fallback URI should decode"), path );}#[cfg(unix)]#[test]fn file_uri_falls_back_for_posix_paths_with_null_bytes() { let path = PathBuf::from(std::ffi::OsString::from_vec( b"/tmp/null-\0-\xff-byte".to_vec(), )); let path = AbsolutePathBuf::from_absolute_path_checked(path).expect("absolute POSIX path"); let uri = PathUri::from_abs_path(&path); assert_eq!( uri, PathUri::parse("file:///%00/bad/path/L3RtcC9udWxsLQAt_y1ieXRl") .expect("valid fallback URI") ); let json = serde_json::to_string(&uri).expect("fallback URI should serialize"); let reparsed: PathUri = serde_json::from_str(&json).expect("serialized fallback URI should parse"); assert_eq!(json, r#""file:///%00/bad/path/L3RtcC9udWxsLQAt_y1ieXRl""#); assert_eq!(reparsed, uri); assert_eq!( reparsed.to_abs_path().expect("fallback URI should decode"), path );}#[cfg(unix)]#[test]fn ordinary_bad_path_uri_is_not_decoded_as_a_fallback() { let path = AbsolutePathBuf::from_absolute_path_checked("/bad/path/L3RtcC9udWxsLQAt_y1ieXRl") .expect("absolute POSIX path"); let uri = PathUri::from_abs_path(&path); assert_eq!(uri.to_string(), "file:///bad/path/L3RtcC9udWxsLQAt_y1ieXRl"); assert_eq!( uri.to_abs_path().expect("URI should convert literally"), path );}#[test]fn malformed_bad_path_uris_are_rejected() { for uri in [ "file:///%00/bad/path/", "file:///%00/bad/path/not*base64", "file:///%00/bad/path/YQ==", "file:///%00/bad/path/YR", "file:///%00/bad/path/YQ/extra", "file:///%00/other/YQ", ] { assert_eq!( PathUri::parse(uri), Err(PathUriParseError::InvalidFileUriPath { path: uri.to_string(), }), "parsing {uri}" ); }}#[test]fn structurally_valid_bad_path_uri_with_invalid_native_payload_fails_conversion() { let uri = PathUri::parse("file:///%00/bad/path/YQ") .expect("canonical base64 fallback URI should parse"); assert_eq!( uri.to_abs_path() .expect_err("relative fallback payload should not convert") .kind(), io::ErrorKind::InvalidInput );}#[test]fn bad_path_uris_are_opaque_to_lexical_operations() { let uri = PathUri::parse("file:///%00/bad/path/YQ") .expect("canonical base64 fallback URI should parse"); assert_eq!(uri.basename(), None); assert_eq!(uri.parent(), None); assert_eq!(uri.join(""), Ok(uri.clone())); assert_eq!( uri.join("child"), Err(PathUriParseError::InvalidFileUriPath { path: uri.to_string(), }) );}#[test]fn file_uri_parses_a_posix_path_on_any_host() { let uri = PathUri::parse("file:///home/alice/src/main.rs") .expect("POSIX file URI should parse on every host"); assert_eq!(uri.encoded_path(), "/home/alice/src/main.rs"); assert_eq!(uri.basename(), Some("main.rs".to_string())); assert_eq!(uri.to_string(), "file:///home/alice/src/main.rs");}#[test]fn file_uri_preserves_paths_that_resemble_windows_paths() { for (input, expected_path) in [("file:///C:/Project", "/C:/Project"), ("file:///C:", "/C:")] { let uri = PathUri::parse(input).expect("file URI should parse"); let reparsed = PathUri::parse(&uri.to_string()).expect("file URI should reparse"); assert_eq!(uri.encoded_path(), expected_path); assert_eq!(reparsed, uri); }}#[test]#[cfg(unix)]fn file_uri_accepts_non_utf8_posix_paths() { let path = PathBuf::from(std::ffi::OsString::from_vec(b"/tmp/non-utf8-\xff".to_vec())); let path = AbsolutePathBuf::from_absolute_path_checked(path).expect("absolute POSIX path"); let uri = PathUri::from_abs_path(&path); assert_eq!( uri.to_abs_path() .expect("URI should convert to native path"), path ); assert_eq!( PathUri::parse(&uri.to_string()).expect("non-UTF-8 URI should reparse"), uri );}#[test]fn file_uri_round_trips_literal_percent_characters() { let uri = PathUri::parse("file:///tmp/100%25/file").expect("file URI should parse"); assert_eq!(uri.to_string(), "file:///tmp/100%25/file"); assert_eq!(uri.encoded_path(), "/tmp/100%25/file"); assert_eq!(uri.basename(), Some("file".to_string()));}#[test]#[cfg(windows)]fn file_uri_round_trips_windows_unc_paths() { let path = AbsolutePathBuf::from_absolute_path_checked(r"\\server\share\src\main.rs") .expect("absolute UNC path"); let uri = PathUri::from_abs_path(&path); assert_eq!(uri.encoded_path(), "/share/src/main.rs"); assert_eq!(uri.to_abs_path().expect("UNC URI should convert"), path);}#[test]fn file_uri_retains_unc_authority() { let uri = PathUri::parse("file://server/share/src/main.rs").expect("valid file URI"); assert_eq!(uri.encoded_path(), "/share/src/main.rs"); assert_eq!(uri.to_string(), "file://server/share/src/main.rs");}#[test]fn file_uri_spelling_aliases_have_one_canonical_form() { for input in [ "FILE:///workspace/src", "file:/workspace/src", "file://localhost/workspace/src", "file://LOCALHOST/workspace/src", ] { let uri = PathUri::parse(input).expect("file URI alias should parse"); assert_eq!(uri.to_string(), "file:///workspace/src", "parsing {input}"); }}#[test]fn unsupported_schemes_are_rejected_at_construction() { for (input, expected_scheme) in [ ("codex-env:///devbox/workspace", "codex-env"), ("artifact://store/object-1", "artifact"), ("http://example.com/file", "http"), ("https://example.com/file", "https"), ("ssh://host/workspace", "ssh"), ("vscode-remote://ssh-remote+host/workspace", "vscode-remote"), ("untitled:Untitled-1", "untitled"), ] { let error = PathUri::parse(input).expect_err("unsupported schemes should be rejected"); assert!( matches!( error, PathUriParseError::UnsupportedScheme(scheme) if scheme == expected_scheme ), "parsing {input}" ); }}#[test]fn path_uri_serializes_as_a_string() { let uri: PathUri = "file:///workspace/src/lib.rs" .parse() .expect("valid file URI"); let json = serde_json::to_string(&uri).expect("URI should serialize"); let deserialized: PathUri = serde_json::from_str(&json).expect("URI should deserialize"); assert_eq!(json, r#""file:///workspace/src/lib.rs""#); assert_eq!(deserialized, uri);}#[test]fn path_uri_deserializes_legacy_absolute_paths() { let path = AbsolutePathBuf::current_dir() .expect("current directory") .join("workspace/src"); let json = serde_json::to_string(&path).expect("absolute path should serialize"); let uri: PathUri = serde_json::from_str(&json).expect("legacy absolute path should parse"); assert_eq!(uri, PathUri::from_abs_path(&path));}#[test]fn path_uri_rejects_relative_native_paths() { let error = PathUri::from_path("src/lib.rs").expect_err("relative path should be rejected"); assert_eq!(error.kind(), io::ErrorKind::InvalidInput);}#[test]fn path_uri_rejects_legacy_relative_paths_with_absolute_path_guard() { let base = AbsolutePathBuf::current_dir().expect("current directory"); let _guard = AbsolutePathBufGuard::new(base.as_path()); let error = serde_json::from_str::<PathUri>(r#""src/lib.rs""#) .expect_err("legacy relative path should be rejected"); assert!(error.to_string().contains("path is not absolute"));}#[test]fn unsupported_scheme_is_rejected_during_deserialization() { let error = serde_json::from_str::<PathUri>(r#""artifact://store/object-1""#) .expect_err("unsupported scheme should fail deserialization"); assert!( error .to_string() .contains("unsupported path URI scheme `artifact`") );}#[test]fn known_path_uris_reject_queries_and_fragments() { let query_error = PathUri::parse("file:///tmp/file.rs?version=1").expect_err("query should be rejected"); let fragment_error = PathUri::parse("file:///tmp/file.rs#L1").expect_err("fragment should be rejected"); assert!(matches!(query_error, PathUriParseError::QueryNotAllowed)); assert!(matches!( fragment_error, PathUriParseError::FragmentNotAllowed ));}#[test]fn path_uris_reject_encoded_null_bytes() { assert!(PathUri::parse("file:///tmp/%00").is_err());}#[test]fn encoded_filename_characters_round_trip_without_becoming_uri_metadata() { let uri = PathUri::parse("file:///tmp/a%3Fb%23c%25d") .expect("encoded filename characters should parse"); assert_eq!(uri.to_string(), "file:///tmp/a%3Fb%23c%25d"); assert_eq!(uri.encoded_path(), "/tmp/a%3Fb%23c%25d"); assert_eq!(uri.basename(), Some("a?b#c%d".to_string()));}#[test]fn double_encoded_separator_remains_filename_text() { let uri = PathUri::parse("file:///tmp/a%252Fb") .expect("double-encoded separator should parse as filename text"); assert_eq!(uri.to_string(), "file:///tmp/a%252Fb"); assert_eq!(uri.encoded_path(), "/tmp/a%252Fb"); assert_eq!(uri.basename(), Some("a%2Fb".to_string()));}#[test]fn basename_uses_decoded_uri_segments() { for (input, expected) in [ ("file:///", None), ("file:///workspace/src/lib.rs", Some("lib.rs")), ("file:///workspace/a%20file.rs", Some("a file.rs")), ("file:///C:/", Some("C:")), ("file://server/share", Some("share")), ] { let uri = PathUri::parse(input).expect("valid file URI"); assert_eq!( uri.basename(), expected.map(str::to_string), "basename for {input}" ); }}#[test]fn parent_uses_uri_hierarchy_and_preserves_authority() { for (input, expected) in [ ( "file:///workspace/src/lib.rs", Some("file:///workspace/src"), ), ("file:///workspace", Some("file:///")), ("file:///", None), ("file:///C:/Users", Some("file:///C:")), ("file:///C:/", Some("file:///")), ( "file://server/share/src/main.rs", Some("file://server/share/src"), ), ("file://server/share", Some("file://server/")), ] { let uri = PathUri::parse(input).expect("valid file URI"); let expected = expected.map(|value| PathUri::parse(value).expect("valid expected URI")); assert_eq!(uri.parent(), expected, "parent for {input}"); }}#[test]fn join_normalizes_relative_uri_segments() { for (base, relative, expected) in [ ( "file:///workspace/src", "../tests/test.rs", "file:///workspace/tests/test.rs", ), ("file:///", "../../etc", "file:///etc"), ("file:///C:/Users", "../Windows", "file:///C:/Windows"), ( "file://server/share/src", "../tests", "file://server/share/tests", ), ( "file:///workspace", "a?b#c%d", "file:///workspace/a%3Fb%23c%25d", ), ("file:///workspace/", "", "file:///workspace/"), ] { let base = PathUri::parse(base).expect("valid base URI"); let expected = PathUri::parse(expected).expect("valid expected URI"); assert_eq!(base.join(relative), Ok(expected), "joining {relative}"); }}#[test]fn join_rejects_absolute_and_null_paths() { let base = PathUri::parse("file:///workspace").expect("valid base URI"); assert!(matches!( base.join("/src"), Err(PathUriParseError::JoinPathMustBeRelative(path)) if path == "/src" )); assert_eq!( base.join("src\0file"), Err(PathUriParseError::InvalidFileUriPath { path: "src\0file".to_string(), }) );}#[test]fn to_url_returns_the_validated_url() { let uri = PathUri::parse("file://localhost/workspace/a%20file.rs").expect("valid file URI"); assert_eq!( uri.to_url(), Url::parse("file:///workspace/a%20file.rs").expect("valid URL") );}