using System.Text; using NuGet.Versioning; using Velopack.Json; using Velopack.Locators; using Velopack.Sources; using Velopack.Tests.TestHelpers; namespace Velopack.Tests; public class UpdateManagerTests { private readonly ITestOutputHelper _output; public UpdateManagerTests(ITestOutputHelper output) { _output = output; } private FakeDownloader GetMockDownloaderNoDelta() { var feed = new VelopackAssetFeed() { Assets = new VelopackAsset[] { new VelopackAsset() { PackageId = "MyCoolApp", Version = new SemanticVersion(1, 1, 0), Type = VelopackAssetType.Full, FileName = $"MyCoolApp-1.1.0.nupkg", SHA1 = "3a2eadd15dd984e4559f2b4d790ec8badaeb6a39", Size = 1040561, }, new VelopackAsset() { PackageId = "MyCoolApp", Version = new SemanticVersion(1, 0, 0), Type = VelopackAssetType.Full, FileName = $"MyCoolApp-1.0.0.nupkg", SHA1 = "94689fede03fed7ab59c24337673a27837f0c3ec", Size = 1004502, }, } }; var json = SimpleJson.SerializeObject(feed); return new FakeDownloader() { MockedResponseBytes = Encoding.UTF8.GetBytes(json) }; } private FakeDownloader GetMockDownloaderWith2Delta() { var feed = new VelopackAssetFeed { Assets = new VelopackAsset[] { new VelopackAsset() { PackageId = "MyCoolApp", Version = new SemanticVersion(1, 1, 0), Type = VelopackAssetType.Full, FileName = $"MyCoolApp-1.1.0.nupkg", SHA1 = "3a2eadd15dd984e4559f2b4d790ec8badaeb6a39", Size = 1040561, }, new VelopackAsset() { PackageId = "MyCoolApp", Version = new SemanticVersion(1, 0, 0), Type = VelopackAssetType.Full, FileName = $"MyCoolApp-1.0.0.nupkg", SHA1 = "94689fede03fed7ab59c24337673a27837f0c3ec", Size = 1004502, }, new VelopackAsset() { PackageId = "MyCoolApp", Version = new SemanticVersion(1, 1, 0), Type = VelopackAssetType.Delta, FileName = $"MyCoolApp-1.1.0-delta.nupkg", SHA1 = "14db31d2647c6d2284882a2e101924a9c409ee67", Size = 80396, }, new VelopackAsset() { PackageId = "MyCoolApp", Version = new SemanticVersion(1, 0, 0), Type = VelopackAssetType.Delta, FileName = $"MyCoolApp-1.0.0-delta.nupkg", SHA1 = "14db31d2647c6d2284882a2e101924a9c409ee67", Size = 80396, }, new VelopackAsset() { PackageId = "MyCoolApp", Version = new SemanticVersion(1, 2, 0), Type = VelopackAssetType.Delta, FileName = $"MyCoolApp-1.2.0-delta.nupkg", SHA1 = "14db31d2647c6d2284882a2e101924a9c409ee67", Size = 80396, }, new VelopackAsset() { PackageId = "MyCoolApp", Version = new SemanticVersion(1, 2, 0), Type = VelopackAssetType.Full, FileName = $"MyCoolApp-1.2.0.nupkg", SHA1 = "3a2eadd15dd984e4559f2b4d790ec8badaeb6a39", Size = 1040561, }, } }; var json = SimpleJson.SerializeObject(feed); return new FakeDownloader() { MockedResponseBytes = Encoding.UTF8.GetBytes(json) }; } [Fact] public void CanDownloadFilesAsUrl() { var fixture = PathHelper.GetFixture("AvaloniaCrossPlat-1.0.11-win-full.nupkg"); using var logger = _output.BuildLoggerFor(); using var _1 = Utility.GetTempDirectory(out var tempPath); var dl = new FakeDownloader() { MockedResponseBytes = Encoding.UTF8.GetBytes(SimpleJson.SerializeObject( new VelopackAssetFeed { Assets = new VelopackAsset[] { new VelopackAsset() { PackageId = "AvaloniaCrossPlat", Version = new SemanticVersion(1, 0, 11), Type = VelopackAssetType.Full, FileName = $"https://mysite.com/releases/AvaloniaCrossPlat$-1.1.0.nupkg", SHA1 = Utility.CalculateFileSHA1(fixture), Size = new FileInfo(fixture).Length, } } })) }; var source = new SimpleWebSource("http://any.com", dl); var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger); var um = new UpdateManager(source, null, logger, locator); var info = um.CheckForUpdates(); Assert.NotNull(info); Assert.True(new SemanticVersion(1, 0, 11) == info.TargetFullRelease.Version); Assert.Equal(0, info.DeltasToTarget.Count()); Assert.False(info.IsDowngrade); Assert.StartsWith($"http://any.com/releases.{VelopackRuntimeInfo.SystemOs.GetOsShortName()}.json?", dl.LastUrl); dl.MockedResponseBytes = File.ReadAllBytes(fixture); dl.WriteMockLocalFile = true; um.DownloadUpdates(info); Assert.True(File.Exists(Path.Combine(tempPath, "AvaloniaCrossPlat$-1.1.0.nupkg"))); Assert.Equal(Path.Combine(tempPath, "AvaloniaCrossPlat$-1.1.0.nupkg.partial"), dl.LastLocalFile); Assert.Equal("https://mysite.com/releases/AvaloniaCrossPlat$-1.1.0.nupkg", dl.LastUrl); } [Fact] public void CheckFromLocal() { using var logger = _output.BuildLoggerFor(); using var _1 = Utility.GetTempDirectory(out var tempPath); var dl = GetMockDownloaderNoDelta(); var source = new SimpleWebSource("http://any.com", dl); var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger); var um = new UpdateManager(source, null, logger, locator); var info = um.CheckForUpdates(); Assert.NotNull(info); Assert.True(new SemanticVersion(1, 1, 0) == info.TargetFullRelease.Version); Assert.Equal(0, info.DeltasToTarget.Count()); Assert.False(info.IsDowngrade); Assert.StartsWith($"http://any.com/releases.{VelopackRuntimeInfo.SystemOs.GetOsShortName()}.json?", dl.LastUrl); } [Fact] public void CheckFromLocalWithChannel() { using var logger = _output.BuildLoggerFor(); using var _1 = Utility.GetTempDirectory(out var tempPath); var dl = GetMockDownloaderNoDelta(); var source = new SimpleWebSource("http://any.com", dl); var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger); var opt = new UpdateOptions { ExplicitChannel = "experimental" }; var um = new UpdateManager(source, opt, logger, locator); var info = um.CheckForUpdates(); Assert.NotNull(info); Assert.True(new SemanticVersion(1, 1, 0) == info.TargetFullRelease.Version); Assert.Equal(0, info.DeltasToTarget.Count()); Assert.False(info.IsDowngrade); Assert.StartsWith("http://any.com/releases.experimental.json?", dl.LastUrl); } [Fact] public void CheckForSameAsInstalledVersion() { using var logger = _output.BuildLoggerFor(); using var _1 = Utility.GetTempDirectory(out var tempPath); var dl = GetMockDownloaderWith2Delta(); var myVer = new VelopackAsset() { PackageId = "MyCoolApp", Version = new SemanticVersion(1, 2, 0), Type = VelopackAssetType.Full, FileName = $"MyCoolApp-1.2.0.nupkg", SHA1 = "3a2eadd15dd984e4559f2b4d790ec8badaeb6a39", Size = 1040561, }; var source = new SimpleWebSource("http://any.com", dl); var locator = new TestVelopackLocator("MyCoolApp", "1.2.0", tempPath, null, null, null, logger: logger, localPackage: myVer, channel: "stable"); // checking for same version should return null var um = new UpdateManager(source, null, logger, locator); var info = um.CheckForUpdates(); Assert.Null(info); Assert.StartsWith("http://any.com/releases.stable.json?", dl.LastUrl); // checking for same version WITHOUT explicit channel should return null var opt = new UpdateOptions { AllowVersionDowngrade = true }; um = new UpdateManager(source, opt, logger, locator); Assert.Null(info); Assert.StartsWith("http://any.com/releases.stable.json?", dl.LastUrl); // checking for same version with explicit channel & downgrade allowed should return version opt = new UpdateOptions { ExplicitChannel = "experimental", AllowVersionDowngrade = true }; um = new UpdateManager(source, opt, logger, locator); info = um.CheckForUpdates(); Assert.True(info.IsDowngrade); Assert.NotNull(info); Assert.True(new SemanticVersion(1, 2, 0) == info.TargetFullRelease.Version); Assert.StartsWith("http://any.com/releases.experimental.json?", dl.LastUrl); Assert.Equal(0, info.DeltasToTarget.Count()); } [Fact] public void CheckForLowerThanInstalledVersion() { using var logger = _output.BuildLoggerFor(); using var _1 = Utility.GetTempDirectory(out var tempPath); var dl = GetMockDownloaderWith2Delta(); var myVer = new VelopackAsset() { PackageId = "MyCoolApp", Version = new SemanticVersion(2, 0, 0), Type = VelopackAssetType.Full, FileName = $"MyCoolApp-2.0.0.nupkg", SHA1 = "3a2eadd15dd984e4559f2b4d790ec8badaeb6a39", Size = 1040561, }; var source = new SimpleWebSource("http://any.com", dl); var locator = new TestVelopackLocator("MyCoolApp", "2.0.0", tempPath, null, null, null, logger: logger, localPackage: myVer, channel: "stable"); // checking for lower version should return null var um = new UpdateManager(source, null, logger, locator); var info = um.CheckForUpdates(); Assert.Null(info); Assert.StartsWith("http://any.com/releases.stable.json?", dl.LastUrl); // checking for lower version with downgrade allowed should return lower version var opt = new UpdateOptions { AllowVersionDowngrade = true }; um = new UpdateManager(source, opt, logger, locator); info = um.CheckForUpdates(); Assert.True(info.IsDowngrade); Assert.NotNull(info); Assert.True(new SemanticVersion(1, 2, 0) == info.TargetFullRelease.Version); Assert.StartsWith("http://any.com/releases.stable.json?", dl.LastUrl); Assert.Equal(0, info.DeltasToTarget.Count()); } [Fact] public void CheckFromLocalWithDelta() { using var logger = _output.BuildLoggerFor(); using var _1 = Utility.GetTempDirectory(out var tempPath); var dl = GetMockDownloaderWith2Delta(); var myVer = new VelopackAsset() { PackageId = "MyCoolApp", Version = new SemanticVersion(1, 0, 0), Type = VelopackAssetType.Full, FileName = $"MyCoolApp-1.0.0.nupkg", SHA1 = "94689fede03fed7ab59c24337673a27837f0c3ec", Size = 1004502, }; var source = new SimpleWebSource("http://any.com", dl); var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, null, null, null, logger: logger, localPackage: myVer); var um = new UpdateManager(source, null, logger, locator); var info = um.CheckForUpdates(); Assert.False(info.IsDowngrade); Assert.NotNull(info); Assert.True(new SemanticVersion(1, 2, 0) == info.TargetFullRelease.Version); Assert.Equal(2, info.DeltasToTarget.Count()); } [Fact] public void NoDeltaIfNoBasePackage() { using var logger = _output.BuildLoggerFor(); using var _1 = Utility.GetTempDirectory(out var tempPath); var dl = GetMockDownloaderWith2Delta(); var source = new SimpleWebSource("http://any.com", dl); var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger: logger); var um = new UpdateManager(source, null, logger, locator); var info = um.CheckForUpdates(); Assert.NotNull(info); Assert.False(info.IsDowngrade); Assert.True(new SemanticVersion(1, 2, 0) == info.TargetFullRelease.Version); Assert.Equal(0, info.DeltasToTarget.Count()); } [Fact] public void CheckFromLocalWithDeltaNoLocalPackage() { using var logger = _output.BuildLoggerFor(); using var _1 = Utility.GetTempDirectory(out var tempPath); var dl = GetMockDownloaderWith2Delta(); var source = new SimpleWebSource("http://any.com", dl); var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger: logger); var um = new UpdateManager(source, null, logger, locator); var info = um.CheckForUpdates(); Assert.NotNull(info); Assert.False(info.IsDowngrade); Assert.True(new SemanticVersion(1, 2, 0) == info.TargetFullRelease.Version); Assert.Equal(0, info.DeltasToTarget.Count()); } [Fact(Skip = "Consumes API Quota")] public void CheckGithub() { // https://github.com/caesay/SquirrelCustomLauncherTestApp using var logger = _output.BuildLoggerFor(); using var _1 = Utility.GetTempDirectory(out var tempPath); var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger); var source = new GithubSource("https://github.com/caesay/SquirrelCustomLauncherTestApp", null, false); var um = new UpdateManager(source, null, logger, locator); var info = um.CheckForUpdates(); Assert.NotNull(info); Assert.True(new SemanticVersion(1, 0, 1) == info.TargetFullRelease.Version); Assert.Equal(0, info.DeltasToTarget.Count()); } [Fact(Skip = "Consumes API Quota")] public void CheckGithubWithNonExistingChannel() { // https://github.com/caesay/SquirrelCustomLauncherTestApp using var logger = _output.BuildLoggerFor(); using var _1 = Utility.GetTempDirectory(out var tempPath); var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger); var source = new GithubSource("https://github.com/caesay/SquirrelCustomLauncherTestApp", null, false); var opt = new UpdateOptions { ExplicitChannel = "hello" }; var um = new UpdateManager(source, opt, logger, locator); Assert.Throws(() => um.CheckForUpdates()); } [Fact] public void CheckFromEmptyFileSource() { using var logger = _output.BuildLoggerFor(); using var _1 = Utility.GetTempDirectory(out var tempPath); var source = new SimpleFileSource(new DirectoryInfo(tempPath)); var locator = new TestVelopackLocator("MyCoolApp", "1.0.0", tempPath, logger); var um = new UpdateManager(source, null, logger, locator); var info = um.CheckForUpdates(); Assert.Null(info); } [Fact] public void NoUpdatesIfCurrentEqualsRemoteVersion() { using var logger = _output.BuildLoggerFor(); using var _1 = Utility.GetTempDirectory(out var tempPath); var dl = GetMockDownloaderNoDelta(); var source = new SimpleWebSource("http://any.com", dl); var locator = new TestVelopackLocator("MyCoolApp", "1.1.0", tempPath, logger); var um = new UpdateManager(source, null, logger, locator); var info = um.CheckForUpdates(); Assert.Null(info); } [Fact] public void NoUpdatesIfCurrentGreaterThanRemoteVersion() { using var logger = _output.BuildLoggerFor(); using var _1 = Utility.GetTempDirectory(out var tempPath); var dl = GetMockDownloaderNoDelta(); var source = new SimpleWebSource("http://any.com", dl); var locator = new TestVelopackLocator("MyCoolApp", "1.2.0", tempPath, logger); var um = new UpdateManager(source, null, logger, locator); var info = um.CheckForUpdates(); Assert.Null(info); } [Theory] [InlineData("Clowd", "3.4.287")] [InlineData("slack", "1.1.8")] public void DownloadsLatestFullVersion(string id, string version) { using var logger = _output.BuildLoggerFor(); using var _1 = Utility.GetTempDirectory(out var packagesDir); var repo = new FakeFixtureRepository(id, false); var source = new SimpleWebSource("http://any.com", repo); var locator = new TestVelopackLocator(id, "1.0.0", packagesDir, logger); var um = new UpdateManager(source, null, logger, locator); var info = um.CheckForUpdates(); Assert.NotNull(info); Assert.True(SemanticVersion.Parse(version) == info.TargetFullRelease.Version); Assert.Equal(0, info.DeltasToTarget.Count()); um.DownloadUpdates(info); var target = Path.Combine(packagesDir, $"{id}-{version}-full.nupkg"); Assert.True(File.Exists(target)); um.VerifyPackageChecksum(info.TargetFullRelease); } [SkippableTheory] [InlineData("Clowd", "3.4.287", "3.4.292")] //[InlineData("slack", "1.1.8", "1.2.2")] public async Task DownloadsDeltasAndCreatesFullVersion(string id, string fromVersion, string toVersion) { Skip.If(VelopackRuntimeInfo.IsLinux); using var logger = _output.BuildLoggerFor(); using var _1 = Utility.GetTempDirectory(out var packagesDir); var repo = new FakeFixtureRepository(id, true); var source = new SimpleWebSource("http://any.com", repo); var feed = await source.GetReleaseFeed(logger, VelopackRuntimeInfo.SystemOs.GetOsShortName()); var basePkg = feed.Assets .Where(x => x.Type == VelopackAssetType.Full) .Single(x => x.Version == SemanticVersion.Parse(fromVersion)); var basePkgFixturePath = PathHelper.GetFixture(basePkg.FileName); var basePkgPath = Path.Combine(packagesDir, basePkg.FileName); File.Copy(basePkgFixturePath, basePkgPath); var updateExe = PathHelper.CopyUpdateTo(packagesDir); var locator = new TestVelopackLocator(id, fromVersion, packagesDir, null, null, updateExe, null, logger); var um = new UpdateManager(source, null, logger, locator); var info = await um.CheckForUpdatesAsync(); Assert.NotNull(info); Assert.True(SemanticVersion.Parse(toVersion) == info.TargetFullRelease.Version); Assert.Equal(3, info.DeltasToTarget.Count()); Assert.NotNull(info.BaseRelease); await um.DownloadUpdatesAsync(info); var target = Path.Combine(packagesDir, $"{id}-{toVersion}-full.nupkg"); Assert.True(File.Exists(target)); } }