@@ -45,10 +45,7 @@ fn is_poetry_cache_environment(path: &Path) -> bool {
4545 // - Linux: ~/.cache/pypoetry/virtualenvs/
4646 // - macOS: ~/Library/Caches/pypoetry/virtualenvs/
4747 // - Windows: %LOCALAPPDATA%\pypoetry\Cache\virtualenvs\
48- let path_str = path. to_str ( ) . unwrap_or_default ( ) ;
49-
50- // Check if path contains typical Poetry cache directory structure
51- if path_str. contains ( "pypoetry" ) && path_str. contains ( "virtualenvs" ) {
48+ if has_poetry_cache_components ( path) {
5249 // Further validate by checking if the directory name matches Poetry's naming pattern
5350 // Pattern: {name}-{8-char-hash}-py or just .venv
5451 if let Some ( dir_name) = path. file_name ( ) . and_then ( |n| n. to_str ( ) ) {
@@ -63,6 +60,21 @@ fn is_poetry_cache_environment(path: &Path) -> bool {
6360 false
6461}
6562
63+ fn has_poetry_cache_components ( path : & Path ) -> bool {
64+ let mut found_pypoetry = false ;
65+
66+ path. components ( )
67+ . filter_map ( |component| component. as_os_str ( ) . to_str ( ) )
68+ . any ( |component| {
69+ if component. eq_ignore_ascii_case ( "pypoetry" ) {
70+ found_pypoetry = true ;
71+ return false ;
72+ }
73+
74+ found_pypoetry && component. eq_ignore_ascii_case ( "virtualenvs" )
75+ } )
76+ }
77+
6678/// Check if a .venv directory is an in-project Poetry environment
6779/// This is for the case when virtualenvs.in-project = true is set.
6880/// We check if the parent directory has Poetry configuration files.
@@ -359,6 +371,100 @@ mod tests {
359371 use super :: * ;
360372 use pet_core:: os_environment:: EnvironmentApi ;
361373
374+ fn path_from_components ( components : & [ & str ] ) -> PathBuf {
375+ let mut path = PathBuf :: new ( ) ;
376+ for component in components {
377+ path. push ( component) ;
378+ }
379+ path
380+ }
381+
382+ #[ test]
383+ fn test_poetry_cache_environment_requires_exact_cache_components ( ) {
384+ let path = path_from_components ( & [
385+ "home" ,
386+ "user" ,
387+ ".cache" ,
388+ "pypoetry" ,
389+ "virtualenvs" ,
390+ "project-1a2b3c4d-py3.11" ,
391+ ] ) ;
392+
393+ assert ! ( is_poetry_cache_environment( & path) ) ;
394+ }
395+
396+ #[ test]
397+ fn test_poetry_cache_environment_allows_windows_cache_component ( ) {
398+ let path = path_from_components ( & [
399+ "Users" ,
400+ "user" ,
401+ "AppData" ,
402+ "Local" ,
403+ "pypoetry" ,
404+ "Cache" ,
405+ "virtualenvs" ,
406+ "project-1a2b3c4d-py3.11" ,
407+ ] ) ;
408+
409+ assert ! ( is_poetry_cache_environment( & path) ) ;
410+ }
411+
412+ #[ test]
413+ fn test_poetry_cache_environment_rejects_substring_cache_components ( ) {
414+ let path = path_from_components ( & [
415+ "Users" ,
416+ "pypoetry_user" ,
417+ "virtualenvs_backup" ,
418+ "project-1a2b3c4d-py3.11" ,
419+ ] ) ;
420+
421+ assert ! ( !is_poetry_cache_environment( & path) ) ;
422+ }
423+
424+ #[ test]
425+ fn test_poetry_cache_environment_requires_ordered_cache_components ( ) {
426+ let path = path_from_components ( & [
427+ "home" ,
428+ "user" ,
429+ ".cache" ,
430+ "virtualenvs" ,
431+ "pypoetry" ,
432+ "project-1a2b3c4d-py3.11" ,
433+ ] ) ;
434+
435+ assert ! ( !is_poetry_cache_environment( & path) ) ;
436+ }
437+
438+ #[ test]
439+ fn test_poetry_cache_environment_allows_mixed_case_cache_components ( ) {
440+ let path = path_from_components ( & [
441+ "Users" ,
442+ "user" ,
443+ "AppData" ,
444+ "Local" ,
445+ "PyPoetry" ,
446+ "Cache" ,
447+ "VirtualEnvs" ,
448+ "project-1a2b3c4d-py3.11" ,
449+ ] ) ;
450+
451+ assert ! ( is_poetry_cache_environment( & path) ) ;
452+ }
453+
454+ #[ test]
455+ fn test_poetry_cache_environment_requires_poetry_env_name ( ) {
456+ let path = path_from_components ( & [
457+ "home" ,
458+ "user" ,
459+ ".cache" ,
460+ "pypoetry" ,
461+ "virtualenvs" ,
462+ "not-a-poetry-env" ,
463+ ] ) ;
464+
465+ assert ! ( !is_poetry_cache_environment( & path) ) ;
466+ }
467+
362468 #[ test]
363469 fn test_sync_search_result_from_replaces_cached_result ( ) {
364470 let environment = EnvironmentApi :: new ( ) ;
0 commit comments