293 lines
9.9 KiB
C#
293 lines
9.9 KiB
C#
using System.IO;
|
|
using System.Net.Http;
|
|
using CursorLang.Models;
|
|
using CursorLang.Services;
|
|
using CursorLang.Tests.Infrastructure;
|
|
using CursorLang.ViewModels;
|
|
|
|
namespace CursorLang.Tests.ViewModels;
|
|
|
|
/// <summary>
|
|
/// The updates section of the settings window: what it shows at every step and
|
|
/// what it asks of the service behind it.
|
|
/// </summary>
|
|
public sealed class UpdateViewModelTests
|
|
{
|
|
[Fact]
|
|
public void Before_the_first_check_the_section_says_nothing()
|
|
{
|
|
using UpdateViewModel viewModel = Create(new FakeUpdateService());
|
|
|
|
Assert.Equal(UpdateStatus.Idle, viewModel.Status);
|
|
Assert.False(viewModel.HasStatus);
|
|
Assert.False(viewModel.IsDownloadOffered);
|
|
Assert.False(viewModel.IsInstallOffered);
|
|
Assert.True(viewModel.CanCheck);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task An_update_found_is_offered_for_download()
|
|
{
|
|
var updates = new FakeUpdateService { Release = Release("2.0.0.0") };
|
|
using UpdateViewModel viewModel = Create(updates);
|
|
|
|
await viewModel.CheckCommand.ExecuteAsync(null);
|
|
|
|
Assert.Equal(UpdateStatus.Available, viewModel.Status);
|
|
Assert.True(viewModel.IsDownloadOffered);
|
|
Assert.False(viewModel.IsInstallOffered);
|
|
Assert.Equal("https://host/releases/tag", viewModel.ReleaseUrl?.ToString());
|
|
Assert.True(viewModel.IsReleaseLinkShown);
|
|
Assert.Equal("en:UpdateAvailable", viewModel.StatusText);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task With_the_latest_version_installed_there_is_nothing_to_offer()
|
|
{
|
|
using UpdateViewModel viewModel = Create(new FakeUpdateService());
|
|
|
|
await viewModel.CheckCommand.ExecuteAsync(null);
|
|
|
|
Assert.Equal(UpdateStatus.UpToDate, viewModel.Status);
|
|
Assert.False(viewModel.IsDownloadOffered);
|
|
Assert.False(viewModel.IsReleaseLinkShown);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_check_by_the_button_says_when_it_did_not_work_out()
|
|
{
|
|
var updates = new FakeUpdateService { Failure = new HttpRequestException("no network") };
|
|
using UpdateViewModel viewModel = Create(updates);
|
|
|
|
await viewModel.CheckCommand.ExecuteAsync(null);
|
|
|
|
Assert.Equal(UpdateStatus.Failed, viewModel.Status);
|
|
Assert.True(viewModel.HasStatus);
|
|
}
|
|
|
|
// The app does not always start with a live network, and the user who never
|
|
// asked about updates has no use for the complaint
|
|
[Fact]
|
|
public async Task A_check_at_startup_keeps_a_failure_to_itself()
|
|
{
|
|
var updates = new FakeUpdateService { Failure = new HttpRequestException("no network") };
|
|
using UpdateViewModel viewModel = Create(updates);
|
|
|
|
await viewModel.StartAsync();
|
|
|
|
Assert.Equal(UpdateStatus.Idle, viewModel.Status);
|
|
Assert.False(viewModel.HasStatus);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_successful_check_is_remembered_in_the_settings()
|
|
{
|
|
var settings = new AppSettings();
|
|
var updates = new FakeUpdateService { Release = Release("2.0.0.0") };
|
|
using UpdateViewModel viewModel = Create(updates, settings);
|
|
|
|
await viewModel.CheckCommand.ExecuteAsync(null);
|
|
|
|
Assert.NotNull(settings.LastUpdateCheck);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_check_that_did_not_work_out_is_not_remembered()
|
|
{
|
|
var settings = new AppSettings();
|
|
var updates = new FakeUpdateService { Failure = new HttpRequestException("no network") };
|
|
using UpdateViewModel viewModel = Create(updates, settings);
|
|
|
|
await viewModel.CheckCommand.ExecuteAsync(null);
|
|
|
|
Assert.Null(settings.LastUpdateCheck);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_recent_check_is_not_repeated_at_startup()
|
|
{
|
|
var settings = new AppSettings { LastUpdateCheck = DateTimeOffset.UtcNow };
|
|
var updates = new FakeUpdateService();
|
|
using UpdateViewModel viewModel = Create(updates, settings);
|
|
|
|
await viewModel.StartAsync();
|
|
|
|
Assert.Equal(0, updates.CheckCalls);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_check_of_yesterday_is_repeated_at_startup()
|
|
{
|
|
var settings = new AppSettings { LastUpdateCheck = DateTimeOffset.UtcNow - TimeSpan.FromDays(2) };
|
|
var updates = new FakeUpdateService();
|
|
using UpdateViewModel viewModel = Create(updates, settings);
|
|
|
|
await viewModel.StartAsync();
|
|
|
|
Assert.Equal(1, updates.CheckCalls);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_ban_on_checking_by_itself_is_obeyed()
|
|
{
|
|
var settings = new AppSettings { CheckForUpdates = false };
|
|
var updates = new FakeUpdateService();
|
|
using UpdateViewModel viewModel = Create(updates, settings);
|
|
|
|
await viewModel.StartAsync();
|
|
|
|
Assert.Equal(0, updates.CheckCalls);
|
|
|
|
// The button still works: the setting is about the app doing it on its own
|
|
await viewModel.CheckCommand.ExecuteAsync(null);
|
|
Assert.Equal(1, updates.CheckCalls);
|
|
}
|
|
|
|
[Fact]
|
|
public void The_ban_on_checking_travels_to_the_settings()
|
|
{
|
|
var settings = new AppSettings { CheckForUpdates = true };
|
|
using UpdateViewModel viewModel = Create(new FakeUpdateService(), settings);
|
|
|
|
viewModel.CheckAutomatically = false;
|
|
|
|
Assert.False(settings.CheckForUpdates);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_package_from_Store_is_left_to_Store()
|
|
{
|
|
var updates = new FakeUpdateService { IsSupported = false, Release = Release("2.0.0.0") };
|
|
using UpdateViewModel viewModel = Create(updates);
|
|
|
|
await viewModel.StartAsync();
|
|
await viewModel.CheckCommand.ExecuteAsync(null);
|
|
|
|
Assert.False(viewModel.IsSupported);
|
|
Assert.Equal(0, updates.CheckCalls);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_downloaded_package_is_offered_for_installation()
|
|
{
|
|
using TempFolder folder = new();
|
|
string package = folder.File("CursorLang-2.0.0.0.msixbundle");
|
|
await File.WriteAllTextAsync(package, "package", TestContext.Current.CancellationToken);
|
|
|
|
var updates = new FakeUpdateService { Release = Release("2.0.0.0"), PackagePath = package };
|
|
using UpdateViewModel viewModel = Create(updates);
|
|
|
|
await viewModel.CheckCommand.ExecuteAsync(null);
|
|
await viewModel.DownloadCommand.ExecuteAsync(null);
|
|
|
|
Assert.Equal(UpdateStatus.Ready, viewModel.Status);
|
|
Assert.True(viewModel.IsInstallOffered);
|
|
Assert.False(viewModel.IsDownloadOffered);
|
|
|
|
viewModel.InstallCommand.Execute(null);
|
|
|
|
Assert.Equal([package], updates.Installed);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task While_the_package_is_downloading_the_section_shows_it()
|
|
{
|
|
var updates = new FakeUpdateService
|
|
{
|
|
Release = Release("2.0.0.0"),
|
|
DownloadGate = new TaskCompletionSource(),
|
|
};
|
|
|
|
using UpdateViewModel viewModel = Create(updates);
|
|
await viewModel.CheckCommand.ExecuteAsync(null);
|
|
|
|
Task download = viewModel.DownloadCommand.ExecuteAsync(null);
|
|
|
|
Assert.Equal(UpdateStatus.Downloading, viewModel.Status);
|
|
Assert.True(viewModel.IsProgressShown);
|
|
Assert.True(viewModel.IsBusy);
|
|
Assert.False(viewModel.CanCheck);
|
|
|
|
updates.DownloadGate.SetResult();
|
|
await download;
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_download_that_did_not_work_out_is_told_about()
|
|
{
|
|
var updates = new FakeUpdateService { Release = Release("2.0.0.0") };
|
|
using UpdateViewModel viewModel = Create(updates);
|
|
await viewModel.CheckCommand.ExecuteAsync(null);
|
|
|
|
updates.Failure = new HttpRequestException("the connection dropped");
|
|
await viewModel.DownloadCommand.ExecuteAsync(null);
|
|
|
|
Assert.Equal(UpdateStatus.Failed, viewModel.Status);
|
|
}
|
|
|
|
// The temp folder is cleared by Windows as it sees fit, and the app has no
|
|
// business handing a file that is gone to the installer
|
|
[Fact]
|
|
public async Task A_package_gone_from_the_disk_is_offered_for_download_again()
|
|
{
|
|
var updates = new FakeUpdateService
|
|
{
|
|
Release = Release("2.0.0.0"),
|
|
PackagePath = Path.Combine(Path.GetTempPath(), "CursorLang.Tests", "never-written.msixbundle"),
|
|
};
|
|
|
|
using UpdateViewModel viewModel = Create(updates);
|
|
await viewModel.CheckCommand.ExecuteAsync(null);
|
|
await viewModel.DownloadCommand.ExecuteAsync(null);
|
|
|
|
viewModel.InstallCommand.Execute(null);
|
|
|
|
Assert.Empty(updates.Installed);
|
|
Assert.Equal(UpdateStatus.Available, viewModel.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task The_status_is_written_in_the_chosen_language()
|
|
{
|
|
var localization = new FakeLocalizationService();
|
|
using UpdateViewModel viewModel = Create(new FakeUpdateService(), localization: localization);
|
|
|
|
await viewModel.CheckCommand.ExecuteAsync(null);
|
|
Assert.Equal("en:UpdateUpToDate", viewModel.StatusText);
|
|
|
|
localization.CurrentLanguage = "ru";
|
|
Assert.Equal("ru:UpdateUpToDate", viewModel.StatusText);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Closing_unsubscribes_from_the_language()
|
|
{
|
|
var localization = new FakeLocalizationService();
|
|
UpdateViewModel viewModel = Create(new FakeUpdateService(), localization: localization);
|
|
await viewModel.CheckCommand.ExecuteAsync(null);
|
|
|
|
List<string?> changed = [];
|
|
viewModel.PropertyChanged += (_, e) => changed.Add(e.PropertyName);
|
|
|
|
viewModel.Dispose();
|
|
localization.CurrentLanguage = "ru";
|
|
|
|
Assert.Empty(changed);
|
|
}
|
|
|
|
private static ReleaseInfo Release(string version) => new(
|
|
Version.Parse(version),
|
|
$"v{version}",
|
|
new Uri("https://host/releases/tag"),
|
|
new ReleaseAsset($"CursorLang-{version}.msixbundle", new Uri("https://host/package"), 0));
|
|
|
|
private static UpdateViewModel Create(
|
|
IUpdateService updates,
|
|
AppSettings? settings = null,
|
|
ILocalizationService? localization = null) =>
|
|
new(updates,
|
|
localization ?? new FakeLocalizationService(),
|
|
settings ?? new AppSettings(),
|
|
new UpdateOptions());
|
|
}
|