Desktop: let a lock pressed during an unlock win, instead of the unlock finishing behind it

This commit is contained in:
jaap-jan committed 2026-10-02 14:37:52 +02:00
1 parent 37e9d74938
commit 079a273d46
3 files changed
+99 -6

No files matched your search

@@ -639,6 +639,36 @@ public sealed partial class ShellFlowTests : IAsyncLifetime
server.LiveRowCount.ShouldBe(1, "a save should reach the server without pressing Sync");
}
/// <remarks>
/// Lock pressed while an unlock is still loading. The unlock is held at the point the vault has been handed
/// over and is about to be read — the device-key probe — and the lock lands there. The unlock used to carry
/// on regardless, into a vault the lock had disposed and a shell that no longer held one, and put an
/// exception's message on the lock screen. An earlier lock, before the hand-over, would have left the vault
/// opening behind the lock screen; both are the same check.
/// </remarks>
[Fact]
public async Task LockingWhileAnUnlockIsStillLoading_LeavesItLocked()
{
await EnrolledAndConfirmedAsync();
var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
deviceKeys.HoldAvailability = release.Task;
shell.Passphrase = Passphrase;
var unlocking = shell.UnlockCommand.ExecuteAsync(null);
await EventuallyAsync(() => shell.Vault is not null, "the unlock should reach the hand-over and wait there");
await shell.LockCommand.ExecuteAsync(null);
release.SetResult();
await unlocking;
shell.State.ShouldBe(ShellState.Locked);
shell.Vault.ShouldBeNull();
shell.StatusMessage.ShouldBe("Locked.");
}
[Fact]
public async Task AnAutomaticPass_SaysNothingWhenThereIsNothingToDo()
{
@@ -25,8 +25,18 @@ internal sealed class FakeDeviceKeyStore : IDeviceKeyStore
internal void Overwrite(byte[] replacement) => key = replacement;
/// <inheritdoc />
public ValueTask<bool> IsAvailableAsync(CancellationToken cancellationToken) =>
ValueTask.FromResult(IsAvailable);
public async ValueTask<bool> IsAvailableAsync(CancellationToken cancellationToken)
{
if (HoldAvailability is { } hold)
{
await hold.WaitAsync(cancellationToken).ConfigureAwait(false);
}
return IsAvailable;
}
/// <summary>When set, availability is not answered until it completes: an unlock held part-way.</summary>
internal Task? HoldAvailability { get; set; }
/// <inheritdoc />
public ValueTask SaveAsync(ReadOnlyMemory<byte> devicePrivateKey, CancellationToken cancellationToken)