modified release pipeline (#4)
Release / release (push) Canceled after 47s

await certificate from ssl.com

Reviewed-on: #4
Co-authored-by: Aleksandr Neychev <alexnejchev73@gmail.com>
This commit was merged in pull request #4.
This commit is contained in:
2026-08-13 11:17:22 +00:00
committed by alex
parent 5e11b16758
commit 42974ecc8f
34 changed files with 495 additions and 2091 deletions
+1 -3
View File
@@ -22,10 +22,8 @@ public sealed class AppTests
[InlineData(typeof(MainWindowPlacement))]
[InlineData(typeof(ILocalizationService))]
[InlineData(typeof(IStartupService))]
[InlineData(typeof(IUpdateService))]
[InlineData(typeof(UpdateOptions))]
[InlineData(typeof(Version))]
[InlineData(typeof(SettingsViewModel))]
[InlineData(typeof(UpdateViewModel))]
[InlineData(typeof(MainWindow))]
[InlineData(typeof(AppSettings))]
public void Everything_the_window_needs_is_declared_in_the_container(Type service)
@@ -1,19 +1,8 @@
using CursorLang.Core.Models;
using CursorLang.Settings.Services;
using CursorLang.Settings.ViewModels;
using CursorLang.Tests.Shared;
namespace CursorLang.Settings.Tests.Infrastructure;
/// <summary>
/// Parts every test needs but few tests care about.
/// </summary>
internal static class Fake
{
internal static UpdateViewModel Updates() =>
new(new FakeUpdateService(), new FakeLocalizationService());
}
/// <summary>
/// A theme that paints nothing and only remembers the windows attached to it.
/// </summary>
@@ -285,11 +285,12 @@ public sealed class SettingsViewModelTests
private static SettingsViewModel Create(
AppSettings? settings = null,
ILocalizationService? localization = null,
IStartupService? startup = null) =>
IStartupService? startup = null,
Version? version = null) =>
new(settings ?? new AppSettings(),
localization ?? new FakeLocalizationService(),
startup ?? new FakeStartupService(),
Fake.Updates());
version ?? new Version(1, 0, 0, 0));
// The setting travels to Windows without being awaited: the window must not freeze
private static async Task WaitForStartupRequests(FakeStartupService startup, int count)
@@ -1,230 +0,0 @@
using System.IO;
using System.Net.Http;
using CursorLang.Core.Models;
using CursorLang.Core.Services;
using CursorLang.Settings.ViewModels;
using CursorLang.Tests.Shared;
namespace CursorLang.Settings.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
{
// The section has something to say at every moment, the one before the first
// answer included: the status line is never an empty spot in the window
[Fact]
public void Before_the_first_check_the_section_says_so()
{
using UpdateViewModel viewModel = Create(new FakeUpdateService());
Assert.Equal(UpdateStatus.Idle, viewModel.Status);
Assert.Equal("en:UpdateNotChecked", viewModel.StatusText);
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.Equal("en:UpdateFailed", viewModel.StatusText);
}
// The window is opened by hand, and the answer is what the section is there
// for: a dead network is part of the answer rather than a reason to say nothing
[Fact]
public async Task A_check_when_the_window_opens_says_when_it_did_not_work_out()
{
var updates = new FakeUpdateService { Failure = new HttpRequestException("no network") };
using UpdateViewModel viewModel = Create(updates);
await viewModel.StartAsync();
Assert.Equal(UpdateStatus.Failed, viewModel.Status);
Assert.Equal("en:UpdateFailed", viewModel.StatusText);
}
// Nothing is remembered between openings: an answer from yesterday is worth
// less than today's, and the request costs nothing at this rate
[Fact]
public async Task Every_opening_of_the_window_asks_anew()
{
var updates = new FakeUpdateService();
using UpdateViewModel viewModel = Create(updates);
await viewModel.StartAsync();
Assert.Equal(1, updates.CheckCalls);
await viewModel.StartAsync();
Assert.Equal(2, updates.CheckCalls);
}
[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,
ILocalizationService? localization = null) =>
new(updates, localization ?? new FakeLocalizationService());
}
@@ -404,39 +404,20 @@ public sealed class MainWindowTests
});
}
// The version is nowhere else in the window, so the title has to carry it
[Fact]
public void The_updates_are_checked_by_the_button_in_the_window()
public void The_title_of_the_window_shows_the_version()
{
Sta.Run(() =>
{
var updates = new FakeUpdateService();
using UpdateViewModel section = CreateUpdates(updates);
using SettingsViewModel viewModel = CreateViewModel(updates: section);
var localization = new FakeLocalizationService();
localization.Strings["SettingsTitle"] = "CursorLang {0} — Settings";
Open(viewModel, window =>
{
Button check = Assert.Single(FindButtonsBoundTo(window, "Updates.CheckCommand"));
using SettingsViewModel viewModel = CreateViewModel(
localization: localization,
version: new Version(1, 2, 3, 0));
Assert.True(check.IsVisible);
check.Command.Execute(null);
Assert.Equal(1, updates.CheckCalls);
});
});
}
// An app installed from the Store is updated by the Store
[Fact]
public void An_app_that_updates_itself_elsewhere_shows_no_updates_section()
{
Sta.Run(() =>
{
using UpdateViewModel section = CreateUpdates(new FakeUpdateService { IsSupported = false });
using SettingsViewModel viewModel = CreateViewModel(updates: section);
Open(viewModel, window =>
Assert.All(FindButtonsBoundTo(window, "Updates.CheckCommand"), button =>
Assert.False(button.IsVisible)));
Open(viewModel, window => Assert.Equal("CursorLang 1.2.3 — Settings", window.Title));
});
}
@@ -486,11 +467,11 @@ public sealed class MainWindowTests
AppSettings? settings = null,
ILocalizationService? localization = null,
IStartupService? startup = null,
UpdateViewModel? updates = null) =>
Version? version = null) =>
new(settings ?? new AppSettings(),
localization ?? new FakeLocalizationService(),
startup ?? new FakeStartupService(),
updates ?? Fake.Updates());
version ?? new Version(1, 0, 0, 0));
private static void Open(SettingsViewModel viewModel, Action<MainWindow> check) =>
Open(viewModel, new FakeThemeService(), new MainWindowPlacement(), check);
@@ -525,9 +506,6 @@ public sealed class MainWindowTests
}
}
private static UpdateViewModel CreateUpdates(IUpdateService updates) =>
new(updates, new FakeLocalizationService());
private static IEnumerable<Button> FindButtonsBoundTo(DependencyObject root, string path) =>
FindAll<Button>(root).Where(button =>
BindingOperations.GetBinding(button, ButtonBase.CommandProperty)?.Path.Path == path);