@@ -397,4 +397,109 @@ mod tests {
397397 let result = shared. environments . read ( ) . unwrap ( ) . clone ( ) . unwrap ( ) ;
398398 assert_eq ! ( result[ 0 ] . environment. name. as_deref( ) , Some ( "stale" ) ) ;
399399 }
400+
401+ #[ test]
402+ fn test_sync_full_when_source_has_none_cache ( ) {
403+ let environment = EnvironmentApi :: new ( ) ;
404+ let shared = WindowsStore :: from ( & environment) ;
405+ let refreshed = WindowsStore :: from ( & environment) ;
406+
407+ // shared has some data, refreshed has None
408+ shared
409+ . environments
410+ . write ( )
411+ . unwrap ( )
412+ . replace ( Arc :: new ( vec ! [ cached_environment( "existing" ) ] ) ) ;
413+
414+ shared. sync_refresh_state_from ( & refreshed, & RefreshStateSyncScope :: Full ) ;
415+
416+ // After syncing from source with None cache, shared should also be None
417+ assert ! ( shared. environments. read( ) . unwrap( ) . is_none( ) ) ;
418+ }
419+
420+ #[ test]
421+ fn test_sync_is_idempotent ( ) {
422+ let environment = EnvironmentApi :: new ( ) ;
423+ let shared = WindowsStore :: from ( & environment) ;
424+ let refreshed = WindowsStore :: from ( & environment) ;
425+
426+ refreshed
427+ . environments
428+ . write ( )
429+ . unwrap ( )
430+ . replace ( Arc :: new ( vec ! [ cached_environment( "fresh" ) ] ) ) ;
431+
432+ shared. sync_refresh_state_from ( & refreshed, & RefreshStateSyncScope :: Full ) ;
433+ shared. sync_refresh_state_from ( & refreshed, & RefreshStateSyncScope :: Full ) ;
434+
435+ let result = shared. environments . read ( ) . unwrap ( ) . clone ( ) . unwrap ( ) ;
436+ assert_eq ! ( result. len( ) , 1 ) ;
437+ assert_eq ! ( result[ 0 ] . environment. name. as_deref( ) , Some ( "fresh" ) ) ;
438+ }
439+
440+ #[ test]
441+ fn is_windows_app_folder_case_insensitive ( ) {
442+ assert ! ( is_windows_app_folder_in_program_files( Path :: new(
443+ r"C:\PROGRAM FILES\WINDOWSAPPS\Something"
444+ ) ) ) ;
445+ assert ! ( is_windows_app_folder_in_program_files( Path :: new(
446+ r"d:\program files\windowsapps\something"
447+ ) ) ) ;
448+ }
449+
450+ #[ test]
451+ fn is_windows_app_folder_rejects_single_char_input ( ) {
452+ assert ! ( !is_windows_app_folder_in_program_files( Path :: new( "C" ) ) ) ;
453+ assert ! ( !is_windows_app_folder_in_program_files( Path :: new( "/" ) ) ) ;
454+ }
455+
456+ #[ test]
457+ fn normalize_for_comparison_strips_extended_prefix ( ) {
458+ assert_eq ! (
459+ normalize_for_comparison( Path :: new( r"\\?\C:\foo\bar.exe" ) ) ,
460+ PathBuf :: from( r"C:\foo\bar.exe" )
461+ ) ;
462+ }
463+
464+ #[ test]
465+ fn normalize_for_comparison_strips_extended_unc_prefix ( ) {
466+ assert_eq ! (
467+ normalize_for_comparison( Path :: new( r"\\?\UNC\server\share\file.exe" ) ) ,
468+ PathBuf :: from( r"\\server\share\file.exe" )
469+ ) ;
470+ }
471+
472+ #[ test]
473+ fn normalize_for_comparison_preserves_plain_path ( ) {
474+ let path = Path :: new ( r"C:\Users\User\python.exe" ) ;
475+ assert_eq ! ( normalize_for_comparison( path) , PathBuf :: from( path) ) ;
476+ }
477+
478+ #[ test]
479+ fn cached_environment_with_empty_symlinks ( ) {
480+ let cached = CachedStoreEnvironment :: from_environment ( PythonEnvironment {
481+ symlinks : Some ( vec ! [ ] ) ,
482+ ..Default :: default ( )
483+ } ) ;
484+ assert ! ( cached. normalized_symlinks. is_empty( ) ) ;
485+ }
486+
487+ #[ test]
488+ fn cached_environment_with_no_symlinks ( ) {
489+ let cached = CachedStoreEnvironment :: from_environment ( PythonEnvironment {
490+ symlinks : None ,
491+ ..Default :: default ( )
492+ } ) ;
493+ assert ! ( cached. normalized_symlinks. is_empty( ) ) ;
494+ }
495+
496+ #[ cfg( unix) ]
497+ #[ test]
498+ fn try_from_returns_none_on_unix ( ) {
499+ let environment = EnvironmentApi :: new ( ) ;
500+ let locator = WindowsStore :: from ( & environment) ;
501+ let env = PythonEnv :: new ( PathBuf :: from ( "/usr/bin/python3" ) , None , None ) ;
502+
503+ assert ! ( locator. try_from( & env) . is_none( ) ) ;
504+ }
400505}
0 commit comments