diff --git a/DeployClient.Tests/DeployClient.Tests.csproj b/DeployClient.Tests/DeployClient.Tests.csproj new file mode 100644 index 0000000..902ec85 --- /dev/null +++ b/DeployClient.Tests/DeployClient.Tests.csproj @@ -0,0 +1,87 @@ + + + + + + Debug + AnyCPU + {2F81EBF7-A395-4716-8B1C-23FE9EE8B7CD} + Library + Properties + DeployClient.Tests + DeployClient.Tests + v4.8 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\FluentAssertions.5.10.3\lib\net47\FluentAssertions.dll + + + ..\packages\MSTest.TestFramework.2.1.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + + + ..\packages\MSTest.TestFramework.2.1.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + + + + + ..\packages\System.IO.Abstractions.11.0.7\lib\net461\System.IO.Abstractions.dll + + + ..\packages\System.IO.Abstractions.TestingHelpers.11.0.7\lib\net461\System.IO.Abstractions.TestingHelpers.dll + + + + + + + + + + + + + + {b6122ccd-75f9-4def-8daa-e11789d6d6d8} + DeployClient + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/DeployClient.Tests/PackageCrawlerTests.cs b/DeployClient.Tests/PackageCrawlerTests.cs new file mode 100644 index 0000000..c9d8345 --- /dev/null +++ b/DeployClient.Tests/PackageCrawlerTests.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Abstractions; +using System.IO.Abstractions.TestingHelpers; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace DeployClient.Tests +{ + [TestClass] + public class PackageCrawlerTests + { + private string _currentExecutionPath; + + + [TestInitialize] + public void Initialize() + { + _currentExecutionPath = $@"{Environment.CurrentDirectory}\DeployClient"; + } + + + #region constructor tests + + [TestMethod] + public void FileCrawler_Should_Use_Execution_Directory_When_Null_Argument() + { + // Arrange + var mockFileSystem = GetBasicPreparedMockFileSystem(_currentExecutionPath); + + // Act + var fileCrawler = new PackageCrawler(mockFileSystem, null); + + // Assert + fileCrawler.PackageDirectoryPath.Should().Be(_currentExecutionPath); + } + + [TestMethod] + public void FileCrawler_Should_Use_Execution_Directory_When_Empty_String_Argument() + { + // Arrange + var mockFileSystem = GetBasicPreparedMockFileSystem(_currentExecutionPath); + + // Act + var fileCrawler = new PackageCrawler(mockFileSystem, string.Empty); + + // Assert + fileCrawler.PackageDirectoryPath.Should().Be(_currentExecutionPath); + } + + [TestMethod] + public void FileCrawler_Should_Use_Execution_Directory_When_Whitespaces_Argument() + { + // Arrange + var mockFileSystem = GetBasicPreparedMockFileSystem(_currentExecutionPath); + + // Act + var fileCrawler = new PackageCrawler(mockFileSystem, " "); + + // Assert + fileCrawler.PackageDirectoryPath.Should().Be(_currentExecutionPath); + } + + [TestMethod] + public void FileCrawler_Should_Throw_Exception_When_Directory_Not_Exist() + { + // Arrange + var mockFileSystem = GetBasicPreparedMockFileSystem(_currentExecutionPath); + + // Act + // ReSharper disable once ObjectCreationAsStatement + Action initialization = () => new PackageCrawler(mockFileSystem, "FooBar"); + + // Assert + initialization.Should().Throw(); + } + + [TestMethod] + public void FileCrawler_Should_Initialize_With_Provided_Directory_When_Directory_Exist() + { + // Arrange + const string directoryName = "Packages"; + + var mockFileSystem = GetBasicPreparedMockFileSystem(_currentExecutionPath); + mockFileSystem.Directory.CreateDirectory(directoryName); + + // Act + var fileCrawler = new PackageCrawler(mockFileSystem, directoryName); + + // Assert + fileCrawler.PackageDirectoryPath.Should().Be($@"{_currentExecutionPath}\{directoryName}"); + } + + + + [TestMethod] + public void FileCrawler_Should_Initialize_With_Provided_Directory_When_Directory_Exist_And_Full_Path() + { + // Arrange + const string directoryName = "Packages"; + + var mockFileSystem = GetBasicPreparedMockFileSystem(_currentExecutionPath); + var directory = mockFileSystem.Directory.CreateDirectory(directoryName); + + var packageDirectoryPath = directory.FullName; + + // Act + var fileCrawler = new PackageCrawler(mockFileSystem, packageDirectoryPath); + + // Assert + fileCrawler.PackageDirectoryPath.Should().Be(packageDirectoryPath); + } + + #endregion + + #region GetPackagesFullPaths tests + + [TestMethod] + public void GetPackagesFullPaths_Should_Return_Empty_Enumerable_When_Directory_Empty() + { + // Arrange + const string directoryName = "Packages"; + + var mockFileSystem = GetBasicPreparedMockFileSystem(_currentExecutionPath); + mockFileSystem.Directory.CreateDirectory(directoryName); + + var packageCrawler = new PackageCrawler(mockFileSystem, directoryName); + + // Act + var packages = packageCrawler.GetPackagesFullPaths(); + + // Assert + packages.Should().BeEmpty(); + } + + [TestMethod] + public void GetPackagesFullPaths_Should_Return_Zip_Files_Full_Paths_When_Directory_Contains_Zip_Files_Only() + { + // Arrange + const string directoryName = "Packages"; + + var mockFiles = GetBasicMockFiles(); + mockFiles.Add($@"{_currentExecutionPath}\Packages\TestPackage_1.zip", new MockFileData("#1 fake zip file.")); + mockFiles.Add($@"{_currentExecutionPath}\Packages\TestPackage_2.zip", new MockFileData("#2 fake zip file.")); + mockFiles.Add($@"{_currentExecutionPath}\Packages\TestPackage_3.zip", new MockFileData("#3 fake zip file.")); + + var mockFileSystem = new MockFileSystem(mockFiles, _currentExecutionPath); + + var packageCrawler = new PackageCrawler(mockFileSystem, directoryName); + + // Act + var packages = packageCrawler.GetPackagesFullPaths(); + + // Assert + packages.Should().HaveCount(3); + } + + [TestMethod] + public void GetPackagesFullPaths_Should_Return_Only_Zip_Files_Full_Paths_When_Directory_Contains_Different_File_Kinds() + { + // Arrange + const string directoryName = "Packages"; + + var mockFiles = GetBasicMockFiles(); + mockFiles.Add($@"{_currentExecutionPath}\Packages\TestPackage.zip", new MockFileData("A fake zip file.")); + mockFiles.Add($@"{_currentExecutionPath}\Packages\TestTextFile.txt", new MockFileData("Just a text file.")); + + var mockFileSystem = new MockFileSystem(mockFiles, _currentExecutionPath); + + var packageCrawler = new PackageCrawler(mockFileSystem, directoryName); + + // Act + var packages = packageCrawler.GetPackagesFullPaths(); + + // Assert + packages.Should().HaveCount(1); + } + + [TestMethod] + public void GetPackagesFullPaths_Should_Return_Zip_Files_Full_Paths_When_Directory_Contains_Different_File_Kinds_And_Full_Path_Initialization() + { + // Arrange + var packageDirectoryPath = $@"{_currentExecutionPath}\Packages"; + + var mockFiles = GetBasicMockFiles(); + mockFiles.Add($@"{packageDirectoryPath}\TestPackage.zip", new MockFileData("A fake zip file.")); + mockFiles.Add($@"{packageDirectoryPath}\TestTextFile.txt", new MockFileData("Just a text file.")); + + var mockFileSystem = new MockFileSystem(mockFiles, _currentExecutionPath); + + var packageCrawler = new PackageCrawler(mockFileSystem, packageDirectoryPath); + + // Act + var packages = packageCrawler.GetPackagesFullPaths(); + + // Assert + packages.Should().HaveCount(1); + } + + [TestMethod] + public void GetPackagesFullPaths_Should_Return_Zip_Files_Of_Top_Directory_Only_When_Sub_Directory_Exists_With_Zip_Files() + { + // Arrange + const string directoryName = "Packages"; + + var mockFiles = GetBasicMockFiles(); + mockFiles.Add($@"{_currentExecutionPath}\Packages\TestPackage.zip", new MockFileData("A fake zip file.")); + mockFiles.Add($@"{_currentExecutionPath}\Packages\SubDirectory\SubDir_TestPackage.zip", new MockFileData("A fake zip file from sub-directory.")); + + var mockFileSystem = new MockFileSystem(mockFiles, _currentExecutionPath); + + var packageCrawler = new PackageCrawler(mockFileSystem, directoryName); + + // Act + var packages = packageCrawler.GetPackagesFullPaths(); + + // Assert + packages.Should().HaveCount(1); + } + + #endregion + + + #region helper methods + + private IFileSystem GetBasicPreparedMockFileSystem(string currentDirectoryPath) + { + return new MockFileSystem(GetBasicMockFiles(), currentDirectoryPath); + } + + private IDictionary GetBasicMockFiles() + { + return new Dictionary + { + { + $@"{_currentExecutionPath}\DeployClient_dummy.txt", + new MockFileData("Represents the executable to know where we are.") + } + }; + } + + #endregion + } +} diff --git a/DeployClient.Tests/Properties/AssemblyInfo.cs b/DeployClient.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..42d1e25 --- /dev/null +++ b/DeployClient.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("DeployClient.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DeployClient.Tests")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("2f81ebf7-a395-4716-8b1c-23fe9ee8b7cd")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DeployClient.Tests/packages.config b/DeployClient.Tests/packages.config new file mode 100644 index 0000000..8b45c61 --- /dev/null +++ b/DeployClient.Tests/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/DeployClient/App.config b/DeployClient/App.config index 796693c..9091ff8 100644 --- a/DeployClient/App.config +++ b/DeployClient/App.config @@ -6,7 +6,7 @@ - + diff --git a/DeployClient/DeployClient.csproj b/DeployClient/DeployClient.csproj index db85a3a..3f2b03e 100644 --- a/DeployClient/DeployClient.csproj +++ b/DeployClient/DeployClient.csproj @@ -8,7 +8,7 @@ Exe DeployClient DeployClient - v4.5 + v4.8 512 true @@ -41,6 +41,9 @@ + + ..\packages\System.IO.Abstractions.11.0.7\lib\net461\System.IO.Abstractions.dll + ..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll @@ -56,6 +59,7 @@ + diff --git a/DeployClient/PackageCrawler.cs b/DeployClient/PackageCrawler.cs new file mode 100644 index 0000000..8c2e573 --- /dev/null +++ b/DeployClient/PackageCrawler.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.IO; +using System.IO.Abstractions; +using System.Linq; + +namespace DeployClient +{ + internal class PackageCrawler + { + private readonly IFileSystem _fileSystem; + private readonly IDirectoryInfo _packageDirectoryInfo; + + + + /// + /// The directory that contains to installing files. + /// + public string PackageDirectoryPath => _packageDirectoryInfo.FullName; + + + + public PackageCrawler(string packageDirectoryPath) : this(new FileSystem(), packageDirectoryPath) + { + } + + public PackageCrawler(IFileSystem fileSystem, string packageDirectoryPath) + { + _fileSystem = fileSystem; + + _packageDirectoryInfo = GetPackageDirectory(packageDirectoryPath); + } + + + + private IDirectoryInfo GetPackageDirectory(string packageDirectoryPath) + { + var path = string.IsNullOrWhiteSpace(packageDirectoryPath) + ? _fileSystem.Directory.GetCurrentDirectory() + : packageDirectoryPath; + + var directoryInfo = _fileSystem.DirectoryInfo.FromDirectoryName(path); + + if (!directoryInfo.Exists) + { + throw new DirectoryNotFoundException($"Directory \"{directoryInfo.FullName}\" not found."); + } + + return directoryInfo; + } + + /// + /// Returns all installation packages (*.zip files) full file paths which were found in defined . + /// + /// Enumerable of full file paths. + internal IEnumerable GetPackagesFullPaths() + { + const string searchPattern = "*.zip"; + + return _packageDirectoryInfo.GetFiles(searchPattern, SearchOption.TopDirectoryOnly).Select(f => f.FullName); + + } + } +} diff --git a/DeployClient/Program.cs b/DeployClient/Program.cs index 8329565..0e8b688 100644 --- a/DeployClient/Program.cs +++ b/DeployClient/Program.cs @@ -1,8 +1,6 @@ using Cantarus.Libraries.Encryption; using System; -using System.Collections; using System.Collections.Generic; -using System.Configuration; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -11,11 +9,11 @@ namespace DeployClient { - class Program + internal static class Program { - internal static CommandLineOptions Options = new CommandLineOptions(); + internal static readonly CommandLineOptions Options = new CommandLineOptions(); - enum ExitCode : int + private enum ExitCode { Success = 0, Error = 1, @@ -24,7 +22,7 @@ enum ExitCode : int InstallFailure = 4 } - static async Task Main(string[] args) + private static async Task Main(string[] args) { try { @@ -55,29 +53,12 @@ static async Task Main(string[] args) // Output identifying module archives. WriteLine("Identifying module archives..."); - // Read zip files in current directory. - string currentDirectory = Directory.GetCurrentDirectory(); - List zipFiles = new List(Directory.GetFiles(currentDirectory, "*.zip")); - - // Is there something to do? - if (zipFiles.Count <= 0) - { - // No, exit. - WriteLine("No module archives found."); - WriteLine("Exiting."); - ReadLine(); - Environment.Exit((int)ExitCode.NoModulesFound); - } - - // Inform user of modules found. - WriteLine(string.Format("Found {0} module archives in {1}:", zipFiles.Count, currentDirectory)); - - foreach (string zipFile in zipFiles) - { - WriteLine(string.Format("\t{0}. {1}", zipFiles.IndexOf(zipFile) + 1, Path.GetFileName(zipFile))); - } - WriteLine(); + // Read zip files from packages directory if provided, otherwise from current directory + var packageCrawler = new PackageCrawler(Options.PackagesDirectoryPath); + var zipFiles = packageCrawler.GetPackagesFullPaths().ToArray(); + ValidateFoundPackages(zipFiles, packageCrawler.PackageDirectoryPath); + if (!Options.NoPrompt) { // Prompt to continue. @@ -269,6 +250,11 @@ private static void GetSettings(string[] args) { Options.EncryptionKey = Properties.Settings.Default.EncryptionKey; } + + if (string.IsNullOrWhiteSpace(Options.PackagesDirectoryPath)) + { + Options.PackagesDirectoryPath = Properties.Settings.Default.PackagesDirectory; + } } } @@ -352,6 +338,31 @@ private static void WriteLine(string message = "") Console.WriteLine(message); } + private static void ValidateFoundPackages(IEnumerable zipFiles, string directory) + { + var packages = zipFiles?.ToArray() ?? new string[0]; + + // Is there something to do? + if (!packages.Any()) + { + // No, exit. + WriteLine("No module archives found."); + WriteLine("Exiting."); + ReadLine(); + Environment.Exit((int)ExitCode.NoModulesFound); + } + + // Inform user of modules found. + WriteLine($"Found {packages.Length} module archives in {directory}:"); + + var fileCounter = 1; + foreach (var package in packages) + { + WriteLine($"\t{fileCounter++}. {Path.GetFileName(package)}"); + } + WriteLine(); + } + private static string ReadLine() { if (Options.IsSilent || Options.NoPrompt) diff --git a/DeployClient/Properties/AssemblyInfo.cs b/DeployClient/Properties/AssemblyInfo.cs index 84889ea..687f22b 100644 --- a/DeployClient/Properties/AssemblyInfo.cs +++ b/DeployClient/Properties/AssemblyInfo.cs @@ -34,3 +34,5 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("0.8.0.0")] [assembly: AssemblyFileVersion("0.8.0.0")] + +[assembly: InternalsVisibleTo("DeployClient.Tests")] diff --git a/DeployClient/packages.config b/DeployClient/packages.config index 9c3293f..5c2cb18 100644 --- a/DeployClient/packages.config +++ b/DeployClient/packages.config @@ -1,5 +1,6 @@  + \ No newline at end of file diff --git a/PolyDeploy.sln b/PolyDeploy.sln index 61c3a36..8870beb 100644 --- a/PolyDeploy.sln +++ b/PolyDeploy.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26403.7 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30128.74 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PolyDeploy", "PolyDeploy\PolyDeploy.csproj", "{B15D41DD-2D1A-490C-977F-2F14E56447D5}" EndProject @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Encryption", "Encryption\En EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeployClient", "DeployClient\DeployClient.csproj", "{B6122CCD-75F9-4DEF-8DAA-E11789D6D6D8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeployClient.Tests", "DeployClient.Tests\DeployClient.Tests.csproj", "{2F81EBF7-A395-4716-8B1C-23FE9EE8B7CD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Clients|Any CPU = Clients|Any CPU @@ -34,8 +36,17 @@ Global {B6122CCD-75F9-4DEF-8DAA-E11789D6D6D8}.Debug|Any CPU.Build.0 = Debug|Any CPU {B6122CCD-75F9-4DEF-8DAA-E11789D6D6D8}.Release|Any CPU.ActiveCfg = Release|Any CPU {B6122CCD-75F9-4DEF-8DAA-E11789D6D6D8}.Release|Any CPU.Build.0 = Release|Any CPU + {2F81EBF7-A395-4716-8B1C-23FE9EE8B7CD}.Clients|Any CPU.ActiveCfg = Release|Any CPU + {2F81EBF7-A395-4716-8B1C-23FE9EE8B7CD}.Clients|Any CPU.Build.0 = Release|Any CPU + {2F81EBF7-A395-4716-8B1C-23FE9EE8B7CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F81EBF7-A395-4716-8B1C-23FE9EE8B7CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F81EBF7-A395-4716-8B1C-23FE9EE8B7CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F81EBF7-A395-4716-8B1C-23FE9EE8B7CD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9AE13660-40A1-4C85-B612-999111FB6DC7} + EndGlobalSection EndGlobal