using DodoSSH.Client.Ssh;
namespace DodoSSH.Client.App.Tests;
///
/// An SSH layer that connects to nothing and hands out shells that simply stay open.
///
///
/// This suite has no network and no sshd; what it needs from a session is only that one can exist
/// and outlive a lock. The real factory was here before and was never called by anything, which made the
/// suite's independence from the network a coincidence rather than a property.
///
internal sealed class IdleSshConnectionFactory : ISshConnectionFactory
{
///
public Task ConnectAsync(
SshConnectionRequest request,
CancellationToken cancellationToken) =>
Task.FromResult(new IdleConnection(request));
}
/// A connection whose shells never end by themselves.
internal sealed class IdleConnection(SshConnectionRequest request) : ISshConnection
{
///
public bool IsConnected { get; private set; } = true;
///
public HostKeyPresentation HostKey { get; } =
new(request.Host, request.Port, "ssh-ed25519", "SHA256:idle");
///
public Task OpenShellAsync(
TerminalSize size,
CancellationToken cancellationToken) =>
Task.FromResult(new IdleShell());
///
public ValueTask DisposeAsync()
{
IsConnected = false;
return ValueTask.CompletedTask;
}
}
///
/// A shell that is connected, silent, and ends only when something ends it.
///
///
/// Which is what the interesting case looks like from the host's side: a remote sitting in the middle of
/// a long job produces nothing for minutes and must not be mistaken for one that has exited.
///
internal sealed class IdleShell : ISshShellSession
{
///
public bool IsOpen { get; private set; } = true;
///
public async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken)
{
// Never returns 0 of its own accord: 0 would tell the pump the remote closed the channel, which
// is the opposite of what this stands in for.
await Task.Delay(System.Threading.Timeout.InfiniteTimeSpan, cancellationToken)
.ConfigureAwait(false);
return 0;
}
///
public ValueTask WriteAsync(ReadOnlyMemory data, CancellationToken cancellationToken) =>
ValueTask.CompletedTask;
///
public void Resize(TerminalSize size)
{
}
///
public ValueTask DisposeAsync()
{
IsOpen = false;
return ValueTask.CompletedTask;
}
}