Public Access
128 lines
4.7 KiB
C#
128 lines
4.7 KiB
C#
using Avalonia.Controls;
|
|
using Avalonia.Input;
|
|
using Avalonia.Threading;
|
|
using DodoSSH.Client.App.Views;
|
|
using DodoSSH.Client.Session;
|
|
using DodoSSH.Client.Session.Tests;
|
|
using DodoSSH.Client.Shell.ViewModels;
|
|
using DodoSSH.Client.Ssh;
|
|
using DodoSSH.Client.Storage;
|
|
using DodoSSH.Client.Terminal;
|
|
using DodoSSH.Client.Transfer;
|
|
using DodoSSH.Crypto;
|
|
using NSubstitute;
|
|
|
|
namespace DodoSSH.Client.App.Layout.Tests;
|
|
|
|
/// <summary>
|
|
/// The keyboard on the cards a closed vault shows: where it is when the card appears, and what Enter does.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Launch used to leave the keyboard nowhere. Focus only moved on the way out of an unlocked vault, so the
|
|
/// passphrase somebody started typing the moment the window appeared went nowhere until they clicked the box,
|
|
/// and Enter in the server address did nothing at all.
|
|
/// </para>
|
|
/// <para>
|
|
/// The whole window, which <see cref="LayoutHarnessTests.WhyTheWindowItselfIsNeverShown"/> says cannot be shown
|
|
/// here: the terminal's WebView refuses this thread's apartment when it is attached. It is taken out of the
|
|
/// tree before the window is shown, which is everything that test is about and nothing these are.
|
|
/// </para>
|
|
/// </remarks>
|
|
public sealed class ClosedVaultFocusTests
|
|
{
|
|
private const string Passphrase = "a sufficiently long passphrase";
|
|
|
|
private static readonly Argon2Profile CheapProfile =
|
|
Argon2Profile.FromStoredParameters(memoryKibibytes: 8 * 1024, passes: 1, parallelism: 1);
|
|
|
|
private static CancellationToken Token => TestContext.Current.CancellationToken;
|
|
|
|
[Fact]
|
|
public async Task AFreshMachine_PutsTheKeyboardInTheServerAddressAndEnterSignsIn()
|
|
{
|
|
using var caches = ClientCacheFactory.ForMemory($"focus-{Guid.CreateVersion7():N}");
|
|
await caches.MigrateAsync(Token);
|
|
|
|
await using var workspace = Workspace();
|
|
await using var shell = Shell(caches, workspace);
|
|
|
|
await shell.StartAsync(Token);
|
|
shell.State.ShouldBe(ShellState.NeedsServer);
|
|
|
|
await LayoutHarness.OnTheUiThreadAsync(
|
|
() => WithWindow(shell, window =>
|
|
{
|
|
var box = window.FindControl<TextBox>("ServerUrlBox").ShouldNotBeNull();
|
|
|
|
window.FocusManager!.GetFocusedElement().ShouldBeSameAs(box);
|
|
|
|
box.KeyBindings.ShouldContain(binding =>
|
|
binding.Gesture == new KeyGesture(Key.Enter) && binding.Command == shell.SignInCommand);
|
|
}),
|
|
Token);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AnEnrolledMachine_PutsTheKeyboardInThePassphraseBox()
|
|
{
|
|
using var caches = ClientCacheFactory.ForMemory($"focus-{Guid.CreateVersion7():N}");
|
|
await caches.MigrateAsync(Token);
|
|
|
|
await new AccountProvisioner(new FakeAccountServer(), new StubKeyBinding(), caches, TimeProvider.System, CheapProfile)
|
|
.EnrollAsync("https://dodossh.example", Passphrase, "laptop", "Personal", Token);
|
|
|
|
await using var workspace = Workspace();
|
|
await using var shell = Shell(caches, workspace);
|
|
|
|
await shell.StartAsync(Token);
|
|
shell.State.ShouldBe(ShellState.Locked);
|
|
|
|
await LayoutHarness.OnTheUiThreadAsync(
|
|
() => WithWindow(shell, window =>
|
|
window.FocusManager!.GetFocusedElement()
|
|
.ShouldBeSameAs(window.FindControl<UnlockCard>("UnlockPane").ShouldNotBeNull().PassphraseBox)),
|
|
Token);
|
|
}
|
|
|
|
private static TerminalWorkspace Workspace() =>
|
|
new(
|
|
new InMemoryTerminalAssetProvider(new Dictionary<string, TerminalAsset>(StringComparer.Ordinal)),
|
|
Substitute.For<ISshConnectionFactory>(),
|
|
TimeProvider.System);
|
|
|
|
private static MainWindowViewModel Shell(ClientCacheFactory caches, TerminalWorkspace workspace) =>
|
|
new(
|
|
new ClientPaths(Path.Combine(Path.GetTempPath(), $"dodossh-focus-{Guid.CreateVersion7():N}")),
|
|
caches,
|
|
workspace,
|
|
new VaultKnownHostStore(),
|
|
new UnavailableDeviceKeyStore(),
|
|
static (_, _) => throw new InvalidOperationException("A layout test has no network."),
|
|
TimeProvider.System,
|
|
Substitute.For<ISftpSessionFactory>(),
|
|
CheapProfile);
|
|
|
|
private static void WithWindow(MainWindowViewModel shell, Action<MainWindow> assert)
|
|
{
|
|
var window = new MainWindow { DataContext = shell };
|
|
|
|
if (window.FindControl<NativeWebView>("Terminal") is { Parent: Panel panel } web)
|
|
{
|
|
panel.Children.Remove(web);
|
|
}
|
|
|
|
try
|
|
{
|
|
LayoutHarness.Settle(window, LayoutHarness.MinimumWidth, LayoutHarness.MinimumHeight);
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
assert(window);
|
|
}
|
|
finally
|
|
{
|
|
window.Close();
|
|
}
|
|
}
|
|
}
|