Functionality to detect Win32 apps which are installed, deleted or renamed while PowerToys is running (#4960)

* Added file system wrapper and interface

* added win32program repository which would load store app and also handle new apps being added/deleted

* Added event handlers to win32 program repo

* added paths to monitor and setting FSWs for each location

* Events firing as expected

* filter extensions added, events fire as expected

* override gethashcode so that duplicates don't show up. OnCreated and OnDeleted events trigger as expected

* implemented setter for filters in FileSystemWatcher

* Rename adds item but does not seem to delete the previous app

* catching an exception when a duplicate item is inserted

* Removed notify filter for directory because we only need to monitor files

* Added exe programs to be indexed in the desktop and startmenu

* created a new class to init FileSystemHelpers instead of main

* Added fix for shortcut applications to work as expected while renaming and deleting them

* Added wrappers for file system operations

* Added some tests

* Added all tests for appref-ms and added a condition to search in sub directories

* Added tests for Exe applications

* Added lnk app tests

* Added tests for shortcut applications

* removed unnecessary wrappers

* override Equals for win32

* removed debug statements

* Fixed exe issue

* Fixed internet shortcut exception

* fixed renaming shortcut apps

* Added a retry block for ReadAllLines

* capitalized method name - RetrieveTargetPath

* made naming consistent, helper variables start with underscore

* Added the exception condition back

* renamed Win32ProgramRepositoryHelper to Win32ProgramFileSystemWatchers

* made win32Program repository variable static

* changed list to ilist

* disposed file system watchers

* make retrieveTargetPath upper case in tests
This commit is contained in:
Alekhya
2020-07-17 22:32:21 -07:00
committed by GitHub
parent 034079b441
commit d09253e532
17 changed files with 894 additions and 58 deletions

View File

@@ -0,0 +1,363 @@
using System;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using System.Linq;
using Wox.Infrastructure.Storage;
using Wox.Plugin;
using Microsoft.Plugin.Program.Programs;
using Microsoft.Plugin.Program.Storage;
using System.IO;
using Wox.Infrastructure.FileSystemHelper;
using System.Diagnostics;
namespace Microsoft.Plugin.Program.UnitTests.Storage
{
using Win32 = Program.Programs.Win32;
[TestFixture]
class Win32ProgramRepositoryTest
{
List<IFileSystemWatcherWrapper> _fileSystemWatchers;
Settings _settings = new Settings();
string[] _pathsToWatch = new string[] { "location1", "location2" };
List<Mock<IFileSystemWatcherWrapper>> _fileSystemMocks;
[SetUp]
public void SetFileSystemWatchers()
{
_fileSystemWatchers = new List<IFileSystemWatcherWrapper>();
_fileSystemMocks = new List<Mock<IFileSystemWatcherWrapper>>();
for (int index = 0; index < _pathsToWatch.Length; index++)
{
var mockFileWatcher = new Mock<IFileSystemWatcherWrapper>();
_fileSystemMocks.Add(mockFileWatcher);
_fileSystemWatchers.Add(mockFileWatcher.Object);
}
}
[TestCase("Name", "ExecutableName", "FullPath", "description1", "description2")]
public void Win32Repository_MustNotStoreDuplicates_WhileAddingItemsWithSameHashCode(string name, string exename, string fullPath, string description1, string description2)
{
// Arrange
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
Win32 item1 = new Win32
{
Name = name,
ExecutableName = exename,
FullPath = fullPath,
Description = description1
};
Win32 item2 = new Win32
{
Name = name,
ExecutableName = exename,
FullPath = fullPath,
Description = description2
};
// Act
_win32ProgramRepository.Add(item1);
Assert.AreEqual(_win32ProgramRepository.Count(), 1);
// To add an item with the same hashCode, ie, same name, exename and fullPath
_win32ProgramRepository.Add(item2);
// Assert, count still remains 1 because they are duplicate items
Assert.AreEqual(_win32ProgramRepository.Count(), 1);
}
[TestCase("path.appref-ms")]
public void Win32ProgramRepository_MustCallOnAppCreatedForApprefApps_WhenCreatedEventIsRaised(string path)
{
// Arrange
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Created, "directory", path);
// Act
_fileSystemMocks[0].Raise(m => m.Created += null, e);
// Assert
Assert.AreEqual(_win32ProgramRepository.Count(), 1);
Assert.AreEqual(_win32ProgramRepository.ElementAt(0).AppType, 2);
}
[TestCase("directory", "path.appref-ms")]
public void Win32ProgramRepository_MustCallOnAppDeletedForApprefApps_WhenDeletedEventIsRaised(string directory, string path)
{
// Arrange
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Deleted, directory, path);
string fullPath = directory + "\\" + path;
Win32 item = Win32.GetAppFromPath(fullPath);
_win32ProgramRepository.Add(item);
// Act
_fileSystemMocks[0].Raise(m => m.Deleted += null, e);
// Assert
Assert.AreEqual(_win32ProgramRepository.Count(), 0);
}
[TestCase("directory", "oldpath.appref-ms", "newpath.appref-ms")]
public void Win32ProgramRepository_MustCallOnAppRenamedForApprefApps_WhenRenamedEventIsRaised(string directory, string oldpath, string newpath)
{
// Arrange
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
RenamedEventArgs e = new RenamedEventArgs(WatcherChangeTypes.Renamed, directory , newpath, oldpath);
string oldFullPath = directory + "\\" + oldpath;
string newFullPath = directory + "\\" + newpath;
Win32 olditem = Win32.GetAppFromPath(oldFullPath);
Win32 newitem = Win32.GetAppFromPath(newFullPath);
_win32ProgramRepository.Add(olditem);
// Act
_fileSystemMocks[0].Raise(m => m.Renamed += null, e);
// Assert
Assert.AreEqual(_win32ProgramRepository.Count(), 1);
Assert.IsTrue(_win32ProgramRepository.Contains(newitem));
Assert.IsFalse(_win32ProgramRepository.Contains(olditem));
}
[TestCase("path.exe")]
public void Win32ProgramRepository_MustCallOnAppCreatedForExeApps_WhenCreatedEventIsRaised(string path)
{
// Arrange
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Created, "directory", path);
// FileVersionInfo must be mocked for exe applications
var mockFileVersionInfo = new Mock<IFileVersionInfoWrapper>();
mockFileVersionInfo.Setup(m => m.GetVersionInfo(It.IsAny<string>())).Returns((FileVersionInfo)null);
Win32._fileVersionInfoWrapper = mockFileVersionInfo.Object;
// Act
_fileSystemMocks[0].Raise(m => m.Created += null, e);
// Assert
Assert.AreEqual(_win32ProgramRepository.Count(), 1);
Assert.AreEqual(_win32ProgramRepository.ElementAt(0).AppType, 2);
}
[TestCase("directory", "path.exe")]
public void Win32ProgramRepository_MustCallOnAppDeletedForExeApps_WhenDeletedEventIsRaised(string directory, string path)
{
// Arrange
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Deleted, directory, path);
// FileVersionInfo must be mocked for exe applications
var mockFileVersionInfo = new Mock<IFileVersionInfoWrapper>();
mockFileVersionInfo.Setup(m => m.GetVersionInfo(It.IsAny<string>())).Returns((FileVersionInfo)null);
Win32._fileVersionInfoWrapper = mockFileVersionInfo.Object;
string fullPath = directory + "\\" + path;
Win32 item = Win32.GetAppFromPath(fullPath);
_win32ProgramRepository.Add(item);
// Act
_fileSystemMocks[0].Raise(m => m.Deleted += null, e);
// Assert
Assert.AreEqual(_win32ProgramRepository.Count(), 0);
}
[TestCase("directory", "oldpath.appref-ms", "newpath.appref-ms")]
public void Win32ProgramRepository_MustCallOnAppRenamedForExeApps_WhenRenamedEventIsRaised(string directory, string oldpath, string newpath)
{
// Arrange
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
RenamedEventArgs e = new RenamedEventArgs(WatcherChangeTypes.Renamed, directory, newpath, oldpath);
string oldFullPath = directory + "\\" + oldpath;
string newFullPath = directory + "\\" + newpath;
// FileVersionInfo must be mocked for exe applications
var mockFileVersionInfo = new Mock<IFileVersionInfoWrapper>();
mockFileVersionInfo.Setup(m => m.GetVersionInfo(It.IsAny<string>())).Returns((FileVersionInfo)null);
Win32._fileVersionInfoWrapper = mockFileVersionInfo.Object;
Win32 olditem = Win32.GetAppFromPath(oldFullPath);
Win32 newitem = Win32.GetAppFromPath(newFullPath);
_win32ProgramRepository.Add(olditem);
// Act
_fileSystemMocks[0].Raise(m => m.Renamed += null, e);
// Assert
Assert.AreEqual(_win32ProgramRepository.Count(), 1);
Assert.IsTrue(_win32ProgramRepository.Contains(newitem));
Assert.IsFalse(_win32ProgramRepository.Contains(olditem));
}
[TestCase("path.url")]
public void Win32ProgramRepository_MustCallOnAppCreatedForUrlApps_WhenCreatedEventIsRaised(string path)
{
// Arrange
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Created, "directory", path);
// File.ReadAllLines must be mocked for url applications
var mockFile = new Mock<IFileWrapper>();
mockFile.Setup(m => m.ReadAllLines(It.IsAny<string>())).Returns(new string[] { "URL=steam://rungameid/1258080" , "IconFile=iconFile"});
Win32._fileWrapper = mockFile.Object;
// Act
_fileSystemMocks[0].Raise(m => m.Created += null, e);
// Assert
Assert.AreEqual(_win32ProgramRepository.Count(), 1);
Assert.AreEqual(_win32ProgramRepository.ElementAt(0).AppType, 1); // Internet Shortcut Application
}
[TestCase("directory", "path.url")]
public void Win32ProgramRepository_MustCallOnAppDeletedForUrlApps_WhenDeletedEventIsRaised(string directory, string path)
{
// Arrange
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Deleted, directory, path);
// File.ReadAllLines must be mocked for url applications
var mockFile = new Mock<IFileWrapper>();
mockFile.Setup(m => m.ReadAllLines(It.IsAny<string>())).Returns(new string[] { "URL=steam://rungameid/1258080", "IconFile=iconFile" });
Win32._fileWrapper = mockFile.Object;
string fullPath = directory + "\\" + path;
Win32 item = Win32.GetAppFromPath(fullPath);
_win32ProgramRepository.Add(item);
// Act
_fileSystemMocks[0].Raise(m => m.Deleted += null, e);
// Assert
Assert.AreEqual(_win32ProgramRepository.Count(), 0);
}
[TestCase("directory", "oldpath.url", "newpath.url")]
public void Win32ProgramRepository_MustCallOnAppRenamedForUrlApps_WhenRenamedEventIsRaised(string directory, string oldpath, string newpath)
{
// Arrange
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
RenamedEventArgs e = new RenamedEventArgs(WatcherChangeTypes.Renamed, directory, newpath, oldpath);
// File.ReadAllLines must be mocked for url applications
var mockFile = new Mock<IFileWrapper>();
mockFile.Setup(m => m.ReadAllLines(It.IsAny<string>())).Returns(new string[] { "URL=steam://rungameid/1258080", "IconFile=iconFile" });
Win32._fileWrapper = mockFile.Object;
string oldFullPath = directory + "\\" + oldpath;
string newFullPath = directory + "\\" + newpath;
Win32 olditem = Win32.GetAppFromPath(oldFullPath);
Win32 newitem = Win32.GetAppFromPath(newFullPath);
_win32ProgramRepository.Add(olditem);
// Act
_fileSystemMocks[0].Raise(m => m.Renamed += null, e);
// Assert
Assert.AreEqual(_win32ProgramRepository.Count(), 1);
Assert.IsTrue(_win32ProgramRepository.Contains(newitem));
Assert.IsFalse(_win32ProgramRepository.Contains(olditem));
}
[TestCase("path.lnk")]
public void Win32ProgramRepository_MustCallOnAppCreatedForLnkApps_WhenCreatedEventIsRaised(string path)
{
// Arrange
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Created, "directory", path);
// ShellLinkHelper must be mocked for lnk applications
var mockShellLink = new Mock<IShellLinkHelper>();
mockShellLink.Setup(m => m.RetrieveTargetPath(It.IsAny<string>())).Returns(String.Empty);
Win32._helper = mockShellLink.Object;
// Act
_fileSystemMocks[0].Raise(m => m.Created += null, e);
// Assert
Assert.AreEqual(_win32ProgramRepository.Count(), 1);
Assert.AreEqual(_win32ProgramRepository.ElementAt(0).AppType, 2);
}
[TestCase("directory", "path.lnk")]
public void Win32ProgramRepository_MustCallOnAppDeletedForLnkApps_WhenDeletedEventIsRaised(string directory, string path)
{
// Arrange
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Deleted, directory, path);
// ShellLinkHelper must be mocked for lnk applications
var mockShellLink = new Mock<IShellLinkHelper>();
mockShellLink.Setup(m => m.RetrieveTargetPath(It.IsAny<string>())).Returns(String.Empty);
Win32._helper = mockShellLink.Object;
string fullPath = directory + "\\" + path;
Win32 item = new Win32
{
Name = "path",
ExecutableName = "path.exe",
ParentDirectory = "directory",
FullPath = "directory\\path.exe",
LnkResolvedPath = "directory\\path.lnk" // This must be equal for lnk applications
};
_win32ProgramRepository.Add(item);
// Act
_fileSystemMocks[0].Raise(m => m.Deleted += null, e);
// Assert
Assert.AreEqual(_win32ProgramRepository.Count(), 0);
}
[TestCase("directory", "oldpath.lnk", "path.lnk")]
public void Win32ProgramRepository_MustCallOnAppRenamedForLnkApps_WhenRenamedEventIsRaised(string directory, string oldpath, string path)
{
// Arrange
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
RenamedEventArgs e = new RenamedEventArgs(WatcherChangeTypes.Renamed, directory, path, oldpath);
string oldFullPath = directory + "\\" + oldpath;
string FullPath = directory + "\\" + path;
// ShellLinkHelper must be mocked for lnk applications
var mockShellLink = new Mock<IShellLinkHelper>();
mockShellLink.Setup(m => m.RetrieveTargetPath(It.IsAny<string>())).Returns(String.Empty);
Win32._helper = mockShellLink.Object;
// old item and new item are the actual items when they are in existence
Win32 olditem = new Win32
{
Name = "oldpath",
ExecutableName = path,
FullPath = FullPath,
};
Win32 newitem = new Win32
{
Name = "path",
ExecutableName = path,
FullPath = FullPath,
};
_win32ProgramRepository.Add(olditem);
// Act
_fileSystemMocks[0].Raise(m => m.Renamed += null, e);
// Assert
Assert.AreEqual(_win32ProgramRepository.Count(), 1);
Assert.IsTrue(_win32ProgramRepository.Contains(newitem));
Assert.IsFalse(_win32ProgramRepository.Contains(olditem));
}
}
}