diff --git a/src/Paket.Bootstrapper/BootstrapperHelper.cs b/src/Paket.Bootstrapper/BootstrapperHelper.cs index 6697ebe628..b33ccf28e9 100644 --- a/src/Paket.Bootstrapper/BootstrapperHelper.cs +++ b/src/Paket.Bootstrapper/BootstrapperHelper.cs @@ -3,6 +3,8 @@ using System.Net; using System.Linq; using System.Reflection; +using System.Security.Cryptography; +using Paket.Bootstrapper.HelperProxies; namespace Paket.Bootstrapper { @@ -103,5 +105,42 @@ internal static void FileMove(string oldPath, string newPath) File.Move(oldPath, newPath); } + + public static bool ValidateHash(IFileSystemProxy fileSystem, string hashFile, string version, string paketFile) + { + if (hashFile == null) + { + ConsoleImpl.WriteTrace("No hash file expected, bypassing check."); + return true; + } + + if (!fileSystem.FileExists(hashFile)) + { + ConsoleImpl.WriteInfo("No hash file of version {0} found.", version); + + return true; + } + + var dict = fileSystem.ReadAllLines(hashFile) + .Select(i => i.Split(' ')) + .ToDictionary(i => i[1], i => i[0]); + + string expectedHash; + if (!dict.TryGetValue("paket.exe", out expectedHash)) + { + fileSystem.DeleteFile(hashFile); + + throw new InvalidDataException("Paket hash file is corrupted"); + } + + using (var stream = fileSystem.OpenRead(paketFile)) + using (var sha = SHA256.Create()) + { + byte[] checksum = sha.ComputeHash(stream); + var hash = BitConverter.ToString(checksum).Replace("-", String.Empty); + + return string.Equals(expectedHash, hash, StringComparison.OrdinalIgnoreCase); + } + } } } diff --git a/src/Paket.Bootstrapper/DownloadStrategies/CacheDownloadStrategy.cs b/src/Paket.Bootstrapper/DownloadStrategies/CacheDownloadStrategy.cs index b3a095bfcc..bbe9ccff81 100644 --- a/src/Paket.Bootstrapper/DownloadStrategies/CacheDownloadStrategy.cs +++ b/src/Paket.Bootstrapper/DownloadStrategies/CacheDownloadStrategy.cs @@ -11,6 +11,11 @@ internal class CacheDownloadStrategy : DownloadStrategy, IHaveEffectiveStrategy { public override string Name { get { return String.Format("{0} - cached", EffectiveStrategy.Name); } } + public override bool CanDownloadHashFile + { + get { return EffectiveStrategy.CanDownloadHashFile; } + } + private readonly string _paketCacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "NuGet", "Cache", "Paket"); @@ -49,38 +54,65 @@ protected override string GetLatestVersionCore(bool ignorePrerelease) } } - protected override void DownloadVersionCore(string latestVersion, string target) + protected override void DownloadVersionCore(string latestVersion, string target, string hashfile) { var cached = Path.Combine(_paketCacheDir, latestVersion, "paket.exe"); - if (!FileSystemProxy.FileExists(cached) || !ValidateHash(latestVersion, cached)) + if (!FileSystemProxy.FileExists(cached)) { ConsoleImpl.WriteInfo("Version {0} not found in cache.", latestVersion); - EffectiveStrategy.DownloadVersion(latestVersion, target); - - ConsoleImpl.WriteTrace("Caching version {0} for later", latestVersion); - FileSystemProxy.CreateDirectory(Path.GetDirectoryName(cached)); - FileSystemProxy.CopyFile(target, cached); + DownloadAndPlaceInCache(latestVersion, target, cached, hashfile); + return; } - else + + if (!BootstrapperHelper.ValidateHash(FileSystemProxy, hashfile, latestVersion, cached)) { - ConsoleImpl.WriteInfo("Copying version {0} from cache.", latestVersion); - ConsoleImpl.WriteTrace("{0} -> {1}", cached, target); - FileSystemProxy.CopyFile(cached, target, true); + ConsoleImpl.WriteWarning("Version {0} found in cache but it's hash isn't valid.", latestVersion); + + DownloadAndPlaceInCache(latestVersion, target, cached, hashfile); + return; } + + ConsoleImpl.WriteInfo("Copying version {0} from cache.", latestVersion); + ConsoleImpl.WriteTrace("{0} -> {1}", cached, target); + FileSystemProxy.CopyFile(cached, target, true); } - protected override void DownloadHashFileCore(string latestVersion) + private void DownloadAndPlaceInCache(string latestVersion, string target, string cached, string hashfile) { - var cached = Path.Combine(_paketCacheDir, latestVersion, "paket-sha256.txt"); + EffectiveStrategy.DownloadVersion(latestVersion, target, hashfile); + + ConsoleImpl.WriteTrace("Caching version {0} for later", latestVersion); + FileSystemProxy.CreateDirectory(Path.GetDirectoryName(cached)); + FileSystemProxy.CopyFile(target, cached); + } + + protected override string DownloadHashFileCore(string latestVersion) + { + if (!EffectiveStrategy.CanDownloadHashFile) + { + return null; + } + + var cached = GetHashFilePathInCache(latestVersion); if (!FileSystemProxy.FileExists(cached)) { ConsoleImpl.WriteInfo("Hash file of version {0} not found in cache.", latestVersion); + var effectivePath = EffectiveStrategy.DownloadHashFile(latestVersion); + if(effectivePath == null) + { + // 'EffectiveStrategy.CanDownloadHashFile' should have returned false... + return null; + } - EffectiveStrategy.DownloadHashFile(latestVersion); + ConsoleImpl.WriteTrace("Copying hash file in cache."); + ConsoleImpl.WriteTrace("{0} -> {1}", effectivePath, cached); + FileSystemProxy.CopyFile(effectivePath, cached, true); } + + return cached; } protected override void SelfUpdateCore(string latestVersion) @@ -114,37 +146,9 @@ private string GetLatestVersionInCache(bool ignorePrerelease) .FirstOrDefault() ?? "0"; } - private bool ValidateHash(string version, string paketFile) + public string GetHashFilePathInCache(string version) { - var hashFile = Path.Combine(_paketCacheDir, version, "paket-sha256.txt"); - - if (!FileSystemProxy.FileExists(hashFile)) - { - ConsoleImpl.WriteInfo("No hash file of version {0} found in cache.", version); - - return true; - } - - var dict = FileSystemProxy.ReadAllLines(hashFile) - .Select(i => i.Split(' ')) - .ToDictionary(i => i[1], i => i[0]); - - string expectedHash; - if (!dict.TryGetValue("paket.exe", out expectedHash)) - { - FileSystemProxy.DeleteFile(hashFile); - - throw new InvalidDataException("Paket hash file is corrupted"); - } - - using (var stream = FileSystemProxy.OpenRead(paketFile)) - using (var sha = new SHA256Managed()) - { - byte[] checksum = sha.ComputeHash(stream); - var hash = BitConverter.ToString(checksum).Replace("-", String.Empty); - - return string.Equals(expectedHash, hash, StringComparison.OrdinalIgnoreCase); - } + return Path.Combine(_paketCacheDir, version, "paket-sha256.txt"); } } } \ No newline at end of file diff --git a/src/Paket.Bootstrapper/DownloadStrategies/DownloadStrategy.cs b/src/Paket.Bootstrapper/DownloadStrategies/DownloadStrategy.cs index 9dc52a10ce..ab02b77ea0 100644 --- a/src/Paket.Bootstrapper/DownloadStrategies/DownloadStrategy.cs +++ b/src/Paket.Bootstrapper/DownloadStrategies/DownloadStrategy.cs @@ -6,17 +6,17 @@ namespace Paket.Bootstrapper.DownloadStrategies public abstract class DownloadStrategy : IDownloadStrategy { public abstract string Name { get; } + public abstract bool CanDownloadHashFile { get; } + public IDownloadStrategy FallbackStrategy { get; set; } public string GetLatestVersion(bool ignorePrerelease) { return Wrap(() => GetLatestVersionCore(ignorePrerelease), "GetLatestVersion"); } - public void DownloadVersion(string latestVersion, string target) + public void DownloadVersion(string latestVersion, string target, string hashfile) { - DownloadHashFile(latestVersion); - - Wrap(() => DownloadVersionCore(latestVersion, target), "DownloadVersion"); + Wrap(() => DownloadVersionCore(latestVersion, target, hashfile), "DownloadVersion"); } public void SelfUpdate(string latestVersion) @@ -24,15 +24,15 @@ public void SelfUpdate(string latestVersion) Wrap(() => SelfUpdateCore(latestVersion), "SelfUpdate"); } - public void DownloadHashFile(string latestVersion) + public string DownloadHashFile(string latestVersion) { - Wrap(() => DownloadHashFileCore(latestVersion), "DownloadHashFile"); + return Wrap(() => DownloadHashFileCore(latestVersion), "DownloadHashFile"); } protected abstract string GetLatestVersionCore(bool ignorePrerelease); - protected abstract void DownloadVersionCore(string latestVersion, string target); + protected abstract void DownloadVersionCore(string latestVersion, string target, string hashfile); protected abstract void SelfUpdateCore(string latestVersion); - protected abstract void DownloadHashFileCore(string latestVersion); + protected abstract string DownloadHashFileCore(string latestVersion); private void Wrap(Action action, string actionName) { diff --git a/src/Paket.Bootstrapper/DownloadStrategies/GitHubDownloadStrategy.cs b/src/Paket.Bootstrapper/DownloadStrategies/GitHubDownloadStrategy.cs index 91fb60aefe..bcc20f4b06 100644 --- a/src/Paket.Bootstrapper/DownloadStrategies/GitHubDownloadStrategy.cs +++ b/src/Paket.Bootstrapper/DownloadStrategies/GitHubDownloadStrategy.cs @@ -20,6 +20,11 @@ public static class Constants private IFileSystemProxy FileSystemProxy { get; set; } public override string Name { get { return "Github"; } } + public override bool CanDownloadHashFile + { + get { return true; } + } + public GitHubDownloadStrategy(IWebRequestProxy webRequestProxy, IFileSystemProxy fileSystemProxy) { WebRequestProxy = webRequestProxy; @@ -70,7 +75,7 @@ private List GetVersions(string data) return versions; } - protected override void DownloadVersionCore(string latestVersion, string target) + protected override void DownloadVersionCore(string latestVersion, string target, string hashfile) { var url = String.Format(Constants.PaketExeDownloadUrlTemplate, latestVersion); ConsoleImpl.WriteInfo("Starting download from {0}", url); @@ -78,6 +83,26 @@ protected override void DownloadVersionCore(string latestVersion, string target) var tmpFile = BootstrapperHelper.GetTempFile("paket"); WebRequestProxy.DownloadFile(url, tmpFile); + if (!BootstrapperHelper.ValidateHash(FileSystemProxy, hashfile, latestVersion, tmpFile)) + { + ConsoleImpl.WriteWarning("Hash of downloaded paket.exe is invalid, retrying once"); + + WebRequestProxy.DownloadFile(url, tmpFile); + + if (!BootstrapperHelper.ValidateHash(FileSystemProxy, hashfile, latestVersion, tmpFile)) + { + ConsoleImpl.WriteWarning("Hash of downloaded paket.exe still invalid (Using the file anyway)"); + } + else + { + ConsoleImpl.WriteTrace("Hash of downloaded file successfully found in {0}", hashfile); + } + } + else + { + ConsoleImpl.WriteTrace("Hash of downloaded file successfully found in {0}", hashfile); + } + FileSystemProxy.CopyFile(tmpFile, target, true); FileSystemProxy.DeleteFile(tmpFile); } @@ -114,13 +139,15 @@ protected override void SelfUpdateCore(string latestVersion) } } - protected override void DownloadHashFileCore(string latestVersion) + protected override string DownloadHashFileCore(string latestVersion) { var url = String.Format(Constants.PaketCheckSumDownloadUrlTemplate, latestVersion); ConsoleImpl.WriteInfo("Starting download from {0}", url); var tmpFile = BootstrapperHelper.GetTempFile("paket-sha256.txt"); WebRequestProxy.DownloadFile(url, tmpFile); + + return tmpFile; } } } \ No newline at end of file diff --git a/src/Paket.Bootstrapper/DownloadStrategies/IDownloadStrategy.cs b/src/Paket.Bootstrapper/DownloadStrategies/IDownloadStrategy.cs index 0a53b3b5bd..c62250589c 100644 --- a/src/Paket.Bootstrapper/DownloadStrategies/IDownloadStrategy.cs +++ b/src/Paket.Bootstrapper/DownloadStrategies/IDownloadStrategy.cs @@ -5,8 +5,9 @@ public interface IDownloadStrategy string Name { get; } IDownloadStrategy FallbackStrategy { get; set; } string GetLatestVersion(bool ignorePrerelease); - void DownloadVersion(string latestVersion, string target); + void DownloadVersion(string latestVersion, string target, string hashFile); void SelfUpdate(string latestVersion); - void DownloadHashFile(string latestVersion); + string DownloadHashFile(string latestVersion); + bool CanDownloadHashFile { get; } } } \ No newline at end of file diff --git a/src/Paket.Bootstrapper/DownloadStrategies/NugetDownloadStrategy.cs b/src/Paket.Bootstrapper/DownloadStrategies/NugetDownloadStrategy.cs index aa4b2c618f..ca8bdd41a5 100644 --- a/src/Paket.Bootstrapper/DownloadStrategies/NugetDownloadStrategy.cs +++ b/src/Paket.Bootstrapper/DownloadStrategies/NugetDownloadStrategy.cs @@ -68,6 +68,11 @@ public override string Name get { return "Nuget"; } } + public override bool CanDownloadHashFile + { + get { return false; } + } + protected override string GetLatestVersionCore(bool ignorePrerelease) { IEnumerable allVersions = null; @@ -100,7 +105,7 @@ protected override string GetLatestVersionCore(bool ignorePrerelease) return latestVersion != null ? latestVersion.Original : String.Empty; } - protected override void DownloadVersionCore(string latestVersion, string target) + protected override void DownloadVersionCore(string latestVersion, string target, string hashfile) { var apiHelper = new NugetApiHelper(PaketNugetPackageName, NugetSource); @@ -226,9 +231,10 @@ protected override void SelfUpdateCore(string latestVersion) FileSystemProxy.DeleteDirectory(randomFullPath, true); } - protected override void DownloadHashFileCore(string latestVersion) + protected override string DownloadHashFileCore(string latestVersion) { // TODO: implement get hash file + return null; } } } \ No newline at end of file diff --git a/src/Paket.Bootstrapper/DownloadStrategies/TemporarilyIgnoreUpdatesDownloadStrategy.cs b/src/Paket.Bootstrapper/DownloadStrategies/TemporarilyIgnoreUpdatesDownloadStrategy.cs index 300cd0baeb..fa73b3e440 100644 --- a/src/Paket.Bootstrapper/DownloadStrategies/TemporarilyIgnoreUpdatesDownloadStrategy.cs +++ b/src/Paket.Bootstrapper/DownloadStrategies/TemporarilyIgnoreUpdatesDownloadStrategy.cs @@ -59,9 +59,9 @@ protected override string GetLatestVersionCore(bool ignorePrerelease) return latestVersion; } - protected override void DownloadVersionCore(string latestVersion, string target) + protected override void DownloadVersionCore(string latestVersion, string target, string hashfile) { - _effectiveStrategy.DownloadVersion(latestVersion, target); + _effectiveStrategy.DownloadVersion(latestVersion, target, hashfile); TouchTarget(target); } @@ -72,6 +72,11 @@ protected override void SelfUpdateCore(string latestVersion) public override string Name { get { return string.Format("{0} (temporarily ignore updates)", EffectiveStrategy.Name); } } + public override bool CanDownloadHashFile + { + get { return EffectiveStrategy.CanDownloadHashFile; } + } + public IDownloadStrategy EffectiveStrategy { get { return _effectiveStrategy; } set { @@ -118,9 +123,9 @@ private void TouchTarget(string target) } } - protected override void DownloadHashFileCore(string latestVersion) + protected override string DownloadHashFileCore(string latestVersion) { - _effectiveStrategy.DownloadHashFile(latestVersion); + return _effectiveStrategy.DownloadHashFile(latestVersion); } } } diff --git a/src/Paket.Bootstrapper/Paket.Bootstrapper.csproj b/src/Paket.Bootstrapper/Paket.Bootstrapper.csproj index 20ec43fd81..2fe1eec602 100644 --- a/src/Paket.Bootstrapper/Paket.Bootstrapper.csproj +++ b/src/Paket.Bootstrapper/Paket.Bootstrapper.csproj @@ -23,6 +23,7 @@ prompt 4 false + 5 AnyCPU diff --git a/src/Paket.Bootstrapper/Program.cs b/src/Paket.Bootstrapper/Program.cs index 8393f07566..527ea6a61f 100644 --- a/src/Paket.Bootstrapper/Program.cs +++ b/src/Paket.Bootstrapper/Program.cs @@ -130,10 +130,21 @@ public static void StartPaketBootstrapping(IDownloadStrategy downloadStrategy, D if ((comparison > 0 && specificVersionRequested) || comparison < 0) { + string hashFile = null; + if (downloadStrategy.CanDownloadHashFile) + { + ConsoleImpl.WriteTrace("Downloading hash for v{0} ...", latestVersion); + var downloadHashWatch = Stopwatch.StartNew(); + hashFile = downloadStrategy.DownloadHashFile(latestVersion); + downloadHashWatch.Stop(); + + ConsoleImpl.WriteTrace("Hash download took {0:0.##} second(s)", downloadHashWatch.Elapsed.TotalSeconds); + } + ConsoleImpl.WriteTrace("Downloading v{0} ...", latestVersion); var downloadWatch = Stopwatch.StartNew(); - downloadStrategy.DownloadVersion(latestVersion, dlArgs.Target); + downloadStrategy.DownloadVersion(latestVersion, dlArgs.Target, hashFile); downloadWatch.Stop(); ConsoleImpl.WriteTrace("Download took {0:0.##} second(s)", downloadWatch.Elapsed.TotalSeconds); diff --git a/tests/Paket.Bootstrapper.Tests/DownloadStrategies/CacheDownloadStrategyTest.cs b/tests/Paket.Bootstrapper.Tests/DownloadStrategies/CacheDownloadStrategyTest.cs index 7c32275e69..2b42fe95e3 100644 --- a/tests/Paket.Bootstrapper.Tests/DownloadStrategies/CacheDownloadStrategyTest.cs +++ b/tests/Paket.Bootstrapper.Tests/DownloadStrategies/CacheDownloadStrategyTest.cs @@ -135,10 +135,10 @@ public void DownloadVersion_UseCachedVersion() mockFileProxy.Setup(x => x.FileExists(ItHasFilename("paket.exe"))).Returns(true); //act - sut.DownloadVersion("any", "any"); + sut.DownloadVersion("any", "any", null); //assert - mockEffectiveStrategy.Verify(x => x.DownloadVersion(It.IsAny(), It.IsAny()), Times.Never); + mockEffectiveStrategy.Verify(x => x.DownloadVersion(It.IsAny(), It.IsAny(), null), Times.Never); mockFileProxy.Verify(x => x.CopyFile(It.IsAny(), It.IsAny(), It.IsAny())); } @@ -149,10 +149,10 @@ public void DownloadVersion_DownloadFromFallback() mockFileProxy.Setup(x => x.FileExists(It.IsAny())).Returns(false); //act - sut.DownloadVersion("any", "any"); + sut.DownloadVersion("any", "any", null); //assert - mockEffectiveStrategy.Verify(x => x.DownloadVersion("any", "any")); + mockEffectiveStrategy.Verify(x => x.DownloadVersion("any", "any", null)); mockFileProxy.Verify(x => x.CopyFile(It.IsAny(), It.IsAny(), It.IsAny())); } @@ -170,23 +170,27 @@ public void SelfUpdate() [Test] public void GetLatestVersion_PaketFileCorrupt_DownloadPaketFile() { + const string hashFile = @"C:\hash.txt"; + //arrange mockEffectiveStrategy.Setup(x => x.GetLatestVersion(true)).Throws().Verifiable(); mockFileProxy.Setup(x => x.OpenRead(It.IsAny())).Returns(new MemoryStream(Guid.NewGuid().ToByteArray())); - mockFileProxy.Setup(x => x.ReadAllLines(It.IsAny())).Returns(new[] { Guid.NewGuid().ToString().Replace("-", String.Empty) + " paket.exe" }); + mockFileProxy.Setup(x => x.ReadAllLines(hashFile)).Returns(new[] { Guid.NewGuid().ToString().Replace("-", String.Empty) + " paket.exe" }); mockFileProxy.Setup(x => x.FileExists(It.IsAny())).Returns(true); //act - sut.DownloadVersion("any", "any"); + sut.DownloadVersion("any", "any", hashFile); //assert - mockEffectiveStrategy.Verify(x => x.DownloadVersion(It.IsAny(), It.IsAny()), Times.Once); + mockEffectiveStrategy.Verify(x => x.DownloadVersion(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); mockFileProxy.Verify(x => x.CopyFile(It.IsAny(), It.IsAny(), It.IsAny())); } [Test] public void GetLatestVersion_PaketFileNotCorrupt_DontDownloadPaketFile() { + const string hashFile = @"C:\hash.txt"; + //arrange var content = Guid.NewGuid().ToByteArray(); var sha = new SHA256Managed(); @@ -194,37 +198,87 @@ public void GetLatestVersion_PaketFileNotCorrupt_DontDownloadPaketFile() var hash = BitConverter.ToString(checksum).Replace("-", String.Empty); mockEffectiveStrategy.Setup(x => x.GetLatestVersion(true)).Throws().Verifiable(); mockFileProxy.Setup(x => x.OpenRead(It.IsAny())).Returns(new MemoryStream(content)); - mockFileProxy.Setup(x => x.ReadAllLines(It.IsAny())).Returns(new[] { hash + " paket.exe" }); + mockFileProxy.Setup(x => x.ReadAllLines(hashFile)).Returns(new[] { hash + " paket.exe" }); mockFileProxy.Setup(x => x.FileExists(It.IsAny())).Returns(true); //act - sut.DownloadVersion("any", "any"); + sut.DownloadVersion("any", "any", hashFile); //assert - mockEffectiveStrategy.Verify(x => x.DownloadVersion(It.IsAny(), It.IsAny()), Times.Never); + mockEffectiveStrategy.Verify(x => x.DownloadVersion(It.IsAny(), It.IsAny(), null), Times.Never); mockFileProxy.Verify(x => x.CopyFile(It.IsAny(), It.IsAny(), It.IsAny())); } [Test] public void GetLatestVersion_PaketHashFileExistsButIsCorrupt_DeleteHashFileAndThrowException() { + const string hashFile = @"C:\hash.txt"; + //arrange mockEffectiveStrategy.Setup(x => x.GetLatestVersion(true)).Throws().Verifiable(); mockFileProxy.Setup(x => x.OpenRead(It.IsAny())).Returns(new MemoryStream(Guid.NewGuid().ToByteArray())); - mockFileProxy.Setup(x => x.ReadAllLines(It.IsAny())).Returns(new[] { Guid.NewGuid().ToString().Replace("-", String.Empty) + " not-paket.exe" }); + mockFileProxy.Setup(x => x.ReadAllLines(hashFile)).Returns(new[] { Guid.NewGuid().ToString().Replace("-", String.Empty) + " not-paket.exe" }); mockFileProxy.Setup(x => x.FileExists(It.IsAny())).Returns(true); //act - Assert.Throws(() => sut.DownloadVersion("any", "any")); + Assert.Throws(() => sut.DownloadVersion("any", "any", hashFile)); //assert - mockEffectiveStrategy.Verify(x => x.DownloadVersion(It.IsAny(), It.IsAny()), Times.Never); - mockFileProxy.Verify(x => x.DeleteFile(ItHasFilename("paket-sha256.txt"))); + mockEffectiveStrategy.Verify(x => x.DownloadVersion(It.IsAny(), It.IsAny(), null), Times.Never); + mockFileProxy.Verify(x => x.DeleteFile(hashFile)); } public string ItHasFilename(string filename) { return It.IsRegex($@"\w*[\\/]{filename}"); } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void CanDownloadHashFile(bool can) + { + mockEffectiveStrategy.SetupGet(x => x.CanDownloadHashFile).Returns(can); + Assert.That(sut.CanDownloadHashFile, Is.EqualTo(can)); + } + + [Test] + public void DownloadHashFile_NotSupported() + { + mockEffectiveStrategy.SetupGet(x => x.CanDownloadHashFile).Returns(false); + + var hashFilePath = sut.DownloadHashFile("42.0"); + + Assert.That(hashFilePath, Is.Null); + } + + [Test] + public void DownloadHashFile_PresentInCache() + { + mockEffectiveStrategy.SetupGet(x => x.CanDownloadHashFile).Returns(true); + var pathInCache = sut.GetHashFilePathInCache("42.0"); + mockFileProxy.Setup(x => x.FileExists(pathInCache)).Returns(true); + + var hashFilePath = sut.DownloadHashFile("42.0"); + + Assert.That(hashFilePath, Is.EqualTo(pathInCache)); + mockEffectiveStrategy.Verify(x => x.DownloadHashFile(It.IsAny()), Times.Never); + } + + [Test] + public void DownloadHashFile_NotInCache() + { + const string pathFromEffectiveStrategy = @"C:\hash.txt"; + mockEffectiveStrategy.Setup(x => x.DownloadHashFile(It.IsAny())).Returns(pathFromEffectiveStrategy); + mockEffectiveStrategy.SetupGet(x => x.CanDownloadHashFile).Returns(true); + var pathInCache = sut.GetHashFilePathInCache("42.0"); + mockFileProxy.Setup(x => x.FileExists(pathInCache)).Returns(false); + + var hashFilePath = sut.DownloadHashFile("42.0"); + + Assert.That(hashFilePath, Is.EqualTo(pathInCache)); + mockEffectiveStrategy.Verify(x => x.DownloadHashFile("42.0"), Times.Once); + mockFileProxy.Verify(x => x.CopyFile(pathFromEffectiveStrategy, pathInCache, true), Times.Once); + } } } diff --git a/tests/Paket.Bootstrapper.Tests/DownloadStrategies/GitHubDownloadStrategyTest.cs b/tests/Paket.Bootstrapper.Tests/DownloadStrategies/GitHubDownloadStrategyTest.cs index be5952ee17..f82245f743 100644 --- a/tests/Paket.Bootstrapper.Tests/DownloadStrategies/GitHubDownloadStrategyTest.cs +++ b/tests/Paket.Bootstrapper.Tests/DownloadStrategies/GitHubDownloadStrategyTest.cs @@ -1,6 +1,7 @@ -using System.IO; +using System; +using System.IO; using System.Reflection; -using System.Text; +using System.Security.Cryptography; using Moq; using NUnit.Framework; using Paket.Bootstrapper.DownloadStrategies; @@ -53,19 +54,77 @@ public void GetLatestVersion_Prerelease() } [Test] - public void DownloadVersion() + public void DownloadVersion_NoHash() { //arrange var tempFileName = BootstrapperHelper.GetTempFile("paket"); - mockWebProxy.Setup(x => x.DownloadFile(It.IsAny(), It.IsAny())).Verifiable(); //act - sut.DownloadVersion("2.57.1", "paketExeLocation"); + sut.DownloadVersion("2.57.1", "paketExeLocation", null); + + //assert + mockWebProxy.Verify(x => x.DownloadFile(It.IsAny(), tempFileName), Times.Once); + mockFileProxy.Verify(x => x.CopyFile(tempFileName, "paketExeLocation", true), Times.Once); + mockFileProxy.Verify(x => x.DeleteFile(tempFileName), Times.Once); + } + + [Test] + public void DownloadVersion_HashFileNotFound() + { + //arrange + var tempFileName = BootstrapperHelper.GetTempFile("paket"); + + //act + sut.DownloadVersion("2.57.1", "paketExeLocation", @"C:\does_not_exists.txt"); + + //assert + mockWebProxy.Verify(x => x.DownloadFile(It.IsAny(), tempFileName), Times.Once); + mockFileProxy.Verify(x => x.CopyFile(tempFileName, "paketExeLocation", true), Times.Once); + mockFileProxy.Verify(x => x.DeleteFile(tempFileName), Times.Once); + } + + [Test] + public void DownloadVersion_InvalidHashRetryOnce() + { + //arrange + var content = Guid.NewGuid().ToByteArray(); + var sha = new SHA256Managed(); + var checksum = sha.ComputeHash(new MemoryStream(content)); + var hash = BitConverter.ToString(checksum).Replace("-", String.Empty); + mockFileProxy.Setup(x => x.OpenRead(It.IsAny())).Returns(() => new MemoryStream(Guid.NewGuid().ToByteArray())); + mockFileProxy.Setup(x => x.FileExists(@"C:\invalid.txt")).Returns(true); + mockFileProxy.Setup(x => x.ReadAllLines(@"C:\invalid.txt")).Returns(new[] { hash + " paket.exe" }); + var tempFileName = BootstrapperHelper.GetTempFile("paket"); + + //act + sut.DownloadVersion("2.57.1", "paketExeLocation", @"C:\invalid.txt"); //assert - mockWebProxy.Verify(x => x.DownloadFile(It.IsAny(), tempFileName)); - mockFileProxy.Verify(x => x.CopyFile(tempFileName, "paketExeLocation", true)); - mockFileProxy.Verify(x => x.DeleteFile(tempFileName)); + mockWebProxy.Verify(x => x.DownloadFile(It.IsAny(), tempFileName), Times.Exactly(2)); + mockFileProxy.Verify(x => x.CopyFile(tempFileName, "paketExeLocation", true), Times.Once); + mockFileProxy.Verify(x => x.DeleteFile(tempFileName), Times.Once); + } + + [Test] + public void DownloadVersion_ValidHashOk() + { + //arrange + var content = Guid.NewGuid().ToByteArray(); + var sha = new SHA256Managed(); + var checksum = sha.ComputeHash(new MemoryStream(content)); + var hash = BitConverter.ToString(checksum).Replace("-", String.Empty); + mockFileProxy.Setup(x => x.OpenRead(It.IsAny())).Returns(new MemoryStream(content)); + mockFileProxy.Setup(x => x.FileExists(@"C:\valid.txt")).Returns(true); + mockFileProxy.Setup(x => x.ReadAllLines(@"C:\valid.txt")).Returns(new[] { hash + " paket.exe" }); + var tempFileName = BootstrapperHelper.GetTempFile("paket"); + + //act + sut.DownloadVersion("2.57.1", "paketExeLocation", @"C:\valid.txt"); + + //assert + mockWebProxy.Verify(x => x.DownloadFile(It.IsAny(), tempFileName), Times.Once); + mockFileProxy.Verify(x => x.CopyFile(tempFileName, "paketExeLocation", true), Times.Once); + mockFileProxy.Verify(x => x.DeleteFile(tempFileName), Times.Once); } [Test] @@ -86,5 +145,23 @@ public void SelfUpdate() mockFileProxy.Verify(x => x.MoveFile(Assembly.GetAssembly(typeof(GitHubDownloadStrategy)).Location, tempFileNameOld)); mockFileProxy.Verify(x => x.MoveFile(tempFileNameNew, Assembly.GetAssembly(typeof(GitHubDownloadStrategy)).Location)); } + + [Test] + public void DownloadHashFile() + { + var expectedPath = BootstrapperHelper.GetTempFile("paket-sha256.txt"); + var expectedUrl = string.Format(GitHubDownloadStrategy.Constants.PaketCheckSumDownloadUrlTemplate, "42.0"); + + var hashFilePath = sut.DownloadHashFile("42.0"); + + Assert.That(hashFilePath, Is.EqualTo(expectedPath)); + mockWebProxy.Verify(x => x.DownloadFile(expectedUrl, expectedPath), Times.Once); + } + + [Test] + public void CanDownloadHashFile() + { + Assert.That(sut.CanDownloadHashFile, Is.True); + } } } \ No newline at end of file diff --git a/tests/Paket.Bootstrapper.Tests/DownloadStrategies/NugetDownloadStrategyTests.cs b/tests/Paket.Bootstrapper.Tests/DownloadStrategies/NugetDownloadStrategyTests.cs index 5248142611..a11a30c2d1 100644 --- a/tests/Paket.Bootstrapper.Tests/DownloadStrategies/NugetDownloadStrategyTests.cs +++ b/tests/Paket.Bootstrapper.Tests/DownloadStrategies/NugetDownloadStrategyTests.cs @@ -97,7 +97,7 @@ public void DefaultApi_DownloadVersion() CreateSystemUnderTestWithDefaultApi(); //act - sut.DownloadVersion(null, "paket"); + sut.DownloadVersion(null, "paket", null); //assert mockWebRequestProxy.Verify(x => x.DownloadFile("https://www.nuget.org/api/v2/package/Paket", It.IsAny())); @@ -113,7 +113,7 @@ public void DefaultApi_DownloadSpecificVersion() CreateSystemUnderTestWithDefaultApi(); //act - sut.DownloadVersion("2.57.0", "paket"); + sut.DownloadVersion("2.57.0", "paket", null); //assert mockWebRequestProxy.Verify(x => x.DownloadFile("https://www.nuget.org/api/v2/package/Paket/2.57.0", It.IsAny())); @@ -241,7 +241,7 @@ public void NugetFolder_DownloadVersion_NoVersionSpecified_GetsLatestVersion() .Returns(new[] { "paket.111.nupkg" }); //act - sut.DownloadVersion(null, "paket"); + sut.DownloadVersion(null, "paket", null); //assert mockFileProxy.Verify(x => x.CopyFile(It.Is(s => s.StartsWith("anyNugetFolder") && s.EndsWith("paket.111.nupkg")), It.Is(s => s.StartsWith("folder") && s.EndsWith("paket.latest.nupkg")), false)); @@ -258,7 +258,7 @@ public void NugetFolder_DownloadSpecificVersion() CreateSystemUnderTestWithNugetFolder(); //act - sut.DownloadVersion("2.57.0", "paket"); + sut.DownloadVersion("2.57.0", "paket", null); //assert mockFileProxy.Verify(x => x.CopyFile(It.Is(s => s.StartsWith("anyNugetFolder") && s.EndsWith("paket.2.57.0.nupkg")), It.Is(s => s.StartsWith("folder") && s.EndsWith("paket.2.57.0.nupkg")), false)); @@ -312,5 +312,22 @@ public void NugetFolder_SelfUpdate_FailsWhenNullIsSpecified() mockFileProxy.Verify(x => x.CopyFile(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } + [Test] + public void DownloadHashFile() + { + CreateSystemUnderTestWithDefaultApi(); + + var hashFilePath = sut.DownloadHashFile("42.0"); + + Assert.That(hashFilePath, Is.Null); + } + + [Test] + public void CanDownloadHashFile() + { + CreateSystemUnderTestWithDefaultApi(); + + Assert.That(sut.CanDownloadHashFile, Is.False); + } } } \ No newline at end of file diff --git a/tests/Paket.Bootstrapper.Tests/DownloadStrategies/TemporarilyIgnoreUpdatesDownloadStrategyTest.cs b/tests/Paket.Bootstrapper.Tests/DownloadStrategies/TemporarilyIgnoreUpdatesDownloadStrategyTest.cs index 41ed3f7f66..de7c3be8e5 100644 --- a/tests/Paket.Bootstrapper.Tests/DownloadStrategies/TemporarilyIgnoreUpdatesDownloadStrategyTest.cs +++ b/tests/Paket.Bootstrapper.Tests/DownloadStrategies/TemporarilyIgnoreUpdatesDownloadStrategyTest.cs @@ -162,15 +162,35 @@ public void DownloadVersion_Touches_Target() { //arrange _mockFileProxy.Setup(fp => fp.Touch(Target)).Verifiable(); - _mockEffectiveStrategy.Setup(x => x.DownloadVersion("any", Target)).Verifiable(); + _mockEffectiveStrategy.Setup(x => x.DownloadVersion("any", Target, null)).Verifiable(); //act - _sut.DownloadVersion("any", Target); + _sut.DownloadVersion("any", Target, null); //assert _mockEffectiveStrategy.Verify(); _mockFileProxy.Verify(); } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void CanDownloadHashFile(bool can) + { + _mockEffectiveStrategy.SetupGet(x => x.CanDownloadHashFile).Returns(can); + Assert.That(_sut.CanDownloadHashFile, Is.EqualTo(can)); + } + + [Test] + public void DownloadHashFile() + { + _mockEffectiveStrategy.Setup(x => x.DownloadHashFile("42.0")).Returns(@"C:\42.txt"); + + var hashFilePath = _sut.DownloadHashFile("42.0"); + + Assert.That(hashFilePath, Is.EqualTo(@"C:\42.txt")); + _mockEffectiveStrategy.Verify(x => x.DownloadHashFile("42.0"), Times.Once); + } } } diff --git a/tests/Paket.Bootstrapper.Tests/StartPaketBootstrappingTests.cs b/tests/Paket.Bootstrapper.Tests/StartPaketBootstrappingTests.cs index 760780cdde..435a9d5624 100644 --- a/tests/Paket.Bootstrapper.Tests/StartPaketBootstrappingTests.cs +++ b/tests/Paket.Bootstrapper.Tests/StartPaketBootstrappingTests.cs @@ -36,7 +36,7 @@ public void DownloadNewVersion_LocalVersionIsOutdated() //assert mockFileProxy.Verify(); - mockDownloadStrategy.Verify(x => x.DownloadVersion("1.1", dlArgs.Target)); + mockDownloadStrategy.Verify(x => x.DownloadVersion("1.1", dlArgs.Target, null)); } [Test] @@ -51,7 +51,7 @@ public void DownloadSepcificVersion_IsUpgrade() //assert mockFileProxy.Verify(); - mockDownloadStrategy.Verify(x => x.DownloadVersion("1.3", dlArgs.Target)); + mockDownloadStrategy.Verify(x => x.DownloadVersion("1.3", dlArgs.Target, null)); } [Test] @@ -66,7 +66,7 @@ public void DownloadSepcificVersion_IsDowngrade() //assert mockFileProxy.Verify(); - mockDownloadStrategy.Verify(x => x.DownloadVersion("1.3", dlArgs.Target)); + mockDownloadStrategy.Verify(x => x.DownloadVersion("1.3", dlArgs.Target, null)); } [Test] @@ -81,7 +81,7 @@ public void DownloadNoNewVersion_LocalVersionIsCurrent() //assert mockFileProxy.Verify(); - mockDownloadStrategy.Verify(x => x.DownloadVersion(It.IsAny(), It.IsAny()), Times.Never); + mockDownloadStrategy.Verify(x => x.DownloadVersion(It.IsAny(), It.IsAny(), null), Times.Never); } [Test] @@ -96,7 +96,7 @@ public void DownloadCurrentVersion_LocalVersionIsPrerelease() //assert mockFileProxy.Verify(); - mockDownloadStrategy.Verify(x => x.DownloadVersion(It.IsAny(), It.IsAny()), Times.Once); + mockDownloadStrategy.Verify(x => x.DownloadVersion(It.IsAny(), It.IsAny(), null), Times.Once); } [Test] @@ -112,7 +112,7 @@ public void DownloadPrerelease_LocalVersionIsCurrent() //assert mockFileProxy.Verify(); - mockDownloadStrategy.Verify(x => x.DownloadVersion("1.1-alpha", dlArgs.Target)); + mockDownloadStrategy.Verify(x => x.DownloadVersion("1.1-alpha", dlArgs.Target, null)); } [Test] @@ -194,7 +194,7 @@ public void FirstStrategyFails_UseFallbackStrategy_FallbackStrategyWillBeUsed() //assert mockFileProxy.Verify(); - mockFallbackStrategy.Verify(x => x.DownloadVersion("1.2", dlArgs.Target)); + mockFallbackStrategy.Verify(x => x.DownloadVersion("1.2", dlArgs.Target, null)); } [Test] @@ -222,7 +222,7 @@ public void OnSuccess_NeverCalledWhenFail() Action onSuccess = () => successCount++; dlArgs.LatestVersion = "1.5"; mockFileProxy.Setup(x => x.FileExists(It.IsAny())).Returns(false); - mockDownloadStrategy.Setup(x => x.DownloadVersion("1.5", "paket.exe")).Throws(new WebException()); + mockDownloadStrategy.Setup(x => x.DownloadVersion("1.5", "paket.exe", null)).Throws(new WebException()); //act Program.StartPaketBootstrapping(mockDownloadStrategy.Object, dlArgs, mockFileProxy.Object, onSuccess);