@@ -154,3 +154,141 @@ fn get_user_home() -> Option<PathBuf> {
154154 Err ( _) => None ,
155155 }
156156}
157+
158+ #[ cfg( test) ]
159+ mod tests {
160+ use super :: * ;
161+
162+ #[ test]
163+ fn test_norm_case_returns_path_for_nonexistent ( ) {
164+ // norm_case should return the original path if it doesn't exist
165+ let nonexistent = PathBuf :: from ( "/this/path/does/not/exist/anywhere" ) ;
166+ let result = norm_case ( & nonexistent) ;
167+ assert_eq ! ( result, nonexistent) ;
168+ }
169+
170+ #[ test]
171+ fn test_norm_case_existing_path ( ) {
172+ // norm_case should work on existing paths
173+ let temp_dir = std:: env:: temp_dir ( ) ;
174+ let result = norm_case ( & temp_dir) ;
175+ // On unix, should return unchanged; on Windows, should normalize case
176+ assert ! ( result. exists( ) ) ;
177+ }
178+
179+ #[ test]
180+ #[ cfg( unix) ]
181+ fn test_norm_case_unix_noop ( ) {
182+ // On unix, norm_case should return the path unchanged
183+ let path = PathBuf :: from ( "/Some/Path/With/Mixed/Case" ) ;
184+ let result = norm_case ( & path) ;
185+ assert_eq ! ( result, path) ;
186+ }
187+
188+ #[ test]
189+ #[ cfg( windows) ]
190+ fn test_norm_case_windows_case_normalization ( ) {
191+ // On Windows, norm_case should normalize the case of existing paths
192+ // Use the Windows directory which always exists
193+ let path = PathBuf :: from ( "c:\\ windows\\ system32" ) ;
194+ let result = norm_case ( & path) ;
195+ // The result should have proper casing (C:\Windows\System32)
196+ assert ! ( result. to_string_lossy( ) . contains( "Windows" ) ) ;
197+ assert ! ( result. to_string_lossy( ) . contains( "System32" ) ) ;
198+ }
199+
200+ #[ test]
201+ #[ cfg( windows) ]
202+ fn test_norm_case_windows_preserves_junction ( ) {
203+ // This is the key test for issue #186:
204+ // norm_case should NOT resolve junctions to their target
205+ use std:: fs;
206+ use std:: process:: Command ;
207+
208+ let temp_dir = std:: env:: temp_dir ( ) ;
209+ let target_dir = temp_dir. join ( "pet_test_junction_target" ) ;
210+ let junction_dir = temp_dir. join ( "pet_test_junction_link" ) ;
211+
212+ // Clean up any existing test directories
213+ let _ = fs:: remove_dir_all ( & target_dir) ;
214+ let _ = fs:: remove_dir_all ( & junction_dir) ;
215+
216+ // Create target directory
217+ fs:: create_dir_all ( & target_dir) . expect ( "Failed to create target directory" ) ;
218+
219+ // Create a junction using mklink /J (requires no special privileges)
220+ let output = Command :: new ( "cmd" )
221+ . args ( [
222+ "/C" ,
223+ "mklink" ,
224+ "/J" ,
225+ & junction_dir. to_string_lossy ( ) ,
226+ & target_dir. to_string_lossy ( ) ,
227+ ] )
228+ . output ( )
229+ . expect ( "Failed to create junction" ) ;
230+
231+ if !output. status . success ( ) {
232+ // Clean up and skip test if junction creation failed
233+ let _ = fs:: remove_dir_all ( & target_dir) ;
234+ eprintln ! (
235+ "Skipping junction test - failed to create junction: {}" ,
236+ String :: from_utf8_lossy( & output. stderr)
237+ ) ;
238+ return ;
239+ }
240+
241+ // Verify junction was created
242+ assert ! ( junction_dir. exists( ) , "Junction should exist" ) ;
243+
244+ // The key assertion: norm_case should return the junction path, NOT the target path
245+ let result = norm_case ( & junction_dir) ;
246+
247+ // The result should still be the junction path, not resolved to target
248+ // Compare the path names (case-insensitive on Windows)
249+ assert ! (
250+ result
251+ . to_string_lossy( )
252+ . to_lowercase( )
253+ . contains( "pet_test_junction_link" ) ,
254+ "norm_case should preserve junction path, got: {:?}" ,
255+ result
256+ ) ;
257+ assert ! (
258+ !result
259+ . to_string_lossy( )
260+ . to_lowercase( )
261+ . contains( "pet_test_junction_target" ) ,
262+ "norm_case should NOT resolve to target path, got: {:?}" ,
263+ result
264+ ) ;
265+
266+ // Clean up
267+ // Remove junction first (using rmdir, not remove_dir_all, to not follow the junction)
268+ let _ = Command :: new ( "cmd" )
269+ . args ( [ "/C" , "rmdir" , & junction_dir. to_string_lossy ( ) ] )
270+ . output ( ) ;
271+ let _ = fs:: remove_dir_all ( & target_dir) ;
272+ }
273+
274+ #[ test]
275+ #[ cfg( windows) ]
276+ fn test_norm_case_windows_relative_path ( ) {
277+ // Test that relative paths are converted to absolute
278+ let relative = PathBuf :: from ( "." ) ;
279+ let result = norm_case ( & relative) ;
280+ assert ! ( result. is_absolute( ) , "Result should be absolute path" ) ;
281+ }
282+
283+ #[ test]
284+ #[ cfg( windows) ]
285+ fn test_norm_case_windows_no_unc_prefix_added ( ) {
286+ // Ensure we don't add UNC prefix to paths that didn't have it
287+ let path = PathBuf :: from ( "C:\\ Windows" ) ;
288+ let result = norm_case ( & path) ;
289+ assert ! (
290+ !result. to_string_lossy( ) . starts_with( r"\\?\" ) ,
291+ "Should not add UNC prefix"
292+ ) ;
293+ }
294+ }
0 commit comments