using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Renci.SshNet.Channels;
using Renci.SshNet.Common;
using Renci.SshNet.Connection;
namespace Renci.SshNet.Tests.Common
{
///
/// Builds the byte stream an SCP server sends to the client and replays it into a real
/// through mocked session and channel objects.
///
public sealed class ScpServerTranscript
{
private readonly List _bytes = new List();
///
/// Appends a control line, terminated by a line feed.
///
public ScpServerTranscript Line(string line)
{
return Raw(line + "\n");
}
///
/// Appends encoded as UTF-8, which is the default
/// .
///
public ScpServerTranscript Raw(string data)
{
return Raw(Encoding.UTF8.GetBytes(data));
}
public ScpServerTranscript Raw(byte[] data)
{
_bytes.AddRange(data);
return this;
}
///
/// Appends a C (file) record, the file content, and the status byte that follows it.
///
public ScpServerTranscript File(string name, string content)
{
var bytes = Encoding.UTF8.GetBytes(content);
var length = bytes.Length.ToString(CultureInfo.InvariantCulture);
return Line($"C0644 {length} {name}").Raw(bytes).Raw([0]);
}
///
/// Replays the transcript into a connected and runs
/// on it.
///
/// The client operation to run.
///
/// to signal end of stream after the transcript, as when the
/// server closes the channel; otherwise a read past the end blocks.
///
/// How long to wait for to finish.
public ScpTranscriptResult Run(Action operation, bool closeChannel = false, TimeSpan? timeout = null)
{
var connectionInfo = new ConnectionInfo("host", 22, "user", new PasswordAuthenticationMethod("user", "pwd"));
var serviceFactoryMock = new Mock(MockBehavior.Strict);
var socketFactoryMock = new Mock(MockBehavior.Strict);
var sessionMock = new Mock(MockBehavior.Strict);
var channelMock = new Mock(MockBehavior.Strict);
var remotePathTransformationMock = new Mock(MockBehavior.Strict);
var sentData = new List();
var execRequests = new List();
var pipeStream = new PipeStream();
pipeStream.Write(_bytes.ToArray(), 0, _bytes.Count);
if (closeChannel)
{
pipeStream.Dispose();
}
_ = sessionMock.Setup(p => p.SessionLoggerFactory).Returns(NullLoggerFactory.Instance);
_ = serviceFactoryMock.Setup(p => p.CreateSocketFactory()).Returns(socketFactoryMock.Object);
_ = serviceFactoryMock.Setup(p => p.CreateSession(connectionInfo, socketFactoryMock.Object)).Returns(sessionMock.Object);
_ = sessionMock.Setup(p => p.Connect());
_ = serviceFactoryMock.Setup(p => p.CreatePipeStream()).Returns(pipeStream);
_ = sessionMock.Setup(p => p.CreateChannelSession()).Returns(channelMock.Object);
_ = channelMock.Setup(p => p.Open());
_ = remotePathTransformationMock.Setup(p => p.Transform(It.IsAny())).Returns(path => path);
_ = channelMock.Setup(p => p.SendExecRequest(It.IsAny()))
.Callback(execRequests.Add)
.Returns(true);
_ = channelMock.Setup(p => p.SendData(It.IsAny())).Callback(sentData.Add);
_ = channelMock.Setup(p => p.SendData(It.IsAny(), It.IsAny(), It.IsAny()))
.Callback((data, offset, count) => sentData.Add(data.Skip(offset).Take(count).ToArray()));
_ = channelMock.Setup(p => p.Dispose());
var scpClient = new ScpClient(connectionInfo, false, serviceFactoryMock.Object, remotePathTransformationMock.Object);
scpClient.Connect();
Exception exception = null;
var task = Task.Run(() =>
{
try
{
operation(scpClient);
}
catch (Exception ex)
{
exception = ex;
}
});
var completed = task.Wait(timeout ?? TimeSpan.FromSeconds(10));
pipeStream.Dispose();
_ = task.Wait(TimeSpan.FromSeconds(10));
return new ScpTranscriptResult(completed, exception, sentData, execRequests);
}
}
}