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); } } }