Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 206 additions & 3 deletions ARESLauncher.Tests/AresUpdaterTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.IO;
using System.Threading.Tasks;
using ARESLauncher.Configuration;
using ARESLauncher.Models;
using ARESLauncher.Services;
Expand Down Expand Up @@ -80,6 +78,134 @@ public async Task Update_DownloadsAndUnpacksSinglePackage()
}
}

[Test]
public async Task GetAvailableVersions_IncludesDebugFolderPackages()
{
var debugPath = Path.Combine(AppContext.BaseDirectory, "Debug");
Directory.CreateDirectory(debugPath);
var version = new SemanticVersion(8, 8, 8);
var archiveName = $"ARES-v{version.ToNormalizedString()}.zip";
var archivePath = TestArchives.CreateArchive(debugPath, archiveName, ("empty", ""));

try
{
var downloader = new RecordingAresDownloader(null);
downloader.AvailableReleases = [new AresRelease { Version = new SemanticVersion(1, 0, 0), IsBeta = false }];

var updater = new AresUpdater(
downloader,
new FakeAppConfigurationService(new LauncherConfiguration()),
new FakeAppSettingsUpdater(),
new FakeCertificateManager(),
new FakeDatabaseManager(),
new FakeAresBinaryManager(),
NullLogger<AresUpdater>.Instance);

var releases = await updater.GetAvailableVersions();

Assert.That(releases.Select(r => r.Version), Does.Contain(version));
Assert.That(releases.Select(r => r.Version), Does.Contain(new SemanticVersion(1, 0, 0)));
}
finally
{
if(File.Exists(archivePath)) File.Delete(archivePath);
}
}

[Test]
public async Task GetAvailableVersions_FiltersBetaReleases_WhenNotOptedIn()
{
var downloader = new RecordingAresDownloader(null);
downloader.AvailableReleases = [
new AresRelease { Version = new SemanticVersion(1, 0, 0), IsBeta = false },
new AresRelease { Version = new SemanticVersion(1, 1, 0), IsBeta = true }
];

var updater = new AresUpdater(
downloader,
new FakeAppConfigurationService(new LauncherConfiguration { IncludeBeta = false }),
new FakeAppSettingsUpdater(),
new FakeCertificateManager(),
new FakeDatabaseManager(),
new FakeAresBinaryManager(),
NullLogger<AresUpdater>.Instance);

var releases = await updater.GetAvailableVersions();

Assert.That(releases.Select(r => r.Version), Does.Contain(new SemanticVersion(1, 0, 0)));
Assert.That(releases.Select(r => r.Version), Does.Not.Contain(new SemanticVersion(1, 1, 0)));
}

[Test]
public async Task GetAvailableVersions_IncludesBetaReleases_WhenOptedIn()
{
var downloader = new RecordingAresDownloader(null);
downloader.AvailableReleases = [
new AresRelease { Version = new SemanticVersion(1, 0, 0), IsBeta = false },
new AresRelease { Version = new SemanticVersion(1, 1, 0), IsBeta = true }
];

var updater = new AresUpdater(
downloader,
new FakeAppConfigurationService(new LauncherConfiguration { IncludeBeta = true }),
new FakeAppSettingsUpdater(),
new FakeCertificateManager(),
new FakeDatabaseManager(),
new FakeAresBinaryManager(),
NullLogger<AresUpdater>.Instance);

var releases = await updater.GetAvailableVersions();

Assert.That(releases.Select(r => r.Version), Does.Contain(new SemanticVersion(1, 0, 0)));
Assert.That(releases.Select(r => r.Version), Does.Contain(new SemanticVersion(1, 1, 0)));
}

[Test]
public async Task Update_UsesDebugFolderPackage_WhenAvailable()
{
var debugPath = Path.Combine(AppContext.BaseDirectory, "Debug");
Directory.CreateDirectory(debugPath);
var tempRoot = TestPaths.CreateTempDirectory();
var uiDir = Path.Combine(tempRoot, "ui");
Directory.CreateDirectory(uiDir);

var version = new SemanticVersion(9, 9, 9);
var archiveName = $"ARES-v{version.ToNormalizedString()}.zip";
var archivePath = TestArchives.CreateArchive(debugPath, archiveName, ("debug.bin", "content"));

try
{
var downloader = new RecordingAresDownloader(null); // Should not be called
var configuration = new FakeAppConfigurationService(new LauncherConfiguration
{
UiBinaryPath = uiDir,
ServiceBinaryPath = uiDir
});

var updater = new AresUpdater(
downloader,
configuration,
new FakeAppSettingsUpdater(),
new FakeCertificateManager(),
new FakeDatabaseManager(),
new FakeAresBinaryManager(),
NullLogger<AresUpdater>.Instance);

await updater.Update(version);

Assert.That(downloader.DownloadCallCount, Is.EqualTo(0));
Assert.That(File.Exists(Path.Combine(uiDir, "debug.bin")), Is.True);

var metadata = BinaryMetadataHelper.ReadMetadata(uiDir);
Assert.That(metadata!.Version, Is.EqualTo(version.ToNormalizedString()));
}
finally
{
if(File.Exists(archivePath)) File.Delete(archivePath);
TestPaths.DeleteDirectoryIfExists(tempRoot);
}
}

[Test]
public async Task Update_PersistsSplitLayout_WhenServiceExecutableExists()
{
Expand All @@ -88,7 +214,8 @@ public async Task Update_PersistsSplitLayout_WhenServiceExecutableExists()

try
{
var archivePath = TestArchives.CreateArchive(tempRoot, "combined.zip", ("UI", "ui"), ("AresService", "svc"));
var serviceName = OperatingSystem.IsWindows() ? "AresService.exe" : "AresService";
var archivePath = TestArchives.CreateArchive(tempRoot, "combined.zip", ("UI", "ui"), (serviceName, "svc"));
var source = new AresSource("AFRL-ARES", "ARES");
var version = new SemanticVersion(2, 0, 0);
var downloader = new RecordingAresDownloader(archivePath);
Expand Down Expand Up @@ -120,4 +247,80 @@ public async Task Update_PersistsSplitLayout_WhenServiceExecutableExists()
TestPaths.DeleteDirectoryIfExists(tempRoot);
}
}

[Test]
public void InvalidateCache_DelegatesToDownloader()
{
var downloader = new RecordingAresDownloader(null);
var updater = new AresUpdater(
downloader,
new FakeAppConfigurationService(new LauncherConfiguration()),
new FakeAppSettingsUpdater(),
new FakeCertificateManager(),
new FakeDatabaseManager(),
new FakeAresBinaryManager(),
NullLogger<AresUpdater>.Instance);

updater.InvalidateCache();

Assert.That(downloader.InvalidateCacheCalled, Is.True);
}

[Test]
public async Task CreateSnapshot_CallsDatabaseManagerCreateSnapshot()
{
var dbManager = new FakeDatabaseManager();
var updater = new AresUpdater(
new RecordingAresDownloader(null),
new FakeAppConfigurationService(new LauncherConfiguration()),
new FakeAppSettingsUpdater(),
new FakeCertificateManager(),
dbManager,
new FakeAresBinaryManager(),
NullLogger<AresUpdater>.Instance);

var version = new SemanticVersion(1, 0, 0);
await updater.CreateSnapshot(version);

Assert.That(dbManager.CreateSnapshotCallCount, Is.EqualTo(1));
Assert.That(dbManager.LastSnapshotVersion, Is.EqualTo(version));
}

[Test]
public async Task RestoreSnapshot_CallsDatabaseManagerRestoreSnapshot()
{
var dbManager = new FakeDatabaseManager();
var updater = new AresUpdater(
new RecordingAresDownloader(null),
new FakeAppConfigurationService(new LauncherConfiguration()),
new FakeAppSettingsUpdater(),
new FakeCertificateManager(),
dbManager,
new FakeAresBinaryManager(),
NullLogger<AresUpdater>.Instance);

var version = new SemanticVersion(1, 0, 0);
await updater.RestoreSnapshot(version);

Assert.That(dbManager.RestoreSnapshotCallCount, Is.EqualTo(1));
Assert.That(dbManager.LastRestoreVersion, Is.EqualTo(version));
}

[Test]
public async Task ResetDatabase_CallsDatabaseManagerReset()
{
var dbManager = new FakeDatabaseManager();
var updater = new AresUpdater(
new RecordingAresDownloader(null),
new FakeAppConfigurationService(new LauncherConfiguration()),
new FakeAppSettingsUpdater(),
new FakeCertificateManager(),
dbManager,
new FakeAresBinaryManager(),
NullLogger<AresUpdater>.Instance);

await updater.ResetDatabase();

Assert.That(dbManager.DatabaseStatus, Is.EqualTo(DatabaseStatus.NonExistent));
}
}
53 changes: 44 additions & 9 deletions ARESLauncher.Tests/TestSupport/TestDoubles.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System;
using System.Threading.Tasks;
using ARESLauncher.Configuration;
using ARESLauncher.Models;
using ARESLauncher.Models.AppSettings;
Expand All @@ -9,22 +7,29 @@

namespace ARESLauncher.Tests;

internal sealed class RecordingAresDownloader(string archivePath) : IAresDownloader
internal sealed class RecordingAresDownloader(string? archivePath) : IAresDownloader
{
public int DownloadCallCount { get; private set; }
public bool InvalidateCacheCalled { get; private set; }
public AresSource? LastSource { get; private set; }
public SemanticVersion? LastVersion { get; private set; }
public string? LastDestination { get; private set; }
public string? LastAuthToken { get; private set; }
public AresRelease[] AvailableReleases { get; set; } = [];

public Task<SemanticVersion[]> GetAvailableVersions(AresSource source, string? authToken)
public Task<AresRelease[]> GetAvailableVersions(AresSource source, string? authToken)
{
throw new NotSupportedException();
return Task.FromResult(AvailableReleases);
}

public Task<SemanticVersion[]> GetAvailableVersions(LauncherSource soruce)
public Task<AresRelease[]> GetAvailableVersions(LauncherSource source, string? authToken)
{
throw new NotSupportedException();
return Task.FromResult(AvailableReleases);
}

public void InvalidateCache()
{
InvalidateCacheCalled = true;
}

public Task<string> Download(LauncherSource source, SemanticVersion version, string destination, string? authToken,
Expand All @@ -42,7 +47,7 @@ public Task<string> Download(AresSource source, SemanticVersion version, string
LastDestination = destination;
LastAuthToken = authToken;
progress?.Report(1);
return Task.FromResult(archivePath);
return Task.FromResult(archivePath ?? throw new InvalidOperationException("Archive path not set"));
}
}

Expand Down Expand Up @@ -86,16 +91,46 @@ public Task Update()

internal sealed class FakeDatabaseManager : IDatabaseManager
{
public DatabaseStatus DatabaseStatus => DatabaseStatus.UpToDate;
public DatabaseStatus DatabaseStatus { get; set; } = DatabaseStatus.UpToDate;
public int RefreshCallCount { get; private set; }
public int RunMigrationsCallCount { get; private set; }
public int CreateSnapshotCallCount { get; private set; }
public int RestoreSnapshotCallCount { get; private set; }
public SemanticVersion? LastSnapshotVersion { get; private set; }
public SemanticVersion? LastRestoreVersion { get; private set; }
public bool SnapshotExistsResult { get; set; }

public Task RunMigrations()
{
RunMigrationsCallCount++;
return Task.CompletedTask;
}

public Task CreateSnapshot(SemanticVersion version)
{
CreateSnapshotCallCount++;
LastSnapshotVersion = version;
return Task.CompletedTask;
}

public Task<bool> HasSnapshot(SemanticVersion version)
{
return Task.FromResult(SnapshotExistsResult);
}

public Task RestoreSnapshot(SemanticVersion version)
{
RestoreSnapshotCallCount++;
LastRestoreVersion = version;
return Task.CompletedTask;
}

public Task Reset()
{
DatabaseStatus = DatabaseStatus.NonExistent;
return Task.CompletedTask;
}

public Task Refresh()
{
RefreshCallCount++;
Expand Down
1 change: 1 addition & 0 deletions ARESLauncher/Configuration/LauncherConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ public class LauncherConfiguration

public string AresServiceProcessName { get; set; } = "AresService";
public string AresUiProcessName { get; set; } = "UI";
public bool IncludeBeta { get; set; } = false;
}
1 change: 0 additions & 1 deletion ARESLauncher/Models/AppSettings/AppSettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ namespace ARESLauncher.Models.AppSettings;
public class AppSettingsService : AppSettingsBase
{
public TokensConfig? TokensConfig { get; set; }
public string? AresDataPath { get; set; }
}
15 changes: 15 additions & 0 deletions ARESLauncher/Models/AresRelease.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using NuGet.Versioning;

namespace ARESLauncher.Models;

public class AresRelease
{
public required SemanticVersion Version { get; init; }
public required bool IsBeta { get; init; }
public bool IsInstalled { get; set; }

public override string ToString()
{
return IsBeta ? $"{Version} (Beta)" : Version.ToString();
}
}
18 changes: 18 additions & 0 deletions ARESLauncher/Models/UpdateConfirmationRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,26 @@

namespace ARESLauncher.Models;

public enum DowngradeOption
{
None,
RestoreSnapshot,
Reset,
Cancel
}

public class UpdateConfirmationRequest
{
public required SemanticVersion CurrentVersion { get; init; }
public required SemanticVersion TargetVersion { get; init; }
public bool HasSnapshot { get; set; }
}

public class UpdateConfirmationResponse
{
public bool ShouldProceed { get; init; }
public DowngradeOption DowngradeOption { get; init; } = DowngradeOption.None;

public static UpdateConfirmationResponse Cancel => new() { ShouldProceed = false, DowngradeOption = DowngradeOption.Cancel };
public static UpdateConfirmationResponse Proceed(DowngradeOption option = DowngradeOption.None) => new() { ShouldProceed = true, DowngradeOption = option };
}
Loading