@@ -311,6 +311,147 @@ public void FindCandidateExecutableFilesReturnsEmptyWhenNoCliToolExists()
311311 Assert . Empty ( candidates ) ;
312312 }
313313
314+ [ Fact ]
315+ public void FindCandidateExecutableFilesPrefersOffPathSystemWinGetOverBundledPinget ( )
316+ {
317+ const string bundledPinget = @"C:\Program Files\UniGetUI\pinget.exe" ;
318+ const string packagedWinGet =
319+ @"C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.29.290.0_x64__8wekyb3d8bbwe\winget.exe" ;
320+
321+ var candidates = WinGet . FindCandidateExecutableFiles (
322+ static _ => [ ] ,
323+ path => path == bundledPinget ,
324+ bundledPinget ,
325+ WinGetCliToolPreference . Default ,
326+ static ( ) => [ packagedWinGet ]
327+ ) ;
328+
329+ Assert . Equal ( [ packagedWinGet , bundledPinget ] , candidates ) ;
330+ }
331+
332+ [ Fact ]
333+ public void FindCandidateExecutableFilesDeduplicatesOffPathSystemWinGetAlreadyFoundOnPath ( )
334+ {
335+ const string systemWinGet = @"C:\WindowsApps\winget.exe" ;
336+ const string bundledPinget = @"C:\Program Files\UniGetUI\pinget.exe" ;
337+
338+ var candidates = WinGet . FindCandidateExecutableFiles (
339+ static executableName => executableName == "winget.exe" ? [ systemWinGet ] : [ ] ,
340+ path => path == bundledPinget ,
341+ bundledPinget ,
342+ WinGetCliToolPreference . Default ,
343+ static ( ) => [ systemWinGet ]
344+ ) ;
345+
346+ Assert . Equal ( [ systemWinGet , bundledPinget ] , candidates ) ;
347+ }
348+
349+ [ Fact ]
350+ public void FindCandidateExecutableFilesIgnoresOffPathSystemWinGetInPingetMode ( )
351+ {
352+ const string bundledPinget = @"C:\Program Files\UniGetUI\pinget.exe" ;
353+
354+ var candidates = WinGet . FindCandidateExecutableFiles (
355+ static _ => [ ] ,
356+ path => path == bundledPinget ,
357+ bundledPinget ,
358+ WinGetCliToolPreference . BundledPinget ,
359+ static ( ) =>
360+ throw new InvalidOperationException (
361+ "System WinGet should not be queried in Pinget mode."
362+ )
363+ ) ;
364+
365+ Assert . Equal ( [ bundledPinget ] , candidates ) ;
366+ }
367+
368+ [ Fact ]
369+ public void EnumerateOffPathExecutablesReturnsExecutionAliasAndAppInstallerLocations ( )
370+ {
371+ const string localAppData = @"C:\Users\test\AppData\Local" ;
372+ string alias = Path . Join ( localAppData , "Microsoft" , "WindowsApps" , "winget.exe" ) ;
373+ const string packageRoot =
374+ @"C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.29.290.0_x64__8wekyb3d8bbwe" ;
375+ string packagedWinGet = Path . Join ( packageRoot , "winget.exe" ) ;
376+
377+ var executables = SystemWinGetLocator
378+ . EnumerateOffPathExecutables (
379+ path => path == alias || path == packagedWinGet ,
380+ localAppData ,
381+ ( ) => [ packageRoot ]
382+ )
383+ . ToArray ( ) ;
384+
385+ Assert . Equal ( [ alias , packagedWinGet ] , executables ) ;
386+ }
387+
388+ [ Fact ]
389+ public void EnumerateOffPathExecutablesSkipsDirectoriesWithoutWinGet ( )
390+ {
391+ const string packageRoot =
392+ @"C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.29.290.0_x64__8wekyb3d8bbwe" ;
393+ string packagedWinGet = Path . Join ( packageRoot , "winget.exe" ) ;
394+
395+ var executables = SystemWinGetLocator
396+ . EnumerateOffPathExecutables (
397+ path => path == packagedWinGet ,
398+ @"C:\Users\test\AppData\Local" ,
399+ ( ) => [ @"C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_0.0.0.0_x64__8wekyb3d8bbwe" , packageRoot ]
400+ )
401+ . ToArray ( ) ;
402+
403+ Assert . Equal ( [ packagedWinGet ] , executables ) ;
404+ }
405+
406+ [ Fact ]
407+ public void EnumerateOffPathExecutablesIgnoresTheExecutionAliasWhenAppInstallerIsNotRegistered ( )
408+ {
409+ const string localAppData = @"C:\Users\test\AppData\Local" ;
410+ string alias = Path . Join ( localAppData , "Microsoft" , "WindowsApps" , "winget.exe" ) ;
411+
412+ var executables = SystemWinGetLocator
413+ . EnumerateOffPathExecutables ( path => path == alias , localAppData , static ( ) => [ ] )
414+ . ToArray ( ) ;
415+
416+ Assert . Empty ( executables ) ;
417+ }
418+
419+ [ Theory ]
420+ [ InlineData ( "Microsoft.DesktopAppInstaller_1.29.290.0_x64__8wekyb3d8bbwe" , true ) ]
421+ [ InlineData ( "Microsoft.DesktopAppInstaller_1.29.290.0_neutral_split.scale-100_8wekyb3d8bbwe" , true ) ]
422+ [ InlineData ( "Microsoft.DesktopAppInstaller_9.9.9.0_x64__1abcdefghijkl" , false ) ]
423+ [ InlineData ( "Microsoft.DesktopAppInstallerExtra_1.0.0.0_x64__8wekyb3d8bbwe" , false ) ]
424+ [ InlineData ( "Contoso.DesktopAppInstaller_1.0.0.0_x64__8wekyb3d8bbwe" , false ) ]
425+ [ InlineData ( "Microsoft.WindowsTerminal_1.0.0.0_x64__8wekyb3d8bbwe" , false ) ]
426+ [ InlineData ( "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe" , false ) ]
427+ [ InlineData ( "Microsoft.DesktopAppInstaller" , false ) ]
428+ public void IsAppInstallerPackageFullNameRequiresTheMicrosoftPublisherId (
429+ string packageFullName ,
430+ bool expected
431+ )
432+ {
433+ Assert . Equal (
434+ expected ,
435+ SystemWinGetLocator . IsAppInstallerPackageFullName ( packageFullName )
436+ ) ;
437+ }
438+
439+ [ Theory ]
440+ [ InlineData ( "Microsoft.DesktopAppInstaller_1.29.290.0_x64__8wekyb3d8bbwe" , "1.29.290.0" ) ]
441+ [ InlineData ( "Microsoft.DesktopAppInstaller_1.2_neutral__8wekyb3d8bbwe" , "1.2" ) ]
442+ [ InlineData ( "Microsoft.DesktopAppInstaller" , "0.0" ) ]
443+ [ InlineData ( "Microsoft.DesktopAppInstaller_notaversion_x64__8wekyb3d8bbwe" , "0.0" ) ]
444+ public void ParsePackageVersionReadsTheVersionPieceOfThePackageFullName (
445+ string packageFullName ,
446+ string expected
447+ )
448+ {
449+ Assert . Equal (
450+ Version . Parse ( expected ) ,
451+ SystemWinGetLocator . ParsePackageVersion ( packageFullName )
452+ ) ;
453+ }
454+
314455 [ Fact ]
315456 public void PingetCliHelperDeserializesListResponsesWithGeneratedContext ( )
316457 {
@@ -424,6 +565,10 @@ int expectedPreference
424565 [ InlineData ( @"C:\Program Files\UniGetUI\pinget.exe" , 1 ) ]
425566 [ InlineData ( @"C:\Tools\pinget.exe" , 1 ) ]
426567 [ InlineData ( @"C:\WindowsApps\winget.exe" , 0 ) ]
568+ [ InlineData (
569+ @"C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.29.290.0_x64__8wekyb3d8bbwe\winget.exe" ,
570+ 0
571+ ) ]
427572 public void GetCliToolKindRecognizesPingetExecutableName (
428573 string executablePath ,
429574 int expectedKind
0 commit comments