diff --git a/src/Renci.SshNet.Silverlight/Renci.SshNet.Silverlight.csproj b/src/Renci.SshNet.Silverlight/Renci.SshNet.Silverlight.csproj index 6769eabf..39f66a0e 100644 --- a/src/Renci.SshNet.Silverlight/Renci.SshNet.Silverlight.csproj +++ b/src/Renci.SshNet.Silverlight/Renci.SshNet.Silverlight.csproj @@ -1183,6 +1183,9 @@ SftpClient.cs + + + ISftpClient.cs Sftp\Flags.cs diff --git a/src/Renci.SshNet.Silverlight5/Renci.SshNet.Silverlight5.csproj b/src/Renci.SshNet.Silverlight5/Renci.SshNet.Silverlight5.csproj index 6892d3b6..fb59067a 100644 --- a/src/Renci.SshNet.Silverlight5/Renci.SshNet.Silverlight5.csproj +++ b/src/Renci.SshNet.Silverlight5/Renci.SshNet.Silverlight5.csproj @@ -1189,6 +1189,9 @@ SftpClient.cs + + + ISftpClient.cs Sftp\Flags.cs diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTestBase.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTestBase.cs new file mode 100644 index 00000000..2cce2e91 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/BaseClientTestBase.cs @@ -0,0 +1,35 @@ +using Moq; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; + +namespace Renci.SshNet.Tests.Classes +{ + public abstract class BaseClientTestBase : TripleATestBase + { + internal Mock _serviceFactoryMock { get; private set; } + internal Mock _socketFactoryMock { get; private set; } + internal Mock _sessionMock { get; private set; } + + protected virtual void CreateMocks() + { + _serviceFactoryMock = new Mock(MockBehavior.Strict); + _socketFactoryMock = new Mock(MockBehavior.Strict); + _sessionMock = new Mock(MockBehavior.Strict); + } + + protected virtual void SetupData() + { + } + + protected virtual void SetupMocks() + { + } + + protected override void Arrange() + { + CreateMocks(); + SetupData(); + SetupMocks(); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connect_OnConnectedThrowsException.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connect_OnConnectedThrowsException.cs index 792ab89d..e21f516d 100644 --- a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connect_OnConnectedThrowsException.cs +++ b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connect_OnConnectedThrowsException.cs @@ -9,24 +9,30 @@ using Renci.SshNet.Security; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class BaseClientTest_Connect_OnConnectedThrowsException + public class BaseClientTest_Connect_OnConnectedThrowsException : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private MyClient _client; private ConnectionInfo _connectionInfo; private ApplicationException _onConnectException; private ApplicationException _actualException; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); + _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); + _onConnectException = new ApplicationException(); } - [TestCleanup] - public void Cleanup() + protected override void SetupMocks() + { + _serviceFactoryMock.Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); + _sessionMock.Setup(p => p.Connect()); + _sessionMock.Setup(p => p.Dispose()); + } + + protected override void TearDown() { if (_client != null) { @@ -36,31 +42,9 @@ namespace Renci.SshNet.Tests.Classes } } - private void SetupData() + protected override void Arrange() { - _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); - _onConnectException = new ApplicationException(); - } - - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - } - - private void SetupMocks() - { - _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); - _sessionMock.Setup(p => p.Connect()); - _sessionMock.Setup(p => p.Dispose()); - } - - protected void Arrange() - { - SetupData(); - CreateMocks(); - SetupMocks(); + base.Arrange(); _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object) { @@ -68,7 +52,7 @@ namespace Renci.SshNet.Tests.Classes }; } - protected void Act() + protected override void Act() { try { @@ -88,10 +72,17 @@ namespace Renci.SshNet.Tests.Classes Assert.AreSame(_onConnectException, _actualException); } + [TestMethod] + public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce() + { + _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once); + } + [TestMethod] public void CreateSessionOnServiceFactoryShouldBeInvokedOnce() { - _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once); + _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object), + Times.Once); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NegativeOne.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NegativeOne.cs index fb58e1f4..e3b10c87 100644 --- a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NegativeOne.cs +++ b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NegativeOne.cs @@ -2,29 +2,49 @@ using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Renci.SshNet.Connection; using Renci.SshNet.Messages.Transport; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class BaseClientTest_Connected_KeepAliveInterval_NegativeOne + public class BaseClientTest_Connected_KeepAliveInterval_NegativeOne : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private BaseClient _client; private ConnectionInfo _connectionInfo; private TimeSpan _keepAliveInterval; private int _keepAliveCount; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); + _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); + _keepAliveInterval = TimeSpan.FromMilliseconds(100d); + _keepAliveCount = 0; } - [TestCleanup] - public void Cleanup() + protected override void SetupMocks() + { + _serviceFactoryMock.Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); + _sessionMock.Setup(p => p.Connect()); + _sessionMock.Setup(p => p.IsConnected).Returns(true); + _sessionMock.Setup(p => p.TrySendMessage(It.IsAny())) + .Returns(true) + .Callback(() => Interlocked.Increment(ref _keepAliveCount)); + } + + protected override void Arrange() + { + base.Arrange(); + + _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); + _client.Connect(); + _client.KeepAliveInterval = _keepAliveInterval; + } + + protected override void TearDown() { if (_client != null) { @@ -34,42 +54,7 @@ namespace Renci.SshNet.Tests.Classes } } - private void SetupData() - { - _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); - _keepAliveInterval = TimeSpan.FromMilliseconds(100d); - _keepAliveCount = 0; - } - - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - } - - private void SetupMocks() - { - _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); - _sessionMock.Setup(p => p.Connect()); - _sessionMock.Setup(p => p.IsConnected).Returns(true); - _sessionMock.Setup(p => p.TrySendMessage(It.IsAny())) - .Returns(true) - .Callback(() => Interlocked.Increment(ref _keepAliveCount)); - } - - protected void Arrange() - { - SetupData(); - CreateMocks(); - SetupMocks(); - - _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); - _client.Connect(); - _client.KeepAliveInterval = _keepAliveInterval; - } - - protected void Act() + protected override void Act() { // allow keep-alive to be sent once Thread.Sleep(150); @@ -84,10 +69,16 @@ namespace Renci.SshNet.Tests.Classes Assert.AreEqual(TimeSpan.FromMilliseconds(-1), _client.KeepAliveInterval); } + [TestMethod] + public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce() + { + _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once); + } + [TestMethod] public void CreateSessionOnServiceFactoryShouldBeInvokedOnce() { - _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once); + _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object), Times.Once); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NotNegativeOne.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NotNegativeOne.cs index a3c807bd..26a8da00 100644 --- a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NotNegativeOne.cs +++ b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAliveInterval_NotNegativeOne.cs @@ -7,24 +7,42 @@ using Renci.SshNet.Messages.Transport; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class BaseClientTest_Connected_KeepAliveInterval_NotNegativeOne + public class BaseClientTest_Connected_KeepAliveInterval_NotNegativeOne : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private BaseClient _client; private ConnectionInfo _connectionInfo; private TimeSpan _keepAliveInterval; private int _keepAliveCount; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); + _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); + _keepAliveInterval = TimeSpan.FromMilliseconds(50d); + _keepAliveCount = 0; } - [TestCleanup] - public void Cleanup() + protected override void SetupMocks() + { + _serviceFactoryMock.Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); + _sessionMock.Setup(p => p.Connect()); + _sessionMock.Setup(p => p.IsConnected).Returns(true); + _sessionMock.Setup(p => p.TrySendMessage(It.IsAny())) + .Returns(true) + .Callback(() => Interlocked.Increment(ref _keepAliveCount)); + } + + protected override void Arrange() + { + base.Arrange(); + + _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); + _client.Connect(); + } + + protected override void TearDown() { if (_client != null) { @@ -34,41 +52,7 @@ namespace Renci.SshNet.Tests.Classes } } - private void SetupData() - { - _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); - _keepAliveInterval = TimeSpan.FromMilliseconds(50d); - _keepAliveCount = 0; - } - - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - } - - private void SetupMocks() - { - _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); - _sessionMock.Setup(p => p.Connect()); - _sessionMock.Setup(p => p.IsConnected).Returns(true); - _sessionMock.Setup(p => p.TrySendMessage(It.IsAny())) - .Returns(true) - .Callback(() => Interlocked.Increment(ref _keepAliveCount)); - } - - protected void Arrange() - { - SetupData(); - CreateMocks(); - SetupMocks(); - - _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); - _client.Connect(); - } - - protected void Act() + protected override void Act() { _client.KeepAliveInterval = _keepAliveInterval; @@ -82,10 +66,17 @@ namespace Renci.SshNet.Tests.Classes Assert.AreEqual(_keepAliveInterval, _client.KeepAliveInterval); } + [TestMethod] + public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce() + { + _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once); + } + [TestMethod] public void CreateSessionOnServiceFactoryShouldBeInvokedOnce() { - _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once); + _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object), + Times.Once); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAlivesNotSentConcurrently.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAlivesNotSentConcurrently.cs index 4430b657..25d6ec7c 100644 --- a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAlivesNotSentConcurrently.cs +++ b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Connected_KeepAlivesNotSentConcurrently.cs @@ -7,24 +7,53 @@ using Renci.SshNet.Messages.Transport; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class BaseClientTest_Connected_KeepAlivesNotSentConcurrently + public class BaseClientTest_Connected_KeepAlivesNotSentConcurrently : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private MockSequence _mockSequence; private BaseClient _client; private ConnectionInfo _connectionInfo; private ManualResetEvent _keepAliveSent; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); + _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); + _keepAliveSent = new ManualResetEvent(false); } - [TestCleanup] - public void Cleanup() + protected override void SetupMocks() + { + _mockSequence = new MockSequence(); + + _serviceFactoryMock.InSequence(_mockSequence) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(_mockSequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); + _sessionMock.InSequence(_mockSequence) + .Setup(p => p.Connect()); + _sessionMock.InSequence(_mockSequence) + .Setup(p => p.TrySendMessage(It.IsAny())) + .Returns(true) + .Callback(() => + { + Thread.Sleep(300); + _keepAliveSent.Set(); + }); + } + + protected override void Arrange() + { + base.Arrange(); + + _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object) + { + KeepAliveInterval = TimeSpan.FromMilliseconds(50d) + }; + _client.Connect(); + } + + protected override void TearDown() { if (_client != null) { @@ -34,47 +63,7 @@ namespace Renci.SshNet.Tests.Classes } } - private void SetupData() - { - _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); - _keepAliveSent = new ManualResetEvent(false); - } - - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - } - - private void SetupMocks() - { - _mockSequence = new MockSequence(); - - _serviceFactoryMock.InSequence(_mockSequence).Setup(p => p.CreateSession(_connectionInfo)).Returns(_sessionMock.Object); - _sessionMock.InSequence(_mockSequence).Setup(p => p.Connect()); - _sessionMock.InSequence(_mockSequence).Setup(p => p.TrySendMessage(It.IsAny())) - .Returns(true) - .Callback(() => - { - Thread.Sleep(300); - _keepAliveSent.Set(); - }); - } - - protected void Arrange() - { - SetupData(); - CreateMocks(); - SetupMocks(); - - _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object) - { - KeepAliveInterval = TimeSpan.FromMilliseconds(50d) - }; - _client.Connect(); - } - - protected void Act() + protected override void Act() { // should keep-alive message be sent concurrently, then multiple keep-alive // message would be sent during this sleep period diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_Connect.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_Connect.cs new file mode 100644 index 00000000..51cd2d81 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_Connect.cs @@ -0,0 +1,107 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Connection; + +namespace Renci.SshNet.Tests.Classes +{ + [TestClass] + public class BaseClientTest_Disconnected_Connect : BaseClientTestBase + { + private Mock _socketFactory2Mock; + private Mock _session2Mock; + private BaseClient _client; + private ConnectionInfo _connectionInfo; + + protected override void SetupData() + { + _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); + } + + protected override void CreateMocks() + { + base.CreateMocks(); + + _socketFactory2Mock = new Mock(MockBehavior.Strict); + _session2Mock = new Mock(MockBehavior.Strict); + } + + protected override void SetupMocks() + { + var sequence = new MockSequence(); + + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); + _sessionMock.InSequence(sequence) + .Setup(p => p.Connect()); + _sessionMock.InSequence(sequence) + .Setup(p => p.OnDisconnecting()); + _sessionMock.InSequence(sequence) + .Setup(p => p.Dispose()); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactory2Mock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactory2Mock.Object)) + .Returns(_session2Mock.Object); + _session2Mock.InSequence(sequence) + .Setup(p => p.Connect()); + } + + protected override void Arrange() + { + base.Arrange(); + + _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); + _client.Connect(); + _client.Disconnect(); + } + + protected override void TearDown() + { + if (_client != null) + { + _session2Mock.Setup(p => p.OnDisconnecting()); + _session2Mock.Setup(p => p.Dispose()); + _client.Dispose(); + } + } + + protected override void Act() + { + _client.Connect(); + } + + [TestMethod] + public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedTwic() + { + _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Exactly(2)); + } + + [TestMethod] + public void CreateSessionOnServiceFactoryShouldBeInvokedTwice() + { + _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object), + Times.Once); + _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactory2Mock.Object), + Times.Once); + } + + [TestMethod] + public void ConnectOnSessionShouldBeInvokedTwice() + { + _sessionMock.Verify(p => p.Connect(), Times.Once); + _session2Mock.Verify(p => p.Connect(), Times.Once); + } + + private class MyClient : BaseClient + { + public MyClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServiceFactory serviceFactory) : base(connectionInfo, ownsConnectionInfo, serviceFactory) + { + } + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_KeepAliveInterval_NotNegativeOne.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_KeepAliveInterval_NotNegativeOne.cs index 588bfd83..1ca4b19d 100644 --- a/src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_KeepAliveInterval_NotNegativeOne.cs +++ b/src/Renci.SshNet.Tests/Classes/BaseClientTest_Disconnected_KeepAliveInterval_NotNegativeOne.cs @@ -7,23 +7,39 @@ using Renci.SshNet.Messages.Transport; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class BaseClientTest_Disconnected_KeepAliveInterval_NotNegativeOne + public class BaseClientTest_Disconnected_KeepAliveInterval_NotNegativeOne : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private BaseClient _client; private ConnectionInfo _connectionInfo; private TimeSpan _keepAliveInterval; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); + _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); + _keepAliveInterval = TimeSpan.FromMilliseconds(50d); } - [TestCleanup] - public void Cleanup() + protected override void SetupMocks() + { + _serviceFactoryMock.Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); + _sessionMock.Setup(p => p.Connect()); + _sessionMock.Setup(p => p.IsConnected).Returns(false); + _sessionMock.Setup(p => p.TrySendMessage(It.IsAny())) + .Returns(true); + } + + protected override void Arrange() + { + base.Arrange(); + + _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); + _client.Connect(); + } + + protected override void TearDown() { if (_client != null) { @@ -33,39 +49,7 @@ namespace Renci.SshNet.Tests.Classes } } - private void SetupData() - { - _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); - _keepAliveInterval = TimeSpan.FromMilliseconds(50d); - } - - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - } - - private void SetupMocks() - { - _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); - _sessionMock.Setup(p => p.Connect()); - _sessionMock.Setup(p => p.IsConnected).Returns(false); - _sessionMock.Setup(p => p.TrySendMessage(It.IsAny())) - .Returns(true); - } - - protected void Arrange() - { - SetupData(); - CreateMocks(); - SetupMocks(); - - _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); - _client.Connect(); - } - - protected void Act() + protected override void Act() { _client.KeepAliveInterval = _keepAliveInterval; @@ -79,10 +63,17 @@ namespace Renci.SshNet.Tests.Classes Assert.AreEqual(_keepAliveInterval, _client.KeepAliveInterval); } + [TestMethod] + public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce() + { + _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once); + } + [TestMethod] public void CreateSessionOnServiceFactoryShouldBeInvokedOnce() { - _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once); + _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object), + Times.Once); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/BaseClientTest_NotConnected_KeepAliveInterval_NotNegativeOne.cs b/src/Renci.SshNet.Tests/Classes/BaseClientTest_NotConnected_KeepAliveInterval_NotNegativeOne.cs index dfaa8b27..0f449c82 100644 --- a/src/Renci.SshNet.Tests/Classes/BaseClientTest_NotConnected_KeepAliveInterval_NotNegativeOne.cs +++ b/src/Renci.SshNet.Tests/Classes/BaseClientTest_NotConnected_KeepAliveInterval_NotNegativeOne.cs @@ -7,24 +7,28 @@ using Renci.SshNet.Messages.Transport; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class BaseClientTest_NotConnected_KeepAliveInterval_NotNegativeOne + public class BaseClientTest_NotConnected_KeepAliveInterval_NotNegativeOne : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private BaseClient _client; private ConnectionInfo _connectionInfo; private TimeSpan _keepAliveInterval; private int _keepAliveCount; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); + _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); + _keepAliveInterval = TimeSpan.FromMilliseconds(100d); + _keepAliveCount = 0; } - [TestCleanup] - public void Cleanup() + protected override void Arrange() + { + base.Arrange(); + + _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); + } + + protected override void TearDown() { if (_client != null) { @@ -34,33 +38,7 @@ namespace Renci.SshNet.Tests.Classes } } - private void SetupData() - { - _connectionInfo = new ConnectionInfo("host", "user", new PasswordAuthenticationMethod("user", "pwd")); - _keepAliveInterval = TimeSpan.FromMilliseconds(100d); - _keepAliveCount = 0; - } - - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - } - - private static void SetupMocks() - { - } - - protected void Arrange() - { - SetupData(); - CreateMocks(); - SetupMocks(); - - _client = new MyClient(_connectionInfo, false, _serviceFactoryMock.Object); - } - - protected void Act() + protected override void Act() { _client.KeepAliveInterval = _keepAliveInterval; @@ -77,7 +55,10 @@ namespace Renci.SshNet.Tests.Classes [TestMethod] public void ConnectShouldActivateKeepAliveIfSessionIs() { - _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo)).Returns(_sessionMock.Object); + _serviceFactoryMock.Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); _sessionMock.Setup(p => p.Connect()); _sessionMock.Setup(p => p.TrySendMessage(It.IsAny())) .Returns(true) diff --git a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs index d60549e5..16f08ba9 100644 --- a/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs +++ b/src/Renci.SshNet.Tests/Classes/ClientAuthenticationTest_Success_MultiList_PartialSuccessLimitReachedFollowedBySuccessInAlternateBranch.cs @@ -181,4 +181,4 @@ namespace Renci.SshNet.Tests.Classes PublicKeyAuthenticationMethodMock.Verify(p => p.Authenticate(SessionMock.Object), Times.Exactly(2)); } } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet.Tests/Classes/Common/BigIntegerTest.cs b/src/Renci.SshNet.Tests/Classes/Common/BigIntegerTest.cs index 8cca838d..c99fb2a6 100644 --- a/src/Renci.SshNet.Tests/Classes/Common/BigIntegerTest.cs +++ b/src/Renci.SshNet.Tests/Classes/Common/BigIntegerTest.cs @@ -1506,10 +1506,12 @@ namespace Renci.SshNet.Tests.Classes.Common Assert.AreEqual("0", a.ToString(), "#4"); a = new BigInteger(); +#pragma warning disable CS1718 // Comparison made to same variable Assert.AreEqual(true, a == a, "#5"); a = new BigInteger(); Assert.AreEqual(false, a < a, "#6"); +#pragma warning restore CS1718 // Comparison made to same variable a = new BigInteger(); Assert.AreEqual(true, a < 10L, "#7"); diff --git a/src/Renci.SshNet.Tests/Classes/Common/PacketDumpTest.cs b/src/Renci.SshNet.Tests/Classes/Common/PacketDumpTest.cs new file mode 100644 index 00000000..00401915 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Common/PacketDumpTest.cs @@ -0,0 +1,155 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Renci.SshNet.Common; +using System; + +namespace Renci.SshNet.Tests.Classes.Common +{ + [TestClass] + public class PacketDumpTest + { + [TestMethod] + public void Create_ByteArrayAndIndentLevel_DataIsNull() + { + const byte[] data = null; + + try + { + PacketDump.Create(data, 0); + Assert.Fail(); + } + catch (ArgumentNullException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("data", ex.ParamName); + } + } + + [TestMethod] + public void Create_ByteArrayAndIndentLevel_IndentLevelLessThanZero() + { + var data = new byte[0]; + + try + { + PacketDump.Create(data, -1); + Assert.Fail(); + } + catch (ArgumentOutOfRangeException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual(string.Format("Cannot be less than zero.{0}Parameter name: {1}", Environment.NewLine, ex.ParamName), ex.Message); + Assert.AreEqual("indentLevel", ex.ParamName); + } + } + + [TestMethod] + public void Create_ByteArrayAndIndentLevel_DataIsEmpty() + { + var data = new byte[0]; + + var actual = PacketDump.Create(data, 2); + + Assert.AreEqual(string.Empty, actual); + } + + [TestMethod] + public void Create_ByteArrayAndIndentLevel_DataIsMultipleOfLineWidth_IndentLevelTwo() + { + var data = new byte[] + { + 0x07, 0x00, 0x1f, 0x65, 0x20, 0x62, 0x09, 0x44, 0x7f, 0x0d, 0x0a, 0x36, 0x80, 0x53, 0x53, 0x48, + 0x2e, 0x4e, 0x45, 0x54, 0x20, 0x32, 0x30, 0x32, 0x30, 0x20, 0xf6, 0x7a, 0x32, 0x7f, 0x1f, 0x7e + }; + var expected = " 00000000 07 00 1F 65 20 62 09 44 7F 0D 0A 36 80 53 53 48 ...e b.D...6.SSH" + + Environment.NewLine + + " 00000010 2E 4E 45 54 20 32 30 32 30 20 F6 7A 32 7F 1F 7E .NET 2020 .z2..~"; + + var actual = PacketDump.Create(data, 2); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void Create_ByteArrayAndIndentLevel_DataIsMultipleOfLineWidth_IndentLevelZero() + { + var data = new byte[] + { + 0x07, 0x00, 0x1f, 0x65, 0x20, 0x62, 0x09, 0x44, 0x7f, 0x0d, 0x0a, 0x36, 0x80, 0x53, 0x53, 0x48, + 0x2e, 0x4e, 0x45, 0x54, 0x20, 0x32, 0x30, 0x32, 0x30, 0x20, 0xf6, 0x7a, 0x32, 0x7f, 0x1f, 0x7e + }; + var expected = "00000000 07 00 1F 65 20 62 09 44 7F 0D 0A 36 80 53 53 48 ...e b.D...6.SSH" + + Environment.NewLine + + "00000010 2E 4E 45 54 20 32 30 32 30 20 F6 7A 32 7F 1F 7E .NET 2020 .z2..~"; + + var actual = PacketDump.Create(data, 0); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void Create_ByteArrayAndIndentLevel_DataIsLineWith() + { + var data = new byte[] + { + 0x07, 0x00, 0x1f, 0x65, 0x20, 0x62, 0x09, 0x44, 0x7f, 0x0d, 0x0a, 0x36, 0x80, 0x53, 0x53, 0x48 + }; + var expected = " 00000000 07 00 1F 65 20 62 09 44 7F 0D 0A 36 80 53 53 48 ...e b.D...6.SSH"; + + var actual = PacketDump.Create(data, 2); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void Create_ByteArrayAndIndentLevel_DataIsLessThanLineWith() + { + var data = new byte[] + { + 0x07, 0x00, 0x1f, 0x65, 0x20, 0x62, 0x09, 0x44, 0x7f, 0x0d, 0x0a, 0x36, 0x80, 0x53 + }; + var expected = " 00000000 07 00 1F 65 20 62 09 44 7F 0D 0A 36 80 53 ...e b.D...6.S"; + + var actual = PacketDump.Create(data, 2); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void Create_ByteArrayAndIndentLevel_DataIsGreaterThanLineWidthButLessThanMultipleOfLineWidth() + { + var data = new byte[] + { + 0x07, 0x00, 0x1f, 0x65, 0x20, 0x62, 0x09, 0x44, 0x7f, 0x0d, 0x0a, 0x36, 0x80, 0x53, 0x53, 0x48, + 0x2e, 0x4e, 0x45, 0x54 + }; + var expected = " 00000000 07 00 1F 65 20 62 09 44 7F 0D 0A 36 80 53 53 48 ...e b.D...6.SSH" + + Environment.NewLine + + " 00000010 2E 4E 45 54 .NET"; + + var actual = PacketDump.Create(data, 2); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void Create_ByteArrayAndIndentLevel_DataIsGreaterThanMultipleOfLineWidth() + { + var data = new byte[] + { + 0x07, 0x00, 0x1f, 0x65, 0x20, 0x62, 0x09, 0x44, 0x7f, 0x0d, 0x0a, 0x36, 0x80, 0x53, 0x53, 0x48, + 0x2e, 0x4e, 0x45, 0x54, 0x20, 0x32, 0x30, 0x32, 0x30, 0x20, 0xf6, 0x7a, 0x32, 0x7f, 0x1f, 0x7e, + 0x78, 0x54, 0x00, 0x52 + }; + var expected = " 00000000 07 00 1F 65 20 62 09 44 7F 0D 0A 36 80 53 53 48 ...e b.D...6.SSH" + + Environment.NewLine + + " 00000010 2E 4E 45 54 20 32 30 32 30 20 F6 7A 32 7F 1F 7E .NET 2020 .z2..~" + + Environment.NewLine + + " 00000020 78 54 00 52 xT.R"; + + var actual = PacketDump.Create(data, 2); + + Assert.AreEqual(expected, actual); + + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Common/PipeStream_Close_BlockingRead.cs b/src/Renci.SshNet.Tests/Classes/Common/PipeStream_Close_BlockingRead.cs index 7438ceda..fb010c14 100644 --- a/src/Renci.SshNet.Tests/Classes/Common/PipeStream_Close_BlockingRead.cs +++ b/src/Renci.SshNet.Tests/Classes/Common/PipeStream_Close_BlockingRead.cs @@ -1,18 +1,18 @@ -using System; +using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Common; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Common { [TestClass] - public class PipeStream_Close_BlockingRead + public class PipeStream_Close_BlockingRead : TripleATestBase { private PipeStream _pipeStream; private int _bytesRead; - private IAsyncResult _asyncReadResult; + private Thread _readThread; - [TestInitialize] - public void Init() + protected override void Arrange() { _pipeStream = new PipeStream(); @@ -22,26 +22,25 @@ namespace Renci.SshNet.Tests.Classes.Common _bytesRead = 123; - Action readAction = () => _bytesRead = _pipeStream.Read(new byte[4], 0, 4); - _asyncReadResult = readAction.BeginInvoke(null, null); - // ensure we've started reading - _asyncReadResult.AsyncWaitHandle.WaitOne(50); + _readThread = new Thread(() => _bytesRead = _pipeStream.Read(new byte[4], 0, 4)); + _readThread.Start(); - Act(); + // ensure we've started reading + Assert.IsFalse(_readThread.Join(50)); } - protected void Act() + protected override void Act() { _pipeStream.Close(); // give async read time to complete - _asyncReadResult.AsyncWaitHandle.WaitOne(100); + _readThread.Join(100); } [TestMethod] public void BlockingReadShouldHaveBeenInterrupted() { - Assert.IsTrue(_asyncReadResult.IsCompleted); + Assert.AreEqual(ThreadState.Stopped, _readThread.ThreadState); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/Common/PipeStream_Close_BlockingWrite.cs b/src/Renci.SshNet.Tests/Classes/Common/PipeStream_Close_BlockingWrite.cs index d270679a..1fbd2d15 100644 --- a/src/Renci.SshNet.Tests/Classes/Common/PipeStream_Close_BlockingWrite.cs +++ b/src/Renci.SshNet.Tests/Classes/Common/PipeStream_Close_BlockingWrite.cs @@ -1,22 +1,23 @@ using System; +using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Common; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Common { [TestClass] - public class PipeStream_Close_BlockingWrite + public class PipeStream_Close_BlockingWrite : TripleATestBase { private PipeStream _pipeStream; private Exception _writeException; - private IAsyncResult _asyncWriteResult; + private Thread _writehread; - [TestInitialize] - public void Init() + protected override void Arrange() { _pipeStream = new PipeStream {MaxBufferLength = 3}; - Action writeAction = () => + _writehread = new Thread(() => { _pipeStream.WriteByte(10); _pipeStream.WriteByte(13); @@ -33,26 +34,25 @@ namespace Renci.SshNet.Tests.Classes.Common _writeException = ex; throw; } - }; - _asyncWriteResult = writeAction.BeginInvoke(null, null); - // ensure we've started writing - _asyncWriteResult.AsyncWaitHandle.WaitOne(50); + }); + _writehread.Start(); - Act(); + // ensure we've started writing + Assert.IsFalse(_writehread.Join(50)); } - protected void Act() + protected override void Act() { _pipeStream.Close(); - // give async write time to complete - _asyncWriteResult.AsyncWaitHandle.WaitOne(100); + // give write time to complete + _writehread.Join(100); } [TestMethod] public void BlockingWriteShouldHaveBeenInterrupted() { - Assert.IsTrue(_asyncWriteResult.IsCompleted); + Assert.AreEqual(ThreadState.Stopped, _writehread.ThreadState); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/Common/PipeStream_Flush_BytesRemainingAfterRead.cs b/src/Renci.SshNet.Tests/Classes/Common/PipeStream_Flush_BytesRemainingAfterRead.cs index b16c908e..3af0a3de 100644 --- a/src/Renci.SshNet.Tests/Classes/Common/PipeStream_Flush_BytesRemainingAfterRead.cs +++ b/src/Renci.SshNet.Tests/Classes/Common/PipeStream_Flush_BytesRemainingAfterRead.cs @@ -1,20 +1,19 @@ -using System; -using System.Threading; +using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Common; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Common { [TestClass] - public class PipeStream_Flush_BytesRemainingAfterRead + public class PipeStream_Flush_BytesRemainingAfterRead : TripleATestBase { private PipeStream _pipeStream; private byte[] _readBuffer; private int _bytesRead; - private IAsyncResult _asyncReadResult; + private Thread _readThread; - [TestInitialize] - public void Init() + protected override void Arrange() { _pipeStream = new PipeStream(); _pipeStream.WriteByte(10); @@ -27,25 +26,25 @@ namespace Renci.SshNet.Tests.Classes.Common _bytesRead = 0; _readBuffer = new byte[4]; - Action readAction = () => _bytesRead = _pipeStream.Read(_readBuffer, 0, _readBuffer.Length); - _asyncReadResult = readAction.BeginInvoke(null, null); - _asyncReadResult.AsyncWaitHandle.WaitOne(50); + _readThread = new Thread(() => _bytesRead = _pipeStream.Read(_readBuffer, 0, _readBuffer.Length)); + _readThread.Start(); - Act(); + // ensure we've started reading + _readThread.Join(50); } - protected void Act() + protected override void Act() { _pipeStream.Flush(); // give async read time to complete - _asyncReadResult.AsyncWaitHandle.WaitOne(100); + _readThread.Join(100); } [TestMethod] public void AsyncReadShouldHaveFinished() { - Assert.IsTrue(_asyncReadResult.IsCompleted); + Assert.AreEqual(ThreadState.Stopped, _readThread.ThreadState); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/Common/PipeStream_Flush_NoBytesRemainingAfterRead.cs b/src/Renci.SshNet.Tests/Classes/Common/PipeStream_Flush_NoBytesRemainingAfterRead.cs index eac2f20b..44c7f3f7 100644 --- a/src/Renci.SshNet.Tests/Classes/Common/PipeStream_Flush_NoBytesRemainingAfterRead.cs +++ b/src/Renci.SshNet.Tests/Classes/Common/PipeStream_Flush_NoBytesRemainingAfterRead.cs @@ -1,19 +1,19 @@ -using System; +using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Common; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Common { [TestClass] - public class PipeStream_Flush_NoBytesRemainingAfterRead + public class PipeStream_Flush_NoBytesRemainingAfterRead : TripleATestBase { private PipeStream _pipeStream; private byte[] _readBuffer; private int _bytesRead; - private IAsyncResult _asyncReadResult; + private Thread _readThread; - [TestInitialize] - public void Init() + protected override void Arrange() { _pipeStream = new PipeStream(); _pipeStream.WriteByte(10); @@ -22,25 +22,25 @@ namespace Renci.SshNet.Tests.Classes.Common _bytesRead = 0; _readBuffer = new byte[4]; - Action readAction = () => _bytesRead = _pipeStream.Read(_readBuffer, 0, _readBuffer.Length); - _asyncReadResult = readAction.BeginInvoke(null, null); - _asyncReadResult.AsyncWaitHandle.WaitOne(50); + _readThread = new Thread(() => _bytesRead = _pipeStream.Read(_readBuffer, 0, _readBuffer.Length)); + _readThread.Start(); - Act(); + // ensure we've started reading + Assert.IsFalse(_readThread.Join(50)); } - protected void Act() + protected override void Act() { _pipeStream.Flush(); // give async read time to complete - _asyncReadResult.AsyncWaitHandle.WaitOne(100); + _readThread.Join(100); } [TestMethod] public void AsyncReadShouldHaveFinished() { - Assert.IsTrue(_asyncReadResult.IsCompleted); + Assert.AreEqual(ThreadState.Stopped, _readThread.ThreadState); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTestBase.cs b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTestBase.cs new file mode 100644 index 00000000..edb14680 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTestBase.cs @@ -0,0 +1,44 @@ +using Moq; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; +using System.Net; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + public abstract class DirectConnectorTestBase : TripleATestBase + { + internal Mock SocketFactoryMock { get; private set; } + internal DirectConnector Connector { get; private set; } + internal SocketFactory SocketFactory { get; private set; } + + protected virtual void CreateMocks() + { + SocketFactoryMock = new Mock(MockBehavior.Strict); + } + + protected virtual void SetupData() + { + Connector = new DirectConnector(SocketFactoryMock.Object); + SocketFactory = new SocketFactory(); + } + + protected virtual void SetupMocks() + { + } + + protected sealed override void Arrange() + { + CreateMocks(); + SetupData(); + SetupMocks(); + } + + protected ConnectionInfo CreateConnectionInfo(string hostName) + { + return new ConnectionInfo(hostName, + 777, + "user", + new KeyboardInteractiveAuthenticationMethod("user")); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_ConnectionRefusedByServer.cs b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_ConnectionRefusedByServer.cs new file mode 100644 index 00000000..40fd93b5 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_ConnectionRefusedByServer.cs @@ -0,0 +1,103 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using System; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class DirectConnectorTest_Connect_ConnectionRefusedByServer : DirectConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private SocketException _actualException; + private Socket _clientSocket; + private Stopwatch _stopWatch; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = CreateConnectionInfo(IPAddress.Loopback.ToString()); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(5000); + _stopWatch = new Stopwatch(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_clientSocket != null) + { + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + _stopWatch.Start(); + + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (SocketException ex) + { + _actualException = ex; + } + finally + { + _stopWatch.Stop(); + } + } + + [TestMethod] + public void ConnectShouldHaveThrownSocketException() + { + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(SocketError.ConnectionRefused, _actualException.SocketErrorCode); + } + + [TestMethod] + public void ConnectShouldHaveRespectedTimeout() + { + var errorText = string.Format("Elapsed: {0}, Timeout: {1}", + _stopWatch.ElapsedMilliseconds, + _connectionInfo.Timeout.TotalMilliseconds); + + Assert.IsTrue(_stopWatch.ElapsedMilliseconds < _connectionInfo.Timeout.TotalMilliseconds, errorText); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_ConnectionSucceeded.cs b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_ConnectionSucceeded.cs new file mode 100644 index 00000000..5741c186 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_ConnectionSucceeded.cs @@ -0,0 +1,118 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Tests.Common; +using System; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class DirectConnectorTest_Connect_ConnectionSucceeded : DirectConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _server; + private Socket _clientSocket; + private Stopwatch _stopWatch; + private bool _disconnected; + private Socket _actual; + + protected override void SetupData() + { + base.SetupData(); + + var random = new Random(); + + _connectionInfo = CreateConnectionInfo(IPAddress.Loopback.ToString()); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200)); + _stopWatch = new Stopwatch(); + _disconnected = false; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _server = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.Port)); + _server.Disconnected += (socket) => _disconnected = true; + _server.Connected += (socket) => socket.Send(new byte[1] { 0x44 }); + _server.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_server != null) + { + _server.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + _stopWatch.Start(); + + try + { + _actual = Connector.Connect(_connectionInfo); + } + finally + { + _stopWatch.Stop(); + } + } + + [TestMethod] + public void ConnectShouldHaveReturnedSocketCreatedUsingSocketFactory() + { + Assert.IsNotNull(_actual); + Assert.AreSame(_clientSocket, _actual); + } + + [TestMethod] + public void ConnectShouldHaveRespectedTimeout() + { + var errorText = string.Format("Elapsed: {0}, Timeout: {1}", + _stopWatch.ElapsedMilliseconds, + _connectionInfo.Timeout.TotalMilliseconds); + + Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText); + } + + [TestMethod] + public void ClientSocketShouldBeConnected() + { + Assert.IsTrue(_clientSocket.Connected); + Assert.IsFalse(_disconnected); + } + + [TestMethod] + public void NoBytesShouldHaveBeenReadFromSocket() + { + var buffer = new byte[1]; + + var bytesRead = _clientSocket.Receive(buffer); + Assert.AreEqual(1, bytesRead); + Assert.AreEqual(0x44, buffer[0]); + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_HostNameInvalid.cs b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_HostNameInvalid.cs new file mode 100644 index 00000000..05a69e94 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_HostNameInvalid.cs @@ -0,0 +1,42 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class DirectConnectorTest_Connect_HostNameInvalid : DirectConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private SocketException _actualException; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = CreateConnectionInfo("invalid."); + _actualException = null; + } + + protected override void Act() + { + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (SocketException ex) + { + _actualException = ex; + } + } + + [TestMethod] + public void ConnectShouldHaveThrownSocketException() + { + Assert.IsNotNull(_actualException); + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(SocketError.HostNotFound, _actualException.SocketErrorCode); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_TimeoutConnectingToServer.cs b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_TimeoutConnectingToServer.cs new file mode 100644 index 00000000..b8e4246a --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/DirectConnectorTest_Connect_TimeoutConnectingToServer.cs @@ -0,0 +1,108 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Tests.Common; +using System; +using System.Diagnostics; +using System.Globalization; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class DirectConnectorTest_Connect_TimeoutConnectingToServer : DirectConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private SshOperationTimeoutException _actualException; + private Socket _clientSocket; + private Stopwatch _stopWatch; + + protected override void SetupData() + { + base.SetupData(); + + var random = new Random(); + + _connectionInfo = CreateConnectionInfo(IPAddress.Loopback.ToString()); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200)); + _stopWatch = new Stopwatch(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_clientSocket != null) + { + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + _stopWatch.Start(); + + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (SshOperationTimeoutException ex) + { + _actualException = ex; + } + finally + { + _stopWatch.Stop(); + } + } + + [TestMethod] + public void ConnectShouldHaveThrownSshOperationTimeoutException() + { + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Connection failed to establish within {0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message); + } + + [TestMethod] + public void ConnectShouldHaveRespectedTimeout() + { + var errorText = string.Format("Elapsed: {0}, Timeout: {1}", + _stopWatch.ElapsedMilliseconds, + _connectionInfo.Timeout.TotalMilliseconds); + + Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText); + Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTestBase.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTestBase.cs new file mode 100644 index 00000000..82cfe0c2 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTestBase.cs @@ -0,0 +1,35 @@ +using Moq; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + public abstract class HttpConnectorTestBase : TripleATestBase + { + internal Mock SocketFactoryMock { get; private set; } + internal HttpConnector Connector { get; private set; } + internal SocketFactory SocketFactory { get; private set; } + + protected virtual void CreateMocks() + { + SocketFactoryMock = new Mock(MockBehavior.Strict); + } + + protected virtual void SetupData() + { + Connector = new HttpConnector(SocketFactoryMock.Object); + SocketFactory = new SocketFactory(); + } + + protected virtual void SetupMocks() + { + } + + protected sealed override void Arrange() + { + CreateMocks(); + SetupData(); + SetupMocks(); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ConnectionToProxyRefused.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ConnectionToProxyRefused.cs new file mode 100644 index 00000000..5c790b4c --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ConnectionToProxyRefused.cs @@ -0,0 +1,111 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using System; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class HttpConnectorTest_Connect_ConnectionToProxyRefused : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private SocketException _actualException; + private Socket _clientSocket; + private Stopwatch _stopWatch; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Http, + IPAddress.Loopback.ToString(), + 8122, + "proxyUser", + "proxyPwd", + new KeyboardInteractiveAuthenticationMethod("user")); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(5000); + _stopWatch = new Stopwatch(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_clientSocket != null) + { + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + _stopWatch.Start(); + + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (SocketException ex) + { + _actualException = ex; + } + finally + { + _stopWatch.Stop(); + } + } + + [TestMethod] + public void ConnectShouldHaveThrownSocketException() + { + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(SocketError.ConnectionRefused, _actualException.SocketErrorCode); + } + + [TestMethod] + public void ConnectShouldHaveRespectedTimeout() + { + var errorText = string.Format("Elapsed: {0}, Timeout: {1}", + _stopWatch.ElapsedMilliseconds, + _connectionInfo.Timeout.TotalMilliseconds); + + Assert.IsTrue(_stopWatch.ElapsedMilliseconds < _connectionInfo.Timeout.TotalMilliseconds, errorText); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyClosesConnectionBeforeStatusLineIsSent.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyClosesConnectionBeforeStatusLineIsSent.cs new file mode 100644 index 00000000..72bdca90 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyClosesConnectionBeforeStatusLineIsSent.cs @@ -0,0 +1,115 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Tests.Common; +using System; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class HttpConnectorTest_Connect_ProxyClosesConnectionBeforeStatusLineIsSent : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private Socket _clientSocket; + private bool _disconnected; + private ProxyException _actualException; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Http, + IPAddress.Loopback.ToString(), + 8122, + "proxyUser", + "proxyPwd", + new KeyboardInteractiveAuthenticationMethod("user")); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += socket => _disconnected = true; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + socket.Shutdown(SocketShutdown.Send); + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (ProxyException ex) + { + _actualException = ex; + } + } + + [TestMethod] + public void ConnectShouldHaveThrownProxyException() + { + Assert.IsNotNull(_actualException); + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual("HTTP response does not contain status line.", _actualException.Message); + } + + [TestMethod] + public void ConnectionToProxyShouldHaveBeenShutDown() + { + Assert.IsTrue(_disconnected); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyHostInvalid.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyHostInvalid.cs new file mode 100644 index 00000000..5843947d --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyHostInvalid.cs @@ -0,0 +1,49 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class HttpConnectorTest_Connect_ProxyHostInvalid : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private SocketException _actualException; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = new ConnectionInfo("localhost", + 40, + "user", + ProxyTypes.Http, + "invalid.", + 80, + "proxyUser", + "proxyPwd", + new KeyboardInteractiveAuthenticationMethod("user")); + _actualException = null; + } + + protected override void Act() + { + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (SocketException ex) + { + _actualException = ex; + } + } + + [TestMethod] + public void ConnectShouldHaveThrownSocketException() + { + Assert.IsNotNull(_actualException); + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(SocketError.HostNotFound, _actualException.SocketErrorCode); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyPasswordIsEmpty.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyPasswordIsEmpty.cs new file mode 100644 index 00000000..c94933b2 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyPasswordIsEmpty.cs @@ -0,0 +1,131 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class HttpConnectorTest_Connect_ProxyPasswordIsEmpty : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private bool _disconnected; + private Socket _clientSocket; + private List _bytesReceivedByProxy; + private string _expectedHttpRequest; + private Socket _actual; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Http, + IPAddress.Loopback.ToString(), + 8122, + "proxyUser", + string.Empty, + new KeyboardInteractiveAuthenticationMethod("user")); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(20); + _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}" + + "Proxy-Authorization: Basic cHJveHlVc2VyOg=={2}{2}", + _connectionInfo.Host, + _connectionInfo.Port.ToString(CultureInfo.InvariantCulture), + "\r\n"); + _bytesReceivedByProxy = new List(); + _disconnected = false; + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += (socket) => _disconnected = true; + _proxyServer.Connected += socket => + { + socket.Send(Encoding.ASCII.GetBytes("\r\n")); + socket.Send(Encoding.ASCII.GetBytes("SSH.NET\r\n")); + socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n")); + socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n")); + socket.Send(Encoding.ASCII.GetBytes("\r\n")); + socket.Send(Encoding.ASCII.GetBytes("SSH4EVER")); + socket.Shutdown(SocketShutdown.Send); + }; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + _bytesReceivedByProxy.AddRange(bytesReceived); + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Shutdown(SocketShutdown.Both); + _clientSocket.Close(); + } + } + + protected override void Act() + { + _actual = Connector.Connect(_connectionInfo); + } + + [TestMethod] + public void ProxyShouldHaveReceivedExpectedHttpRequest() + { + Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray())); + } + + [TestMethod] + public void ConnectShouldReturnSocketCreatedUsingSocketFactory() + { + Assert.IsNotNull(_actual); + Assert.AreSame(_clientSocket, _actual); + } + + [TestMethod] + public void OnlyHttpResponseShouldHaveBeenConsumed() + { + var buffer = new byte[8]; + + Assert.AreEqual(8, _actual.Available); + Assert.AreEqual(8, _actual.Receive(buffer)); + Assert.AreEqual("SSH4EVER", Encoding.ASCII.GetString(buffer)); + Assert.AreEqual(0, _actual.Receive(buffer)); + } + + [TestMethod] + public void ClientSocketShouldBeConnected() + { + Assert.IsFalse(_disconnected); + Assert.IsTrue(_actual.Connected); + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyPasswordIsNull.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyPasswordIsNull.cs new file mode 100644 index 00000000..277a6f29 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyPasswordIsNull.cs @@ -0,0 +1,131 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class HttpConnectorTest_Connect_ProxyPasswordIsNull : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private bool _disconnected; + private Socket _clientSocket; + private List _bytesReceivedByProxy; + private string _expectedHttpRequest; + private Socket _actual; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Http, + IPAddress.Loopback.ToString(), + 8122, + "proxyUser", + null, + new KeyboardInteractiveAuthenticationMethod("user")); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(20); + _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}" + + "Proxy-Authorization: Basic cHJveHlVc2VyOg=={2}{2}", + _connectionInfo.Host, + _connectionInfo.Port.ToString(CultureInfo.InvariantCulture), + "\r\n"); + _bytesReceivedByProxy = new List(); + _disconnected = false; + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += (socket) => _disconnected = true; + _proxyServer.Connected += socket => + { + socket.Send(Encoding.ASCII.GetBytes("\r\n")); + socket.Send(Encoding.ASCII.GetBytes("SSH.NET\r\n")); + socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n")); + socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n")); + socket.Send(Encoding.ASCII.GetBytes("\r\n")); + socket.Send(Encoding.ASCII.GetBytes("SSH4EVER")); + socket.Shutdown(SocketShutdown.Send); + }; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + _bytesReceivedByProxy.AddRange(bytesReceived); + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Shutdown(SocketShutdown.Both); + _clientSocket.Close(); + } + } + + protected override void Act() + { + _actual = Connector.Connect(_connectionInfo); + } + + [TestMethod] + public void ProxyShouldHaveReceivedExpectedHttpRequest() + { + Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray())); + } + + [TestMethod] + public void ConnectShouldReturnSocketCreatedUsingSocketFactory() + { + Assert.IsNotNull(_actual); + Assert.AreSame(_clientSocket, _actual); + } + + [TestMethod] + public void OnlyHttpResponseShouldHaveBeenConsumed() + { + var buffer = new byte[8]; + + Assert.AreEqual(8, _actual.Available); + Assert.AreEqual(8, _actual.Receive(buffer)); + Assert.AreEqual("SSH4EVER", Encoding.ASCII.GetString(buffer)); + Assert.AreEqual(0, _actual.Receive(buffer)); + } + + [TestMethod] + public void ClientSocketShouldBeConnected() + { + Assert.IsFalse(_disconnected); + Assert.IsTrue(_actual.Connected); + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseDoesNotContainHttpStatusLine.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseDoesNotContainHttpStatusLine.cs new file mode 100644 index 00000000..b9f90019 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseDoesNotContainHttpStatusLine.cs @@ -0,0 +1,120 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class HttpConnectorTest_Connect_ProxyResponseDoesNotContainHttpStatusLine : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private Socket _clientSocket; + private List _bytesReceivedByProxy; + private bool _disconnected; + private ProxyException _actualException; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Http, + IPAddress.Loopback.ToString(), + 8122, + "proxyUser", + "proxyPwd", + new KeyboardInteractiveAuthenticationMethod("user")); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100); + _bytesReceivedByProxy = new List(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += socket => _disconnected = true; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + if (_bytesReceivedByProxy.Count == 0) + { + socket.Send(Encoding.ASCII.GetBytes("Whatever\r\n")); + socket.Shutdown(SocketShutdown.Send); + } + + _bytesReceivedByProxy.AddRange(bytesReceived); + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + } + + protected override void Act() + { + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (ProxyException ex) + { + _actualException = ex; + } + } + + [TestMethod] + public void ConnectShouldHaveThrownProxyException() + { + Assert.IsNotNull(_actualException); + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual("HTTP response does not contain status line.", _actualException.Message); + } + + [TestMethod] + public void ConnectionToProxyShouldHaveBeenShutDown() + { + Assert.IsTrue(_disconnected); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_ExtraTextBeforeStatusLine.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_ExtraTextBeforeStatusLine.cs new file mode 100644 index 00000000..70491330 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_ExtraTextBeforeStatusLine.cs @@ -0,0 +1,135 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class HttpConnectorTest_Connect_ProxyResponseStatusIs200_ExtraTextBeforeStatusLine : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private bool _disconnected; + private Socket _clientSocket; + private List _bytesReceivedByProxy; + private string _expectedHttpRequest; + private Socket _actual; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Http, + IPAddress.Loopback.ToString(), + 8122, + "proxyUser", + "proxyPwd", + new KeyboardInteractiveAuthenticationMethod("user")); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(20); + _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}" + + "Proxy-Authorization: Basic cHJveHlVc2VyOnByb3h5UHdk{2}{2}", + _connectionInfo.Host, + _connectionInfo.Port.ToString(CultureInfo.InvariantCulture), + "\r\n"); + _bytesReceivedByProxy = new List(); + _disconnected = false; + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += (socket) => _disconnected = true; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + _bytesReceivedByProxy.AddRange(bytesReceived); + + // Only send response back after we've received the complete CONNECT request + // as we want to make sure HttpConnector is not waiting for any data before + // it sends the CONNECT request + if (_bytesReceivedByProxy.Count == _expectedHttpRequest.Length) + { + socket.Send(Encoding.ASCII.GetBytes("\r\n")); + socket.Send(Encoding.ASCII.GetBytes("SSH.NET\r\n")); + socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n")); + socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n")); + socket.Send(Encoding.ASCII.GetBytes("\r\n")); + socket.Send(Encoding.ASCII.GetBytes("SSH4EVER")); + socket.Shutdown(SocketShutdown.Send); + } + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Shutdown(SocketShutdown.Both); + _clientSocket.Close(); + } + } + + protected override void Act() + { + _actual = Connector.Connect(_connectionInfo); + } + + [TestMethod] + public void ProxyShouldHaveReceivedExpectedHttpRequest() + { + Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray())); + } + + [TestMethod] + public void ConnectShouldReturnSocketCreatedUsingSocketFactory() + { + Assert.IsNotNull(_actual); + Assert.AreSame(_clientSocket, _actual); + } + + [TestMethod] + public void OnlyHttpResponseShouldHaveBeenConsumed() + { + var buffer = new byte[8]; + + Assert.AreEqual(8, _actual.Available); + Assert.AreEqual(8, _actual.Receive(buffer)); + Assert.AreEqual("SSH4EVER", Encoding.ASCII.GetString(buffer)); + Assert.AreEqual(0, _actual.Receive(buffer)); + } + + [TestMethod] + public void ClientSocketShouldBeConnected() + { + Assert.IsFalse(_disconnected); + Assert.IsTrue(_actual.Connected); + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_HeadersAndContent.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_HeadersAndContent.cs new file mode 100644 index 00000000..dc0bba59 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_HeadersAndContent.cs @@ -0,0 +1,135 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class HttpConnectorTest_Connect_ProxyResponseStatusIs200_HeadersAndContent : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private bool _disconnected; + private Socket _clientSocket; + private List _bytesReceivedByProxy; + private string _expectedHttpRequest; + private Socket _actual; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Http, + IPAddress.Loopback.ToString(), + 8122, + "proxyUser", + "proxyPwd", + new KeyboardInteractiveAuthenticationMethod("user")); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(20); + _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}" + + "Proxy-Authorization: Basic cHJveHlVc2VyOnByb3h5UHdk{2}{2}", + _connectionInfo.Host, + _connectionInfo.Port.ToString(CultureInfo.InvariantCulture), + "\r\n"); + _bytesReceivedByProxy = new List(); + _disconnected = false; + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += (socket) => _disconnected = true; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + _bytesReceivedByProxy.AddRange(bytesReceived); + + // Only send response back after we've received the complete CONNECT request + // as we want to make sure HttpConnector is not waiting for any data before + // it sends the CONNECT request + if (_bytesReceivedByProxy.Count == _expectedHttpRequest.Length) + { + socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n")); + socket.Send(Encoding.ASCII.GetBytes("Content-Length: 10\r\n")); + socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n")); + socket.Send(Encoding.ASCII.GetBytes("\r\n")); + socket.Send(Encoding.ASCII.GetBytes("TEEN_BYTES")); + socket.Send(Encoding.ASCII.GetBytes("!666!")); + socket.Shutdown(SocketShutdown.Send); + } + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Shutdown(SocketShutdown.Both); + _clientSocket.Close(); + } + } + + protected override void Act() + { + _actual = Connector.Connect(_connectionInfo); + } + + [TestMethod] + public void ProxyShouldHaveReceivedExpectedHttpRequest() + { + Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray())); + } + + [TestMethod] + public void ConnectShouldReturnSocketCreatedUsingSocketFactory() + { + Assert.IsNotNull(_actual); + Assert.AreSame(_clientSocket, _actual); + } + + [TestMethod] + public void OnlyHttpResponseShouldHaveBeenConsumed() + { + var buffer = new byte[5]; + + Assert.AreEqual(5, _actual.Available); + Assert.AreEqual(5, _actual.Receive(buffer)); + Assert.AreEqual("!666!", Encoding.ASCII.GetString(buffer)); + Assert.AreEqual(0, _actual.Receive(buffer)); + } + + [TestMethod] + public void ClientSocketShouldBeConnected() + { + Assert.IsFalse(_disconnected); + Assert.IsTrue(_actual.Connected); + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_OnlyHeaders.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_OnlyHeaders.cs new file mode 100644 index 00000000..235b4dc0 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIs200_OnlyHeaders.cs @@ -0,0 +1,133 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class HttpConnectorTest_Connect_ProxyResponseStatusIs200_OnlyHeaders : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private bool _disconnected; + private Socket _clientSocket; + private List _bytesReceivedByProxy; + private string _expectedHttpRequest; + private Socket _actual; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Http, + IPAddress.Loopback.ToString(), + 8122, + "proxyUser", + "proxyPwd", + new KeyboardInteractiveAuthenticationMethod("user")); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(20); + _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}" + + "Proxy-Authorization: Basic cHJveHlVc2VyOnByb3h5UHdk{2}{2}", + _connectionInfo.Host, + _connectionInfo.Port.ToString(CultureInfo.InvariantCulture), + "\r\n"); + _bytesReceivedByProxy = new List(); + _disconnected = false; + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += (socket) => _disconnected = true; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + _bytesReceivedByProxy.AddRange(bytesReceived); + + // Only send response back after we've received the complete CONNECT request + // as we want to make sure HttpConnector is not waiting for any data before + // it sends the CONNECT request + if (_bytesReceivedByProxy.Count == _expectedHttpRequest.Length) + { + socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n")); + socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n")); + socket.Send(Encoding.ASCII.GetBytes("\r\n")); + socket.Send(Encoding.ASCII.GetBytes("SSH4EVER")); + socket.Shutdown(SocketShutdown.Send); + } + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Shutdown(SocketShutdown.Both); + _clientSocket.Close(); + } + } + + protected override void Act() + { + _actual = Connector.Connect(_connectionInfo); + } + + [TestMethod] + public void ProxyShouldHaveReceivedExpectedHttpRequest() + { + Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray())); + } + + [TestMethod] + public void ConnectShouldReturnSocketCreatedUsingSocketFactory() + { + Assert.IsNotNull(_actual); + Assert.AreSame(_clientSocket, _actual); + } + + [TestMethod] + public void OnlyHttpResponseShouldHaveBeenConsumed() + { + var buffer = new byte[8]; + + Assert.AreEqual(8, _actual.Available); + Assert.AreEqual(8, _actual.Receive(buffer)); + Assert.AreEqual("SSH4EVER", Encoding.ASCII.GetString(buffer)); + Assert.AreEqual(0, _actual.Receive(buffer)); + } + + [TestMethod] + public void ClientSocketShouldBeConnected() + { + Assert.IsFalse(_disconnected); + Assert.IsTrue(_actual.Connected); + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIsNot200.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIsNot200.cs new file mode 100644 index 00000000..9eb0094b --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyResponseStatusIsNot200.cs @@ -0,0 +1,120 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class HttpConnectorTest_Connect_ProxyResponseStatusIsNot200 : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private Socket _clientSocket; + private List _bytesReceivedByProxy; + private bool _disconnected; + private ProxyException _actualException; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Http, + IPAddress.Loopback.ToString(), + 8122, + "proxyUser", + "proxyPwd", + new KeyboardInteractiveAuthenticationMethod("user")); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100); + _bytesReceivedByProxy = new List(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += (socket) => _disconnected = true; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + if (_bytesReceivedByProxy.Count == 0) + { + socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 404 I searched everywhere, really...\r\n")); + socket.Shutdown(SocketShutdown.Send); + } + + _bytesReceivedByProxy.AddRange(bytesReceived); + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + } + + protected override void Act() + { + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (ProxyException ex) + { + _actualException = ex; + } + } + + [TestMethod] + public void ConnectShouldHaveThrownProxyException() + { + Assert.IsNotNull(_actualException); + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual("HTTP: Status code 404, \"I searched everywhere, really...\"", _actualException.Message); + } + + [TestMethod] + public void ConnectionToProxyShouldHaveBeenShutDown() + { + Assert.IsTrue(_disconnected); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsEmpty.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsEmpty.cs new file mode 100644 index 00000000..33c22253 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsEmpty.cs @@ -0,0 +1,131 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class HttpConnectorTest_Connect_ProxyUserNameIsEmpty : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private bool _disconnected; + private Socket _clientSocket; + private List _bytesReceivedByProxy; + private string _expectedHttpRequest; + private Socket _actual; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Http, + IPAddress.Loopback.ToString(), + 8122, + string.Empty, + "proxyPwd", + new KeyboardInteractiveAuthenticationMethod("user")); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(20); + _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}{2}", + _connectionInfo.Host, + _connectionInfo.Port.ToString(CultureInfo.InvariantCulture), + "\r\n"); + _bytesReceivedByProxy = new List(); + _disconnected = false; + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += (socket) => _disconnected = true; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + _bytesReceivedByProxy.AddRange(bytesReceived); + + // Only send response back after we've received the complete CONNECT request + // as we want to make sure HttpConnector is not waiting for any data before + // it sends the CONNECT request + if (_bytesReceivedByProxy.Count == _expectedHttpRequest.Length) + { + socket.Send(Encoding.ASCII.GetBytes("\r\n")); + socket.Send(Encoding.ASCII.GetBytes("SSH.NET\r\n")); + socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n")); + socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n")); + socket.Send(Encoding.ASCII.GetBytes("\r\n")); + socket.Send(Encoding.ASCII.GetBytes("SSH4EVER")); + } + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Close(); + } + } + + protected override void Act() + { + _actual = Connector.Connect(_connectionInfo); + } + + [TestMethod] + public void ProxyShouldHaveReceivedExpectedHttpRequest() + { + Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray())); + } + + [TestMethod] + public void ConnectShouldReturnSocketCreatedUsingSocketFactory() + { + Assert.IsNotNull(_actual); + Assert.AreSame(_clientSocket, _actual); + } + + [TestMethod] + public void OnlyHttpResponseShouldHaveBeenConsumed() + { + var buffer = new byte[8]; + + Assert.AreEqual(8, _actual.Available); + Assert.AreEqual(8, _actual.Receive(buffer)); + Assert.AreEqual("SSH4EVER", Encoding.ASCII.GetString(buffer)); + } + + [TestMethod] + public void ClientSocketShouldBeConnected() + { + Assert.IsFalse(_disconnected); + Assert.IsTrue(_actual.Connected); + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsNotNullAndNotEmpty.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsNotNullAndNotEmpty.cs new file mode 100644 index 00000000..dad0e74b --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsNotNullAndNotEmpty.cs @@ -0,0 +1,128 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class HttpConnectorTest_Connect_ProxyUserNameIsNotNullAndNotEmpty : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private bool _disconnected; + private Socket _clientSocket; + private List _bytesReceivedByProxy; + private string _expectedHttpRequest; + private Socket _actual; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Http, + IPAddress.Loopback.ToString(), + 8122, + "user", + "pwd", + new KeyboardInteractiveAuthenticationMethod("user")); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(20); + _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}" + + "Proxy-Authorization: Basic dXNlcjpwd2Q={2}{2}", + _connectionInfo.Host, + _connectionInfo.Port.ToString(CultureInfo.InvariantCulture), + "\r\n"); + _bytesReceivedByProxy = new List(); + _disconnected = false; + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Connected += (socket) => + { + socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n")); + socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n")); + socket.Send(Encoding.ASCII.GetBytes("\r\n")); + socket.Send(Encoding.ASCII.GetBytes("SSH4EVER")); + }; + _proxyServer.Disconnected += (socket) => _disconnected = true; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + _bytesReceivedByProxy.AddRange(bytesReceived); + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Shutdown(SocketShutdown.Both); + _clientSocket.Close(); + } + } + + protected override void Act() + { + _actual = Connector.Connect(_connectionInfo); + } + + [TestMethod] + public void ProxyShouldHaveReceivedExpectedHttpRequest() + { + Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray())); + } + + [TestMethod] + public void ConnectShouldReturnSocketCreatedUsingSocketFactory() + { + Assert.IsNotNull(_actual); + Assert.AreSame(_clientSocket, _actual); + } + + [TestMethod] + public void OnlyHttpResponseShouldHaveBeenConsumed() + { + var buffer = new byte[8]; + + Assert.AreEqual(8, _actual.Available); + Assert.AreEqual(8, _actual.Receive(buffer)); + Assert.AreEqual("SSH4EVER", Encoding.ASCII.GetString(buffer)); + } + + [TestMethod] + public void ClientSocketShouldBeConnected() + { + Assert.IsFalse(_disconnected); + Assert.IsTrue(_actual.Connected); + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} + diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsNull.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsNull.cs new file mode 100644 index 00000000..3de1bddb --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_ProxyUserNameIsNull.cs @@ -0,0 +1,132 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class HttpConnectorTest_Connect_ProxyUserNameIsNull : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private bool _disconnected; + private Socket _clientSocket; + private List _bytesReceivedByProxy; + private string _expectedHttpRequest; + private Socket _actual; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Http, + IPAddress.Loopback.ToString(), + 8122, + null, + "proxyPwd", + new KeyboardInteractiveAuthenticationMethod("user")); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(20); + _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}{2}", + _connectionInfo.Host, + _connectionInfo.Port.ToString(CultureInfo.InvariantCulture), + "\r\n"); + _bytesReceivedByProxy = new List(); + _disconnected = false; + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += (socket) => _disconnected = true; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + _bytesReceivedByProxy.AddRange(bytesReceived); + + // Only send response back after we've received the complete CONNECT request + // as we want to make sure HttpConnector is not waiting for any data before + // it sends the CONNECT request + if (_bytesReceivedByProxy.Count == _expectedHttpRequest.Length) + { + socket.Send(Encoding.ASCII.GetBytes("\r\n")); + socket.Send(Encoding.ASCII.GetBytes("SSH.NET\r\n")); + socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n")); + socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n")); + socket.Send(Encoding.ASCII.GetBytes("\r\n")); + socket.Send(Encoding.ASCII.GetBytes("SSH4EVER")); + } + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_clientSocket != null) + { + _clientSocket.Shutdown(SocketShutdown.Send); + _clientSocket.Close(); + } + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + } + + protected override void Act() + { + _actual = Connector.Connect(_connectionInfo); + } + + [TestMethod] + public void ProxyShouldHaveReceivedExpectedHttpRequest() + { + Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray())); + } + + [TestMethod] + public void ConnectShouldReturnSocketCreatedUsingSocketFactory() + { + Assert.IsNotNull(_actual); + Assert.AreSame(_clientSocket, _actual); + } + + [TestMethod] + public void OnlyHttpResponseShouldHaveBeenConsumed() + { + var buffer = new byte[8]; + + Assert.AreEqual(8, _actual.Available); + Assert.AreEqual(8, _actual.Receive(buffer)); + Assert.AreEqual("SSH4EVER", Encoding.ASCII.GetString(buffer)); + } + + [TestMethod] + public void ClientSocketShouldBeConnected() + { + Assert.IsFalse(_disconnected); + Assert.IsTrue(_actual.Connected); + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutConnectingToProxy.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutConnectingToProxy.cs new file mode 100644 index 00000000..1a65fec1 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutConnectingToProxy.cs @@ -0,0 +1,115 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using System; +using System.Diagnostics; +using System.Globalization; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class HttpConnectorTest_Connect_TimeoutConnectingToProxy : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private SshOperationTimeoutException _actualException; + private Socket _clientSocket; + private Stopwatch _stopWatch; + + protected override void SetupData() + { + base.SetupData(); + + var random = new Random(); + + _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Http, + IPAddress.Loopback.ToString(), + 8122, + "proxyUser", + "proxyPwd", + new KeyboardInteractiveAuthenticationMethod("user")); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200)); + _stopWatch = new Stopwatch(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_clientSocket != null) + { + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + _stopWatch.Start(); + + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (SshOperationTimeoutException ex) + { + _actualException = ex; + } + finally + { + _stopWatch.Stop(); + } + } + + [TestMethod] + public void ConnectShouldHaveThrownSshOperationTimeoutException() + { + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Connection failed to establish within {0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message); + } + + [TestMethod] + public void ConnectShouldHaveRespectedTimeout() + { + var errorText = string.Format("Elapsed: {0}, Timeout: {1}", + _stopWatch.ElapsedMilliseconds, + _connectionInfo.Timeout.TotalMilliseconds); + + Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText); + Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingHttpContent.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingHttpContent.cs new file mode 100644 index 00000000..29ea435d --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingHttpContent.cs @@ -0,0 +1,169 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class HttpConnectorTest_Connect_TimeoutReadingHttpContent : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private SshOperationTimeoutException _actualException; + private Socket _clientSocket; + private AsyncSocketListener _proxyServer; + private List _bytesReceivedByProxy; + private string _expectedHttpRequest; + private Stopwatch _stopWatch; + private AsyncSocketListener _server; + private bool _disconnected; + + protected override void SetupData() + { + base.SetupData(); + + var random = new Random(); + + _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Http, + IPAddress.Loopback.ToString(), + 8122, + "proxyUser", + "proxyPwd", + new KeyboardInteractiveAuthenticationMethod("user")); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200)); + _expectedHttpRequest = string.Format("CONNECT {0}:{1} HTTP/1.0{2}" + + "Proxy-Authorization: Basic cHJveHlVc2VyOnByb3h5UHdk{2}{2}", + _connectionInfo.Host, + _connectionInfo.Port.ToString(CultureInfo.InvariantCulture), + "\r\n"); + _bytesReceivedByProxy = new List(); + _stopWatch = new Stopwatch(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += (socket) => _disconnected = true; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + _bytesReceivedByProxy.AddRange(bytesReceived); + + // Force a timeout by sending less content than indicated by Content-Length header + if (_bytesReceivedByProxy.Count == _expectedHttpRequest.Length) + { + socket.Send(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n")); + socket.Send(Encoding.ASCII.GetBytes("Content-Length: 10\r\n")); + socket.Send(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n")); + socket.Send(Encoding.ASCII.GetBytes("\r\n")); + socket.Send(Encoding.ASCII.GetBytes("TOO_FEW")); + } + }; + _proxyServer.Start(); + + _server = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.Port)); + _server.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_server != null) + { + _server.Dispose(); + } + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + } + + protected override void Act() + { + _stopWatch.Start(); + + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (SshOperationTimeoutException ex) + { + _actualException = ex; + } + finally + { + _stopWatch.Stop(); + } + } + + [TestMethod] + public void ConnectShouldHaveThrownSshOperationTimeoutException() + { + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Socket read operation has timed out after {0:F0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message); + } + + [TestMethod] + public void ProxyShouldHaveReceivedExpectedHttpRequest() + { + Assert.AreEqual(_expectedHttpRequest, Encoding.ASCII.GetString(_bytesReceivedByProxy.ToArray())); + } + + [TestMethod] + public void ConnectShouldHaveRespectedTimeout() + { + var errorText = string.Format("Elapsed: {0}, Timeout: {1}", + _stopWatch.ElapsedMilliseconds, + _connectionInfo.Timeout.TotalMilliseconds); + + Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText); + Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText); + } + + [TestMethod] + public void ClientSocketShouldNotBeConnected() + { + Assert.IsTrue(_disconnected); + Assert.IsFalse(_clientSocket.Connected); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingStatusLine.cs b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingStatusLine.cs new file mode 100644 index 00000000..254262ba --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/HttpConnectorTest_Connect_TimeoutReadingStatusLine.cs @@ -0,0 +1,139 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; +using System; +using System.Diagnostics; +using System.Globalization; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class HttpConnectorTest_Connect_TimeoutReadingStatusLine : HttpConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private SshOperationTimeoutException _actualException; + private Socket _clientSocket; + private AsyncSocketListener _proxyServer; + private Stopwatch _stopWatch; + private AsyncSocketListener _server; + private bool _disconnected; + + protected override void SetupData() + { + base.SetupData(); + + var random = new Random(); + + _connectionInfo = new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Http, + IPAddress.Loopback.ToString(), + 8122, + "proxyUser", + "proxyPwd", + new KeyboardInteractiveAuthenticationMethod("user")); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200)); + _stopWatch = new Stopwatch(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += (socket) => _disconnected = true; + _proxyServer.Start(); + + _server = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.Port)); + _server.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_server != null) + { + _server.Dispose(); + } + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + } + + protected override void Act() + { + _stopWatch.Start(); + + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (SshOperationTimeoutException ex) + { + _actualException = ex; + } + finally + { + _stopWatch.Stop(); + } + } + + [TestMethod] + public void ConnectShouldHaveThrownSshOperationTimeoutException() + { + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Socket read operation has timed out after {0:F0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message); + } + + [TestMethod] + public void ConnectShouldHaveRespectedTimeout() + { + var errorText = string.Format("Elapsed: {0}, Timeout: {1}", + _stopWatch.ElapsedMilliseconds, + _connectionInfo.Timeout.TotalMilliseconds); + + Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText); + Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText); + } + + [TestMethod] + public void ClientSocketShouldNotBeConnected() + { + Assert.IsTrue(_disconnected); + Assert.IsFalse(_clientSocket.Connected); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ConnectionClosedByServer_NoDataSentByServer.cs b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ConnectionClosedByServer_NoDataSentByServer.cs new file mode 100644 index 00000000..c01aacc1 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ConnectionClosedByServer_NoDataSentByServer.cs @@ -0,0 +1,122 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Renci.SshNet.Common; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class ProtocolVersionExchangeTest_ConnectionClosedByServer_NoDataSentByServer + { + private AsyncSocketListener _server; + private ProtocolVersionExchange _protocolVersionExchange; + private string _clientVersion; + private TimeSpan _timeout; + private IPEndPoint _serverEndPoint; + private List _dataReceivedByServer; + private bool _clientDisconnected; + private Socket _client; + private SshConnectionException _actualException; + + [TestInitialize] + public void Setup() + { + Arrange(); + Act(); + } + + [TestCleanup] + public void Cleanup() + { + if (_server != null) + { + _server.Dispose(); + _server = null; + } + + if (_client != null) + { + _client.Shutdown(SocketShutdown.Both); + _client.Close(); + _client = null; + } + } + + protected void Arrange() + { + _clientVersion = "\uD55C"; + _timeout = TimeSpan.FromSeconds(5); + _serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); + _dataReceivedByServer = new List(); + + _server = new AsyncSocketListener(_serverEndPoint); + _server.Start(); + _server.BytesReceived += (bytes, socket) => + { + _dataReceivedByServer.AddRange(bytes); + socket.Shutdown(SocketShutdown.Send); + }; + _server.Disconnected += (socket) => _clientDisconnected = true; + + _client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _client.Connect(_serverEndPoint); + + _protocolVersionExchange = new ProtocolVersionExchange(); + } + + protected void Act() + { + try + { + _protocolVersionExchange.Start(_clientVersion, _client, _timeout); + Assert.Fail(); + } + catch (SshConnectionException ex) + { + _actualException = ex; + } + } + + [TestMethod] + public void StartShouldHaveThrownSshConnectionException() + { + Assert.IsNotNull(_actualException); + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(string.Format("The server response does not contain an SSH identification string.{0}" + + "The connection to the remote server was closed before any data was received.{0}{0}" + + "More information on the Protocol Version Exchange is available here:{0}" + + "https://tools.ietf.org/html/rfc4253#section-4.2", + Environment.NewLine), + _actualException.Message); + } + + [TestMethod] + public void ClientIdentificationWasSentToServer() + { + Assert.AreEqual(5, _dataReceivedByServer.Count); + + Assert.AreEqual(0xed, _dataReceivedByServer[0]); + Assert.AreEqual(0x95, _dataReceivedByServer[1]); + Assert.AreEqual(0x9c, _dataReceivedByServer[2]); + Assert.AreEqual(0x0d, _dataReceivedByServer[3]); + Assert.AreEqual(0x0a, _dataReceivedByServer[4]); + } + + [TestMethod] + public void ConnectionIsClosedByServer() + { + Assert.IsTrue(_client.Connected); + Assert.IsFalse(_clientDisconnected); + + var bytesReceived = _client.Receive(new byte[1]); + Assert.AreEqual(0, bytesReceived); + + Assert.IsTrue(_client.Connected); + Assert.IsFalse(_clientDisconnected); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseContainsNullCharacter.cs b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseContainsNullCharacter.cs new file mode 100644 index 00000000..29792888 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseContainsNullCharacter.cs @@ -0,0 +1,132 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Renci.SshNet.Common; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class ProtocolVersionExchangeTest_ServerResponseContainsNullCharacter + { + private AsyncSocketListener _server; + private ProtocolVersionExchange _protocolVersionExchange; + private string _clientVersion; + private TimeSpan _timeout; + private IPEndPoint _serverEndPoint; + private List _dataReceivedByServer; + private byte[] _serverIdentification; + private bool _clientDisconnected; + private Socket _client; + private SshConnectionException _actualException; + + [TestInitialize] + public void Setup() + { + Arrange(); + Act(); + } + + [TestCleanup] + public void Cleanup() + { + if (_server != null) + { + _server.Dispose(); + _server = null; + } + + if (_client != null) + { + _client.Shutdown(SocketShutdown.Both); + _client.Close(); + _client = null; + } + } + + protected void Arrange() + { + _clientVersion = "SSH-2.0-Renci.SshNet.SshClient.0.0.1"; + _timeout = TimeSpan.FromSeconds(5); + _serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); + _dataReceivedByServer = new List(); + _serverIdentification = Encoding.UTF8.GetBytes("\uD55C!\0\uD55CSSH -2.0-Renci.SshNet.SshClient.0.0.1"); + + _server = new AsyncSocketListener(_serverEndPoint); + _server.Start(); + _server.Connected += socket => socket.Send(_serverIdentification); + _server.BytesReceived += (bytes, socket) => + { + _dataReceivedByServer.AddRange(bytes); + }; + _server.Disconnected += (socket) => _clientDisconnected = true; + + _client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _client.Connect(_serverEndPoint); + + _protocolVersionExchange = new ProtocolVersionExchange(); + } + + protected void Act() + { + try + { + _protocolVersionExchange.Start(_clientVersion, _client, _timeout); + Assert.Fail(); + } + catch (SshConnectionException ex) + { + _actualException = ex; + } + } + + [TestMethod] + public void StartShouldHaveThrownSshConnectionException() + { + var expectedMessage = string.Format("The server response contains a null character at position 0x00000005:{0}{0}" + + " 00000000 ED 95 9C 21 00 ...!.{0}{0}" + + "A server must not send a null character before the Protocol Version Exchange is complete.{0}{0}" + + "More information is available here:{0}" + + "https://tools.ietf.org/html/rfc4253#section-4.2", + Environment.NewLine); + + Assert.IsNotNull(_actualException); + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(expectedMessage, _actualException.Message); + } + + [TestMethod] + public void ClientIdentificationWasSentToServer() + { + var expected = Encoding.UTF8.GetBytes(_clientVersion); + + Assert.AreEqual(expected.Length + 2, _dataReceivedByServer.Count); + + Assert.IsTrue(expected.SequenceEqual(_dataReceivedByServer.Take(expected.Length))); + Assert.AreEqual(Session.CarriageReturn, _dataReceivedByServer[_dataReceivedByServer.Count - 2]); + Assert.AreEqual(Session.LineFeed, _dataReceivedByServer[_dataReceivedByServer.Count - 1]); + } + + [TestMethod] + public void ClientRemainsConnected() + { + Assert.IsTrue(_client.Connected); + Assert.IsFalse(_clientDisconnected); + } + + [TestMethod] + public void ClientHasNotReadPastNullCharacter() + { + var buffer = new byte[1]; + + var bytesReceived = _client.Receive(buffer); + Assert.AreEqual(1, bytesReceived); + Assert.AreEqual(0xed, buffer[0]); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseInvalid_SshIdentificationOnlyContainsProtocolVersion.cs b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseInvalid_SshIdentificationOnlyContainsProtocolVersion.cs new file mode 100644 index 00000000..ff2bc5e5 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseInvalid_SshIdentificationOnlyContainsProtocolVersion.cs @@ -0,0 +1,128 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Renci.SshNet.Common; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class ProtocolVersionExchangeTest_ServerResponseInvalid_SshIdentificationOnlyContainsProtocolVersion + { + private AsyncSocketListener _server; + private ProtocolVersionExchange _protocolVersionExchange; + private string _clientVersion; + private TimeSpan _timeout; + private IPEndPoint _serverEndPoint; + private List _dataReceivedByServer; + private byte[] _serverIdentification; + private bool _clientDisconnected; + private Socket _client; + private SshConnectionException _actualException; + + [TestInitialize] + public void Setup() + { + Arrange(); + Act(); + } + + [TestCleanup] + public void Cleanup() + { + if (_server != null) + { + _server.Dispose(); + _server = null; + } + + if (_client != null) + { + _client.Shutdown(SocketShutdown.Both); + _client.Close(); + _client = null; + } + } + + protected void Arrange() + { + _clientVersion = "SSH-2.0-Renci.SshNet.SshClient.0.0.1"; + _timeout = TimeSpan.FromSeconds(5); + _serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); + _dataReceivedByServer = new List(); + _serverIdentification = Encoding.UTF8.GetBytes("SSH-2.0\r\n"); + + _server = new AsyncSocketListener(_serverEndPoint); + _server.Start(); + _server.BytesReceived += (bytes, socket) => + { + _dataReceivedByServer.AddRange(bytes); + socket.Send(_serverIdentification); + socket.Shutdown(SocketShutdown.Send); + }; + _server.Disconnected += (socket) => _clientDisconnected = true; + + _client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _client.Connect(_serverEndPoint); + + _protocolVersionExchange = new ProtocolVersionExchange(); + } + + protected void Act() + { + try + { + _protocolVersionExchange.Start(_clientVersion, _client, _timeout); + Assert.Fail(); + } + catch (SshConnectionException ex) + { + _actualException = ex; + } + } + + [TestMethod] + public void StartShouldHaveThrownSshConnectionException() + { + var expectedMessage = string.Format("The server response does not contain an SSH identification string:{0}{0}" + + " 00000000 53 53 48 2D 32 2E 30 0D 0A SSH-2.0..{0}{0}" + + "More information on the Protocol Version Exchange is available here:{0}" + + "https://tools.ietf.org/html/rfc4253#section-4.2", + Environment.NewLine); + + Assert.IsNotNull(_actualException); + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(expectedMessage, _actualException.Message); + } + + [TestMethod] + public void ClientIdentificationWasSentToServer() + { + var expected = Encoding.UTF8.GetBytes(_clientVersion); + + Assert.AreEqual(expected.Length + 2, _dataReceivedByServer.Count); + + Assert.IsTrue(expected.SequenceEqual(_dataReceivedByServer.Take(expected.Length))); + Assert.AreEqual(Session.CarriageReturn, _dataReceivedByServer[_dataReceivedByServer.Count - 2]); + Assert.AreEqual(Session.LineFeed, _dataReceivedByServer[_dataReceivedByServer.Count - 1]); + } + + [TestMethod] + public void ConnectionIsClosedByServer() + { + Assert.IsTrue(_client.Connected); + Assert.IsFalse(_clientDisconnected); + + var bytesReceived = _client.Receive(new byte[1]); + Assert.AreEqual(0, bytesReceived); + + Assert.IsTrue(_client.Connected); + Assert.IsFalse(_clientDisconnected); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_Comments.cs b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_Comments.cs new file mode 100644 index 00000000..df533ebe --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_Comments.cs @@ -0,0 +1,119 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Renci.SshNet.Common; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class ProtocolVersionExchangeTest_ServerResponseValid_Comments + { + private AsyncSocketListener _server; + private ProtocolVersionExchange _protocolVersionExchange; + private string _clientVersion; + private TimeSpan _timeout; + private IPEndPoint _serverEndPoint; + private List _dataReceivedByServer; + private byte[] _serverIdentification; + private bool _clientDisconnected; + private Socket _client; + private SshIdentification _actual; + + [TestInitialize] + public void Setup() + { + Arrange(); + Act(); + } + + [TestCleanup] + public void Cleanup() + { + if (_server != null) + { + _server.Dispose(); + _server = null; + } + + if (_client != null) + { + _client.Shutdown(SocketShutdown.Both); + _client.Close(); + _client = null; + } + } + + protected void Arrange() + { + _clientVersion = "SSH-2.0-Renci.SshNet.SshClient.0.0.1"; + _timeout = TimeSpan.FromSeconds(5); + _serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); + _dataReceivedByServer = new List(); + _serverIdentification = Encoding.UTF8.GetBytes("\r\nWelcome stranger!\r\n\r\nSSH-ABC2.0-OurSSHAppliance-1.4.7 Use at own risk.\uD55C\r\n!"); + + _server = new AsyncSocketListener(_serverEndPoint); + _server.Start(); + _server.BytesReceived += (bytes, socket) => + { + _dataReceivedByServer.AddRange(bytes); + socket.Send(_serverIdentification); + socket.Shutdown(SocketShutdown.Send); + }; + _server.Disconnected += (socket) => _clientDisconnected = true; + + _client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _client.Connect(_serverEndPoint); + + _protocolVersionExchange = new ProtocolVersionExchange(); + } + + protected void Act() + { + _actual = _protocolVersionExchange.Start(_clientVersion, _client, _timeout); + } + + [TestMethod] + public void StartShouldReturnIdentificationOfServer() + { + Assert.IsNotNull(_actual); + Assert.AreEqual("ABC2.0", _actual.ProtocolVersion); + Assert.AreEqual("OurSSHAppliance-1.4.7", _actual.SoftwareVersion); + Assert.AreEqual("Use at own risk.\uD55C", _actual.Comments); + } + + [TestMethod] + public void ClientIdentificationWasSentToServer() + { + var expected = Encoding.UTF8.GetBytes(_clientVersion); + + Assert.AreEqual(expected.Length + 2, _dataReceivedByServer.Count); + + Assert.IsTrue(expected.SequenceEqual(_dataReceivedByServer.Take(expected.Length))); + Assert.AreEqual(Session.CarriageReturn, _dataReceivedByServer[_dataReceivedByServer.Count - 2]); + Assert.AreEqual(Session.LineFeed, _dataReceivedByServer[_dataReceivedByServer.Count - 1]); + } + + [TestMethod] + public void ClientRemainsConnected() + { + Assert.IsTrue(_client.Connected); + Assert.IsFalse(_clientDisconnected); + } + + [TestMethod] + public void ClientDidNotReadPastIdentification() + { + var buffer = new byte[1]; + + var bytesReceived = _client.Receive(buffer); + Assert.AreEqual(1, bytesReceived); + Assert.AreEqual(0x21, buffer[0]); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_NoComments.cs b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_NoComments.cs new file mode 100644 index 00000000..8b4f5997 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_ServerResponseValid_NoComments.cs @@ -0,0 +1,119 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Renci.SshNet.Common; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class ProtocolVersionExchangeTest_ServerResponseValid_NoComments + { + private AsyncSocketListener _server; + private ProtocolVersionExchange _protocolVersionExchange; + private string _clientVersion; + private TimeSpan _timeout; + private IPEndPoint _serverEndPoint; + private List _dataReceivedByServer; + private byte[] _serverIdentification; + private bool _clientDisconnected; + private Socket _client; + private SshIdentification _actual; + + [TestInitialize] + public void Setup() + { + Arrange(); + Act(); + } + + [TestCleanup] + public void Cleanup() + { + if (_server != null) + { + _server.Dispose(); + _server = null; + } + + if (_client != null) + { + _client.Shutdown(SocketShutdown.Both); + _client.Close(); + _client = null; + } + } + + protected void Arrange() + { + _clientVersion = "SSH-2.0-Renci.SshNet.SshClient.0.0.1"; + _timeout = TimeSpan.FromSeconds(5); + _serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); + _dataReceivedByServer = new List(); + _serverIdentification = Encoding.UTF8.GetBytes("Welcome stranger!\r\n\r\nSSH-Zero-OurSSHAppliance\r\n!"); + + _server = new AsyncSocketListener(_serverEndPoint); + _server.Start(); + _server.BytesReceived += (bytes, socket) => + { + _dataReceivedByServer.AddRange(bytes); + socket.Send(_serverIdentification); + socket.Shutdown(SocketShutdown.Send); + }; + _server.Disconnected += (socket) => _clientDisconnected = true; + + _client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _client.Connect(_serverEndPoint); + + _protocolVersionExchange = new ProtocolVersionExchange(); + } + + protected void Act() + { + _actual = _protocolVersionExchange.Start(_clientVersion, _client, _timeout); + } + + [TestMethod] + public void StartShouldReturnIdentificationOfServer() + { + Assert.IsNotNull(_actual); + Assert.AreEqual("Zero", _actual.ProtocolVersion); + Assert.AreEqual("OurSSHAppliance", _actual.SoftwareVersion); + Assert.IsNull(_actual.Comments); + } + + [TestMethod] + public void ClientIdentificationWasSentToServer() + { + var expected = Encoding.UTF8.GetBytes(_clientVersion); + + Assert.AreEqual(expected.Length + 2, _dataReceivedByServer.Count); + + Assert.IsTrue(expected.SequenceEqual(_dataReceivedByServer.Take(expected.Length))); + Assert.AreEqual(Session.CarriageReturn, _dataReceivedByServer[_dataReceivedByServer.Count - 2]); + Assert.AreEqual(Session.LineFeed, _dataReceivedByServer[_dataReceivedByServer.Count - 1]); + } + + [TestMethod] + public void ClientRemainsConnected() + { + Assert.IsTrue(_client.Connected); + Assert.IsFalse(_clientDisconnected); + } + + [TestMethod] + public void ClientDidNotReadPastIdentification() + { + var buffer = new byte[1]; + + var bytesReceived = _client.Receive(buffer); + Assert.AreEqual(1, bytesReceived); + Assert.AreEqual(0x21, buffer[0]); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_TimeoutReadingIdentificationString.cs b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_TimeoutReadingIdentificationString.cs new file mode 100644 index 00000000..6ef1668f --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/ProtocolVersionExchangeTest_TimeoutReadingIdentificationString.cs @@ -0,0 +1,113 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Renci.SshNet.Common; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class ProtocolVersionExchangeTest_TimeoutReadingIdentificationString + { + private AsyncSocketListener _server; + private ProtocolVersionExchange _protocolVersionExchange; + private string _clientVersion; + private TimeSpan _timeout; + private IPEndPoint _serverEndPoint; + private List _dataReceivedByServer; + private bool _clientDisconnected; + private Socket _client; + private SshOperationTimeoutException _actualException; + + [TestInitialize] + public void Setup() + { + Arrange(); + Act(); + } + + [TestCleanup] + public void Cleanup() + { + if (_server != null) + { + _server.Dispose(); + _server = null; + } + + if (_client != null) + { + _client.Close(); + _client = null; + } + } + + protected void Arrange() + { + _clientVersion = "SSH-2.0-Renci.SshNet.SshClient.0.0.1"; + _timeout = TimeSpan.FromMilliseconds(200); + _serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); + _dataReceivedByServer = new List(); + _clientDisconnected = false; + + _server = new AsyncSocketListener(_serverEndPoint); + _server.Start(); + _server.BytesReceived += (bytes, socket) => + { + _dataReceivedByServer.AddRange(bytes); + socket.Send(Encoding.UTF8.GetBytes("Welcome!\r\n")); + }; + _server.Disconnected += (socket) => _clientDisconnected = true; + + _client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _client.Connect(_serverEndPoint); + + _protocolVersionExchange = new ProtocolVersionExchange(); + } + + protected void Act() + { + try + { + _protocolVersionExchange.Start(_clientVersion, _client, _timeout); + Assert.Fail(); + } + catch (SshOperationTimeoutException ex) + { + _actualException = ex; + } + } + + [TestMethod] + public void StartShouldHaveThrownSshOperationTimeoutException() + { + Assert.IsNotNull(_actualException); + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(string.Format("Socket read operation has timed out after {0} milliseconds.", _timeout.TotalMilliseconds), _actualException.Message); + } + + [TestMethod] + public void ClientIdentificationWasSentToServer() + { + var expected = Encoding.UTF8.GetBytes(_clientVersion); + + Assert.AreEqual(expected.Length + 2, _dataReceivedByServer.Count); + + Assert.IsTrue(expected.SequenceEqual(_dataReceivedByServer.Take(expected.Length))); + Assert.AreEqual(Session.CarriageReturn, _dataReceivedByServer[_dataReceivedByServer.Count - 2]); + Assert.AreEqual(Session.LineFeed, _dataReceivedByServer[_dataReceivedByServer.Count - 1]); + } + + [TestMethod] + public void ClientSocketShouldBeConnected() + { + Assert.IsTrue(_client.Connected); + Assert.IsFalse(_clientDisconnected); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTestBase.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTestBase.cs new file mode 100644 index 00000000..50f62256 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTestBase.cs @@ -0,0 +1,49 @@ +using Moq; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; +using System.Net; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + public abstract class Socks4ConnectorTestBase : TripleATestBase + { + internal Mock SocketFactoryMock { get; private set; } + internal Socks4Connector Connector { get; private set; } + internal SocketFactory SocketFactory { get; private set; } + + protected virtual void CreateMocks() + { + SocketFactoryMock = new Mock(MockBehavior.Strict); + } + + protected virtual void SetupData() + { + Connector = new Socks4Connector(SocketFactoryMock.Object); + SocketFactory = new SocketFactory(); + } + + protected virtual void SetupMocks() + { + } + + protected sealed override void Arrange() + { + CreateMocks(); + SetupData(); + SetupMocks(); + } + + protected ConnectionInfo CreateConnectionInfo(string proxyUser, string proxyPassword) + { + return new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Socks4, + IPAddress.Loopback.ToString(), + 8122, + proxyUser, + proxyPassword, + new KeyboardInteractiveAuthenticationMethod("user")); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionRejectedByProxy.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionRejectedByProxy.cs new file mode 100644 index 00000000..3ef993f2 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionRejectedByProxy.cs @@ -0,0 +1,134 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class Socks4ConnectorTest_Connect_ConnectionRejectedByProxy : Socks4ConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private Socket _clientSocket; + private List _bytesReceivedByProxy; + private bool _disconnected; + private ProxyException _actualException; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd"); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100); + _bytesReceivedByProxy = new List(); + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _actualException = null; + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += socket => _disconnected = true; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + if (_bytesReceivedByProxy.Count == 0) + { + socket.Send(new byte[] + { + // Reply version (null byte) + 0x00, + // Connection refused + 0x5b + }); + } + + _bytesReceivedByProxy.AddRange(bytesReceived); + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (ProxyException ex) + { + _actualException = ex; + } + } + + [TestMethod] + public void ConnectShouldHaveThrownProxyException() + { + Assert.IsNotNull(_actualException); + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual("SOCKS4: Connection rejected.", _actualException.Message); + } + + [TestMethod] + public void ConnectionToProxyShouldHaveBeenShutDown() + { + Assert.IsTrue(_disconnected); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + + private static byte GetNotSupportedSocksVersion() + { + var random = new Random(); + + while (true) + { + var socksVersion = random.Next(1, 255); + if (socksVersion != 4) + { + return (byte) socksVersion; + } + } + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionSucceeded.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionSucceeded.cs new file mode 100644 index 00000000..9f184942 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionSucceeded.cs @@ -0,0 +1,166 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class Socks4ConnectorTest_Connect_ConnectionSucceeded : Socks4ConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private Socket _clientSocket; + private AsyncSocketListener _proxyServer; + private List _bytesReceivedByProxy; + private bool _disconnected; + private Socket _actual; + + protected override void SetupData() + { + base.SetupData(); + + var random = new Random(); + + _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd"); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200)); + _bytesReceivedByProxy = new List(); + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _actual = null; + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += socket => _disconnected = true; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + _bytesReceivedByProxy.AddRange(bytesReceived); + + if (_bytesReceivedByProxy.Count == bytesReceived.Length) + { + // Send SOCKS response + socket.Send(new byte[] + { + // Reply version (null byte) + 0x00, + // Request granted + 0x5a, + // Destination address port + 0x01, + 0xf0, + // Destination address IP + 0x01, + 0x02, + 0x03, + 0x04 + }); + + // Send extra byte to allow us to verify that connector did not consume too much + socket.Send(new byte[] + { + 0xfe + }); + } + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + _actual = Connector.Connect(_connectionInfo); + } + + [TestMethod] + public void ProxyShouldHaveReceivedExpectedSocksRequest() + { + var expectedSocksRequest = new byte[] + { + // SOCKS version + 0x04, + // CONNECT request + 0x01, + // Destination port + 0x03, + 0x09, + // Destination address (IPv4) + 0x7f, + 0x00, + 0x00, + 0x01, + // Proxy user + 0x70, + 0x72, + 0x6f, + 0x78, + 0x79, + 0x55, + 0x73, + 0x65, + 0x72, + // Null terminator + 0x00 + }; + + var errorText = string.Format("Expected:{0}{1}{0}but was:{0}{2}", + Environment.NewLine, + PacketDump.Create(expectedSocksRequest, 2), + PacketDump.Create(_bytesReceivedByProxy, 2)); + + Assert.IsTrue(expectedSocksRequest.SequenceEqual(_bytesReceivedByProxy), errorText); + } + + [TestMethod] + public void ConnectShouldReturnSocketCreatedUsingSocketFactory() + { + Assert.IsNotNull(_actual); + Assert.AreSame(_clientSocket, _actual); + } + + [TestMethod] + public void OnlySocksResponseShouldHaveBeenConsumed() + { + var buffer = new byte[2]; + + Assert.AreEqual(1, _actual.Receive(buffer)); + Assert.AreEqual(0xfe, buffer[0]); + } + + [TestMethod] + public void ClientSocketShouldBeConnected() + { + Assert.IsFalse(_disconnected); + Assert.IsTrue(_actual.Connected); + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionToProxyRefused.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionToProxyRefused.cs new file mode 100644 index 00000000..85f2f669 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_ConnectionToProxyRefused.cs @@ -0,0 +1,102 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using System; +using System.Diagnostics; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class Socks4ConnectorTest_Connect_ConnectionToProxyRefused : Socks4ConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private SocketException _actualException; + private Socket _clientSocket; + private Stopwatch _stopWatch; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd"); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(5000); + _stopWatch = new Stopwatch(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_clientSocket != null) + { + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + _stopWatch.Start(); + + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (SocketException ex) + { + _actualException = ex; + } + finally + { + _stopWatch.Stop(); + } + } + + [TestMethod] + public void ConnectShouldHaveThrownSocketException() + { + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(SocketError.ConnectionRefused, _actualException.SocketErrorCode); + } + + [TestMethod] + public void ConnectShouldHaveRespectedTimeout() + { + var errorText = string.Format("Elapsed: {0}, Timeout: {1}", + _stopWatch.ElapsedMilliseconds, + _connectionInfo.Timeout.TotalMilliseconds); + + Assert.IsTrue(_stopWatch.ElapsedMilliseconds < _connectionInfo.Timeout.TotalMilliseconds, errorText); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutConnectingToProxy.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutConnectingToProxy.cs new file mode 100644 index 00000000..4ce1560b --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutConnectingToProxy.cs @@ -0,0 +1,105 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using System; +using System.Diagnostics; +using System.Globalization; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class Socks4ConnectorTest_Connect_TimeoutConnectingToProxy : Socks4ConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private SshOperationTimeoutException _actualException; + private Socket _clientSocket; + private Stopwatch _stopWatch; + + protected override void SetupData() + { + base.SetupData(); + + var random = new Random(); + + _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd"); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200)); + _stopWatch = new Stopwatch(); + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + _actualException = null; + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_clientSocket != null) + { + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + _stopWatch.Start(); + + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (SshOperationTimeoutException ex) + { + _actualException = ex; + } + finally + { + _stopWatch.Stop(); + } + } + + [TestMethod] + public void ConnectShouldHaveThrownSshOperationTimeoutException() + { + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Connection failed to establish within {0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message); + } + + [TestMethod] + public void ConnectShouldHaveRespectedTimeout() + { + var errorText = string.Format("Elapsed: {0}, Timeout: {1}", + _stopWatch.ElapsedMilliseconds, + _connectionInfo.Timeout.TotalMilliseconds); + + Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText); + Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingDestinationAddress.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingDestinationAddress.cs new file mode 100644 index 00000000..92840220 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingDestinationAddress.cs @@ -0,0 +1,143 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; +using System; +using System.Diagnostics; +using System.Globalization; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class Socks4ConnectorTest_Connect_TimeoutReadingDestinationAddress : Socks4ConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private SshOperationTimeoutException _actualException; + private Socket _clientSocket; + private AsyncSocketListener _proxyServer; + private Stopwatch _stopWatch; + private AsyncSocketListener _server; + private bool _disconnected; + + protected override void SetupData() + { + base.SetupData(); + + var random = new Random(); + + _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd"); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200)); + _stopWatch = new Stopwatch(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += socket => _disconnected = true; + _proxyServer.Connected += socket => + { + socket.Send(new byte[] + { + // Reply version (null byte) + 0x00, + // Request granted + 0x5a, + // Incomplete destination address + 0x01 + }); + }; + _proxyServer.Start(); + + _server = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.Port)); + _server.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_server != null) + { + _server.Dispose(); + } + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + } + + protected override void Act() + { + _stopWatch.Start(); + + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (SshOperationTimeoutException ex) + { + _actualException = ex; + } + finally + { + _stopWatch.Stop(); + } + } + + [TestMethod] + public void ConnectShouldHaveThrownSshOperationTimeoutException() + { + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Socket read operation has timed out after {0:F0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message); + } + + [TestMethod] + public void ConnectShouldHaveRespectedTimeout() + { + var errorText = string.Format("Elapsed: {0}, Timeout: {1}", + _stopWatch.ElapsedMilliseconds, + _connectionInfo.Timeout.TotalMilliseconds); + + Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText); + Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText); + } + + [TestMethod] + public void ClientSocketShouldNotBeConnected() + { + Assert.IsTrue(_disconnected); + Assert.IsFalse(_clientSocket.Connected); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyCode.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyCode.cs new file mode 100644 index 00000000..bbc724ac --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyCode.cs @@ -0,0 +1,139 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; +using System; +using System.Diagnostics; +using System.Globalization; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class Socks4ConnectorTest_Connect_TimeoutReadingReplyCode : Socks4ConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private SshOperationTimeoutException _actualException; + private Socket _clientSocket; + private AsyncSocketListener _proxyServer; + private Stopwatch _stopWatch; + private AsyncSocketListener _server; + private bool _disconnected; + + protected override void SetupData() + { + base.SetupData(); + + var random = new Random(); + + _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd"); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200)); + _stopWatch = new Stopwatch(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += socket => _disconnected = true; + _proxyServer.Connected += socket => + { + socket.Send(new byte[] + { + // Reply version (null byte) + 0x00 + }); + }; + _proxyServer.Start(); + + _server = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.Port)); + _server.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_server != null) + { + _server.Dispose(); + } + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + } + + protected override void Act() + { + _stopWatch.Start(); + + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (SshOperationTimeoutException ex) + { + _actualException = ex; + } + finally + { + _stopWatch.Stop(); + } + } + + [TestMethod] + public void ConnectShouldHaveThrownSshOperationTimeoutException() + { + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Socket read operation has timed out after {0:F0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message); + } + + [TestMethod] + public void ConnectShouldHaveRespectedTimeout() + { + var errorText = string.Format("Elapsed: {0}, Timeout: {1}", + _stopWatch.ElapsedMilliseconds, + _connectionInfo.Timeout.TotalMilliseconds); + + Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText); + Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText); + } + + [TestMethod] + public void ClientSocketShouldNotBeConnected() + { + Assert.IsTrue(_disconnected); + Assert.IsFalse(_clientSocket.Connected); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyVersion.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyVersion.cs new file mode 100644 index 00000000..d2fcf5c1 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks4ConnectorTest_Connect_TimeoutReadingReplyVersion.cs @@ -0,0 +1,131 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; +using System; +using System.Diagnostics; +using System.Globalization; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class Socks4ConnectorTest_Connect_TimeoutReadingReplyVersion : Socks4ConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private SshOperationTimeoutException _actualException; + private Socket _clientSocket; + private AsyncSocketListener _proxyServer; + private Stopwatch _stopWatch; + private AsyncSocketListener _server; + private bool _disconnected; + + protected override void SetupData() + { + base.SetupData(); + + var random = new Random(); + + _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd"); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200)); + _stopWatch = new Stopwatch(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += socket => _disconnected = true; + _proxyServer.Start(); + + _server = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.Port)); + _server.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_server != null) + { + _server.Dispose(); + } + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + } + + protected override void Act() + { + _stopWatch.Start(); + + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (SshOperationTimeoutException ex) + { + _actualException = ex; + } + finally + { + _stopWatch.Stop(); + } + } + + [TestMethod] + public void ConnectShouldHaveThrownSshOperationTimeoutException() + { + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Socket read operation has timed out after {0:F0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message); + } + + [TestMethod] + public void ConnectShouldHaveRespectedTimeout() + { + var errorText = string.Format("Elapsed: {0}, Timeout: {1}", + _stopWatch.ElapsedMilliseconds, + _connectionInfo.Timeout.TotalMilliseconds); + + Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText); + Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText); + } + + [TestMethod] + public void ClientSocketShouldNotBeConnected() + { + Assert.IsTrue(_disconnected); + Assert.IsFalse(_clientSocket.Connected); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTestBase.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTestBase.cs new file mode 100644 index 00000000..2d2f2cb9 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTestBase.cs @@ -0,0 +1,68 @@ +using Moq; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; +using System; +using System.Net; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + public abstract class Socks5ConnectorTestBase : TripleATestBase + { + internal Mock SocketFactoryMock { get; private set; } + internal Socks5Connector Connector { get; private set; } + internal SocketFactory SocketFactory { get; private set; } + + protected virtual void CreateMocks() + { + SocketFactoryMock = new Mock(MockBehavior.Strict); + } + + protected virtual void SetupData() + { + Connector = new Socks5Connector(SocketFactoryMock.Object); + SocketFactory = new SocketFactory(); + } + + protected virtual void SetupMocks() + { + } + + protected sealed override void Arrange() + { + CreateMocks(); + SetupData(); + SetupMocks(); + } + + protected ConnectionInfo CreateConnectionInfo(string proxyUser, string proxyPassword) + { + return new ConnectionInfo(IPAddress.Loopback.ToString(), + 777, + "user", + ProxyTypes.Socks5, + IPAddress.Loopback.ToString(), + 8122, + proxyUser, + proxyPassword, + new KeyboardInteractiveAuthenticationMethod("user")); + } + + protected static string GenerateRandomString(int minLength, int maxLength) + { + var random = new Random(); + var length = random.Next(minLength, maxLength); + + var sb = new StringBuilder(length); + int offset = 'a'; + + for (var i = 0; i < length; i++) + { + var @char = (char) random.Next(offset, offset + 26); + sb.Append(@char); + } + + return sb.ToString(); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_ConnectionToProxyRefused.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_ConnectionToProxyRefused.cs new file mode 100644 index 00000000..997c18a1 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_ConnectionToProxyRefused.cs @@ -0,0 +1,103 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using System; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class Socks5ConnectorTest_Connect_ConnectionToProxyRefused : Socks5ConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private SocketException _actualException; + private Socket _clientSocket; + private Stopwatch _stopWatch; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd"); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(5000); + _stopWatch = new Stopwatch(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_clientSocket != null) + { + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + _stopWatch.Start(); + + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (SocketException ex) + { + _actualException = ex; + } + finally + { + _stopWatch.Stop(); + } + } + + [TestMethod] + public void ConnectShouldHaveThrownSocketException() + { + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(SocketError.ConnectionRefused, _actualException.SocketErrorCode); + } + + [TestMethod] + public void ConnectShouldHaveRespectedTimeout() + { + var errorText = string.Format("Elapsed: {0}, Timeout: {1}", + _stopWatch.ElapsedMilliseconds, + _connectionInfo.Timeout.TotalMilliseconds); + + Assert.IsTrue(_stopWatch.ElapsedMilliseconds < _connectionInfo.Timeout.TotalMilliseconds, errorText); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_NoAuthentication_ConnectionSucceeded.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_NoAuthentication_ConnectionSucceeded.cs new file mode 100644 index 00000000..7f4b108c --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_NoAuthentication_ConnectionSucceeded.cs @@ -0,0 +1,206 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class Socks5Connector_Connect_NoAuthentication_Succeed : Socks5ConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private Socket _clientSocket; + private List _bytesReceivedByProxy; + private Socket _actual; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = CreateConnectionInfo(new string('a', 255), new string('b', 255)); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100); + _bytesReceivedByProxy = new List(); + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + _bytesReceivedByProxy.AddRange(bytesReceived); + + if (_bytesReceivedByProxy.Count == 4) + { + // We received the greeting + + socket.Send(new byte[] + { + // SOCKS version + 0x05, + // Require no authentication + 0x00 + }); + } + else if (_bytesReceivedByProxy.Count == 4 + (1 + 1 + 1 + 1 + 4 + 2)) + { + // We received the connection request + + socket.Send(new byte[] + { + // SOCKS version + 0x05, + // Connection successful + 0x00, + // Reserved byte + 0x00, + }); + + // Send server bound address + socket.Send(new byte[] + { + // IPv6 + 0x04, + // IP address + 0x01, + 0x02, + 0x12, + 0x41, + 0x31, + 0x02, + 0x42, + 0x41, + 0x71, + 0x02, + 0x32, + 0x81, + 0x01, + 0x52, + 0x12, + 0x91, + // Port + 0x0f, + 0x1b, + }); + + // Send extra byte to allow us to verify that connector did not consume too much + socket.Send(new byte[] + { + 0xff + }); + } + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Shutdown(SocketShutdown.Both); + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + _actual = Connector.Connect(_connectionInfo); + } + + [TestMethod] + public void ConnectShouldHaveReturnedSocketCreatedUsingSocketFactory() + { + Assert.IsNotNull(_actual); + Assert.AreSame(_clientSocket, _actual); + } + + [TestMethod] + public void ClientSocketShouldBeConnected() + { + Assert.IsTrue(_clientSocket.Connected); + } + + [TestMethod] + public void ProxyShouldHaveReceivedExpectedSocksRequest() + { + var expectedSocksRequest = new byte[] + { + // + // Client greeting + // + + // SOCKS version + 0x05, + // Number of authentication methods supported + 0x02, + // No authentication + 0x00, + // Username/password + 0x02, + + // + // Client connection request + // + + // SOCKS version + 0x05, + // Establish a TCP/IP stream connection + 0x01, + // Reserved + 0x00, + // Destination address type (IPv4) + 0x01, + // Destination address (IPv4) + 0x7f, + 0x00, + 0x00, + 0x01, + // Destination port + 0x03, + 0x09 + + }; + + var errorText = string.Format("Expected:{0}{1}{0}but was:{0}{2}", + Environment.NewLine, + PacketDump.Create(expectedSocksRequest, 2), + PacketDump.Create(_bytesReceivedByProxy, 2)); + + Assert.IsTrue(expectedSocksRequest.SequenceEqual(_bytesReceivedByProxy), errorText); + } + + [TestMethod] + public void OnlySocksResponseShouldHaveBeenConsumed() + { + var buffer = new byte[1]; + + var bytesRead = _clientSocket.Receive(buffer); + Assert.AreEqual(1, bytesRead); + Assert.AreEqual(0xff, buffer[0]); + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_ProxySocksVersionIsNotSupported.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_ProxySocksVersionIsNotSupported.cs new file mode 100644 index 00000000..dc9a3a9e --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_ProxySocksVersionIsNotSupported.cs @@ -0,0 +1,123 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Tests.Common; +using System; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class Socks5ConnectorTest_Connect_ProxySocksVersionIsNotSupported : Socks5ConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private Socket _clientSocket; + private byte _proxySocksVersion; + private bool _disconnected; + private ProxyException _actualException; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd"); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100); + _proxySocksVersion = GetNotSupportedSocksVersion(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += socket => _disconnected = true; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + socket.Send(new byte[] { _proxySocksVersion }); + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (ProxyException ex) + { + _actualException = ex; + } + } + + [TestMethod] + public void ConnectShouldHaveThrownProxyException() + { + Assert.IsNotNull(_actualException); + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(string.Format("SOCKS Version '{0}' is not supported.", _proxySocksVersion), _actualException.Message); + } + + [TestMethod] + public void ConnectionToProxyShouldHaveBeenShutDown() + { + Assert.IsTrue(_disconnected); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + + private static byte GetNotSupportedSocksVersion() + { + var random = new Random(); + + while (true) + { + var socksVersion = random.Next(1, 255); + if (socksVersion != 5) + { + return (byte) socksVersion; + } + } + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_TimeoutConnectingToProxy.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_TimeoutConnectingToProxy.cs new file mode 100644 index 00000000..4ac547b9 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_TimeoutConnectingToProxy.cs @@ -0,0 +1,107 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using System; +using System.Diagnostics; +using System.Globalization; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class Socks5ConnectorTest_Connect_TimeoutConnectingToProxy : Socks5ConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private SshOperationTimeoutException _actualException; + private Socket _clientSocket; + private Stopwatch _stopWatch; + + protected override void SetupData() + { + base.SetupData(); + + var random = new Random(); + + _connectionInfo = CreateConnectionInfo("proxyUser", "proxyPwd"); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(random.Next(50, 200)); + _stopWatch = new Stopwatch(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_clientSocket != null) + { + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + _stopWatch.Start(); + + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (SshOperationTimeoutException ex) + { + _actualException = ex; + } + finally + { + _stopWatch.Stop(); + } + } + + [TestMethod] + public void ConnectShouldHaveThrownSshOperationTimeoutException() + { + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Connection failed to establish within {0} milliseconds.", _connectionInfo.Timeout.TotalMilliseconds), _actualException.Message); + } + + [TestMethod] + public void ConnectShouldHaveRespectedTimeout() + { + var errorText = string.Format("Elapsed: {0}, Timeout: {1}", + _stopWatch.ElapsedMilliseconds, + _connectionInfo.Timeout.TotalMilliseconds); + + Assert.IsTrue(_stopWatch.ElapsedMilliseconds >= _connectionInfo.Timeout.TotalMilliseconds, errorText); + Assert.IsTrue(_stopWatch.ElapsedMilliseconds < (_connectionInfo.Timeout.TotalMilliseconds + 100), errorText); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_AuthenticationFailed.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_AuthenticationFailed.cs new file mode 100644 index 00000000..878636ba --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_AuthenticationFailed.cs @@ -0,0 +1,177 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_AuthenticationFailed : Socks5ConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private Socket _clientSocket; + private List _bytesReceivedByProxy; + private bool _disconnected; + private ProxyException _actualException; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = CreateConnectionInfo("aa", "bbbb"); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100); + _bytesReceivedByProxy = new List(); + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += socket => _disconnected = true; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + _bytesReceivedByProxy.AddRange(bytesReceived); + + if (_bytesReceivedByProxy.Count == 4) + { + // We received the greeting + + socket.Send(new byte[] + { + // SOCKS version + 0x05, + // Require username/password authentication + 0x02 + }); + } + else if (_bytesReceivedByProxy.Count == 4 + (1 + 1 + 2 + 1 + 4)) + { + // We received the username/password authentication request + + socket.Send(new byte[] + { + // Authentication version + 0x01, + // Authentication failed + 0x01 + }); + } + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (ProxyException ex) + { + _actualException = ex; + } + } + + [TestMethod] + public void ConnectShouldHaveThrownProxyException() + { + Assert.IsNotNull(_actualException); + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual("SOCKS5: Username/Password authentication failed.", _actualException.Message); + } + + [TestMethod] + public void ProxyShouldHaveReceivedExpectedSocksRequest() + { + var expectedSocksRequest = new List(); + + // + // Client greeting + // + + // SOCKS version + expectedSocksRequest.Add(0x05); + // Number of authentication methods supported + expectedSocksRequest.Add(0x02); + // No authentication + expectedSocksRequest.Add(0x00); + // Username/password + expectedSocksRequest.Add(0x02); + + // + // Username/password authentication request + // + + // Version of the negotiation + expectedSocksRequest.Add(0x01); + // Length of the username + expectedSocksRequest.Add((byte) _connectionInfo.ProxyUsername.Length); + // Username + expectedSocksRequest.AddRange(Encoding.ASCII.GetBytes(_connectionInfo.ProxyUsername)); + // Length of the password + expectedSocksRequest.Add((byte) _connectionInfo.ProxyPassword.Length); + // Password + expectedSocksRequest.AddRange(Encoding.ASCII.GetBytes(_connectionInfo.ProxyPassword)); + + var errorText = string.Format("Expected:{0}{1}{0}but was:{0}{2}", + Environment.NewLine, + PacketDump.Create(expectedSocksRequest, 2), + PacketDump.Create(_bytesReceivedByProxy, 2)); + + Assert.IsTrue(expectedSocksRequest.SequenceEqual(_bytesReceivedByProxy), errorText); + } + + [TestMethod] + public void ConnectionToProxyShouldHaveBeenShutDown() + { + Assert.IsTrue(_disconnected); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_ConnectionSucceeded.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_ConnectionSucceeded.cs new file mode 100644 index 00000000..b94c40e8 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_ConnectionSucceeded.cs @@ -0,0 +1,220 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_ConnectionSucceeded : Socks5ConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private Socket _clientSocket; + private List _bytesReceivedByProxy; + private Socket _actual; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = CreateConnectionInfo(GenerateRandomString(0, 255), GenerateRandomString(0, 255)); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100); + _bytesReceivedByProxy = new List(); + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + _bytesReceivedByProxy.AddRange(bytesReceived); + + if (_bytesReceivedByProxy.Count == 4) + { + // We received the greeting + + socket.Send(new byte[] + { + // SOCKS version + 0x05, + // Require username/password authentication + 0x02 + }); + } + else if (_bytesReceivedByProxy.Count == 4 + (1 + 1 + _connectionInfo.ProxyUsername.Length + 1 + _connectionInfo.ProxyPassword.Length)) + { + // We received the username/password authentication request + + socket.Send(new byte[] + { + // Authentication version + 0x01, + // Authentication successful + 0x00 + }); + } + else if (_bytesReceivedByProxy.Count == 4 + (1 + 1 + _connectionInfo.ProxyUsername.Length + 1 + _connectionInfo.ProxyPassword.Length) + (1 + 1 + 1 + 1 + 4 + 2)) + { + // We received the connection request + + socket.Send(new byte[] + { + // SOCKS version + 0x05, + // Connection successful + 0x00, + // Reserved byte + 0x00, + }); + + // Send server bound address + socket.Send(new byte[] + { + // IPv4 + 0x01, + // IP address + 0x01, + 0x02, + 0x12, + 0x41, + // Port + 0x01, + 0x02, + }); + + // Send extra byte to allow us to verify that connector did not consume too much + socket.Send(new byte[] + { + 0xfe + }); + } + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Shutdown(SocketShutdown.Both); + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + _actual = Connector.Connect(_connectionInfo); + } + + [TestMethod] + public void ConnectShouldHaveReturnedSocketCreatedUsingSocketFactory() + { + Assert.IsNotNull(_actual); + Assert.AreSame(_clientSocket, _actual); + } + + [TestMethod] + public void ClientSocketShouldBeConnected() + { + Assert.IsTrue(_clientSocket.Connected); + } + + [TestMethod] + public void ProxyShouldHaveReceivedExpectedSocksRequest() + { + var expectedSocksRequest = new List(); + + // + // Client greeting + // + + // SOCKS version + expectedSocksRequest.Add(0x05); + // Number of authentication methods supported + expectedSocksRequest.Add(0x02); + // No authentication + expectedSocksRequest.Add(0x00); + // Username/password + expectedSocksRequest.Add(0x02); + + // + // Username/password authentication request + // + + // Version of the negotiation + expectedSocksRequest.Add(0x01); + // Length of the username + expectedSocksRequest.Add((byte)_connectionInfo.ProxyUsername.Length); + // Username + expectedSocksRequest.AddRange(Encoding.ASCII.GetBytes(_connectionInfo.ProxyUsername)); + // Length of the password + expectedSocksRequest.Add((byte)_connectionInfo.ProxyPassword.Length); + // Password + expectedSocksRequest.AddRange(Encoding.ASCII.GetBytes(_connectionInfo.ProxyPassword)); + + // + // Client connection request + // + + // SOCKS version + expectedSocksRequest.Add(0x05); + // Establish a TCP/IP stream connection + expectedSocksRequest.Add(0x01); + // Reserved + expectedSocksRequest.Add(0x00); + // Destination address type (IPv4) + expectedSocksRequest.Add(0x01); + // Destination address (IPv4) + expectedSocksRequest.Add(0x7f); + expectedSocksRequest.Add(0x00); + expectedSocksRequest.Add(0x00); + expectedSocksRequest.Add(0x01); + // Destination port + expectedSocksRequest.Add(0x03); + expectedSocksRequest.Add(0x09); + + var errorText = string.Format("Expected:{0}{1}{0}but was:{0}{2}", + Environment.NewLine, + PacketDump.Create(expectedSocksRequest, 2), + PacketDump.Create(_bytesReceivedByProxy, 2)); + + Assert.IsTrue(expectedSocksRequest.SequenceEqual(_bytesReceivedByProxy), errorText); + } + + [TestMethod] + public void OnlySocksResponseShouldHaveBeenConsumed() + { + var buffer = new byte[1]; + + var bytesRead = _clientSocket.Receive(buffer); + Assert.AreEqual(1, bytesRead); + Assert.AreEqual(0xfe, buffer[0]); + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_PasswordExceedsMaximumLength.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_PasswordExceedsMaximumLength.cs new file mode 100644 index 00000000..61db86a4 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_PasswordExceedsMaximumLength.cs @@ -0,0 +1,149 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_PasswordExceedsMaximumLength : Socks5ConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private Socket _clientSocket; + private List _bytesReceivedByProxy; + private bool _disconnected; + private ProxyException _actualException; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = CreateConnectionInfo(new string('a', 255), new string('b', 256)); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100); + _bytesReceivedByProxy = new List(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += socket => _disconnected = true; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + _bytesReceivedByProxy.AddRange(bytesReceived); + + // Wait until we received the greeting + if (_bytesReceivedByProxy.Count == 4) + { + socket.Send(new byte[] + { + // SOCKS version + 0x05, + // Username/password authentication + 0x02 + }); + } + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (ProxyException ex) + { + _actualException = ex; + } + } + + [TestMethod] + public void ConnectShouldHaveThrownProxyException() + { + Assert.IsNotNull(_actualException); + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual("Proxy password is too long.", _actualException.Message); + } + + [TestMethod] + public void ProxyShouldHaveReceivedExpectedSocksRequest() + { + var expectedSocksRequest = new List(); + + // + // Client greeting + // + + // SOCKS version + expectedSocksRequest.Add(0x05); + // Number of authentication methods supported + expectedSocksRequest.Add(0x02); + // No authentication + expectedSocksRequest.Add(0x00); + // Username/password + expectedSocksRequest.Add(0x02); + + var errorText = string.Format("Expected:{0}{1}{0}but was:{0}{2}", + Environment.NewLine, + PacketDump.Create(expectedSocksRequest, 2), + PacketDump.Create(_bytesReceivedByProxy, 2)); + + Assert.IsTrue(expectedSocksRequest.SequenceEqual(_bytesReceivedByProxy), errorText); + } + + [TestMethod] + public void ConnectionToProxyShouldHaveBeenShutDown() + { + Assert.IsTrue(_disconnected); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_UserNameExceedsMaximumLength.cs b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_UserNameExceedsMaximumLength.cs new file mode 100644 index 00000000..7a0ef5d4 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_UserNameExceedsMaximumLength.cs @@ -0,0 +1,149 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Common; +using Renci.SshNet.Tests.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class Socks5ConnectorTest_Connect_UserNamePasswordAuthentication_UserNameExceedsMaximumLength : Socks5ConnectorTestBase + { + private ConnectionInfo _connectionInfo; + private AsyncSocketListener _proxyServer; + private Socket _clientSocket; + private List _bytesReceivedByProxy; + private bool _disconnected; + private ProxyException _actualException; + + protected override void SetupData() + { + base.SetupData(); + + _connectionInfo = CreateConnectionInfo(new string('a', 256), new string('b', 255)); + _connectionInfo.Timeout = TimeSpan.FromMilliseconds(100); + _bytesReceivedByProxy = new List(); + _actualException = null; + + _clientSocket = SocketFactory.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + _proxyServer = new AsyncSocketListener(new IPEndPoint(IPAddress.Loopback, _connectionInfo.ProxyPort)); + _proxyServer.Disconnected += socket => _disconnected = true; + _proxyServer.BytesReceived += (bytesReceived, socket) => + { + _bytesReceivedByProxy.AddRange(bytesReceived); + + // Wait until we received the greeting + if (_bytesReceivedByProxy.Count == 4) + { + socket.Send(new byte[] + { + // SOCKS version + 0x05, + // Username/password authentication + 0x02 + }); + } + }; + _proxyServer.Start(); + } + + protected override void SetupMocks() + { + SocketFactoryMock.Setup(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + .Returns(_clientSocket); + } + + protected override void TearDown() + { + base.TearDown(); + + if (_proxyServer != null) + { + _proxyServer.Dispose(); + } + + if (_clientSocket != null) + { + _clientSocket.Dispose(); + } + } + + protected override void Act() + { + try + { + Connector.Connect(_connectionInfo); + Assert.Fail(); + } + catch (ProxyException ex) + { + _actualException = ex; + } + } + + [TestMethod] + public void ConnectShouldHaveThrownProxyException() + { + Assert.IsNotNull(_actualException); + Assert.IsNull(_actualException.InnerException); + Assert.AreEqual("Proxy username is too long.", _actualException.Message); + } + + [TestMethod] + public void ProxyShouldHaveReceivedExpectedSocksRequest() + { + var expectedSocksRequest = new List(); + + // + // Client greeting + // + + // SOCKS version + expectedSocksRequest.Add(0x05); + // Number of authentication methods supported + expectedSocksRequest.Add(0x02); + // No authentication + expectedSocksRequest.Add(0x00); + // Username/password + expectedSocksRequest.Add(0x02); + + var errorText = string.Format("Expected:{0}{1}{0}but was:{0}{2}", + Environment.NewLine, + PacketDump.Create(expectedSocksRequest, 2), + PacketDump.Create(_bytesReceivedByProxy, 2)); + + Assert.IsTrue(expectedSocksRequest.SequenceEqual(_bytesReceivedByProxy), errorText); + } + + [TestMethod] + public void ConnectionToProxyShouldHaveBeenShutDown() + { + Assert.IsTrue(_disconnected); + } + + [TestMethod] + public void ClientSocketShouldHaveBeenDisposed() + { + try + { + _clientSocket.Receive(new byte[0]); + Assert.Fail(); + } + catch (ObjectDisposedException) + { + } + } + + [TestMethod] + public void CreateOnSocketFactoryShouldHaveBeenInvokedOnce() + { + SocketFactoryMock.Verify(p => p.Create(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), + Times.Once()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Connection/SshIdentificationTest.cs b/src/Renci.SshNet.Tests/Classes/Connection/SshIdentificationTest.cs new file mode 100644 index 00000000..2a7137ae --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/Connection/SshIdentificationTest.cs @@ -0,0 +1,139 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Renci.SshNet.Connection; +using System; + +namespace Renci.SshNet.Tests.Classes.Connection +{ + [TestClass] + public class SshIdentificationTest + { + [TestMethod] + public void Ctor_ProtocolVersionAndSoftwareVersion() + { + const string protocolVersion = "1.5"; + const string softwareVersion = "SSH.NET_2020.0.0"; + + var sshIdentification = new SshIdentification(protocolVersion, softwareVersion); + Assert.AreSame(protocolVersion, sshIdentification.ProtocolVersion); + Assert.AreSame(softwareVersion, sshIdentification.SoftwareVersion); + Assert.IsNull(sshIdentification.Comments); + } + + [TestMethod] + public void Ctor_ProtocolVersionAndSoftwareVersion_ProtocolVersionIsNull() + { + const string protocolVersion = null; + const string softwareVersion = "SSH.NET_2020.0.0"; + + try + { + new SshIdentification(protocolVersion, softwareVersion); + Assert.Fail(); + } + catch (ArgumentNullException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("protocolVersion", ex.ParamName); + } + } + + [TestMethod] + public void Ctor_ProtocolVersionAndSoftwareVersion_SoftwareVersionIsNull() + { + const string protocolVersion = "2.0"; + const string softwareVersion = null; + + try + { + new SshIdentification(protocolVersion, softwareVersion); + Assert.Fail(); + } + catch (ArgumentNullException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("softwareVersion", ex.ParamName); + } + } + + [TestMethod] + public void Ctor_ProtocolVersionAndSoftwareVersionAndComments() + { + const string protocolVersion = "1.5"; + const string softwareVersion = "SSH.NET_2020.0.0"; + const string comments = "Beware, dangerous!"; + + var sshIdentification = new SshIdentification(protocolVersion, softwareVersion, comments); + Assert.AreSame(protocolVersion, sshIdentification.ProtocolVersion); + Assert.AreSame(softwareVersion, sshIdentification.SoftwareVersion); + Assert.AreSame(comments, sshIdentification.Comments); + } + + [TestMethod] + public void Ctor_ProtocolVersionAndSoftwareVersionAndComments_CommentsIsNull() + { + const string protocolVersion = "1.5"; + const string softwareVersion = "SSH.NET_2020.0.0"; + const string comments = null; + + var sshIdentification = new SshIdentification(protocolVersion, softwareVersion, comments); + Assert.AreSame(protocolVersion, sshIdentification.ProtocolVersion); + Assert.AreSame(softwareVersion, sshIdentification.SoftwareVersion); + Assert.IsNull(comments, sshIdentification.Comments); + } + + [TestMethod] + public void Ctor_ProtocolVersionAndSoftwareVersionAndComments_ProtocolVersionIsNull() + { + const string protocolVersion = null; + const string softwareVersion = "SSH.NET_2020.0.0"; + const string comments = "Beware!"; + + try + { + new SshIdentification(protocolVersion, softwareVersion, comments); + Assert.Fail(); + } + catch (ArgumentNullException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("protocolVersion", ex.ParamName); + } + } + + [TestMethod] + public void Ctor_ProtocolVersionAndSoftwareVersionAndComments_SoftwareVersionIsNull() + { + const string protocolVersion = "2.0"; + const string softwareVersion = null; + const string comments = "Beware!"; + + try + { + new SshIdentification(protocolVersion, softwareVersion, comments); + Assert.Fail(); + } + catch (ArgumentNullException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("softwareVersion", ex.ParamName); + } + } + + [TestMethod] + public void ToString_Comments() + { + var sshIdentification = new SshIdentification("2.0", "SSH.NET", "Beware, dangerous"); + Assert.AreEqual("SSH-2.0-SSH.NET Beware, dangerous", sshIdentification.ToString()); + } + + [TestMethod] + public void ToString_CommentsIsNull() + { + var sshIdentification = new SshIdentification("2.0", "SSH.NET_2020.0.0"); + Assert.AreEqual("SSH-2.0-SSH.NET_2020.0.0", sshIdentification.ToString()); + + sshIdentification = new SshIdentification("2.0", "SSH.NET_2020.0.0", null); + Assert.AreEqual("SSH-2.0-SSH.NET_2020.0.0", sshIdentification.ToString()); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/Messages/Connection/ChannelExtendedDataMessageTest.cs b/src/Renci.SshNet.Tests/Classes/Messages/Connection/ChannelExtendedDataMessageTest.cs index e50f5fa4..469908ea 100644 --- a/src/Renci.SshNet.Tests/Classes/Messages/Connection/ChannelExtendedDataMessageTest.cs +++ b/src/Renci.SshNet.Tests/Classes/Messages/Connection/ChannelExtendedDataMessageTest.cs @@ -30,7 +30,7 @@ namespace Renci.SshNet.Tests.Classes.Messages.Connection [Ignore] // placeholder public void ChannelExtendedDataMessageConstructorTest1() { - uint localChannelNumber = 0; // TODO: Initialize to an appropriate value + //uint localChannelNumber = 0; // TODO: Initialize to an appropriate value //ChannelExtendedDataMessage target = new ChannelExtendedDataMessage(localChannelNumber, null, null); Assert.Inconclusive("TODO: Implement code to verify target"); } diff --git a/src/Renci.SshNet.Tests/Classes/NetConfClientTestBase.cs b/src/Renci.SshNet.Tests/Classes/NetConfClientTestBase.cs new file mode 100644 index 00000000..ee8cb018 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/NetConfClientTestBase.cs @@ -0,0 +1,17 @@ +using Moq; +using Renci.SshNet.NetConf; + +namespace Renci.SshNet.Tests.Classes +{ + public abstract class NetConfClientTestBase : BaseClientTestBase + { + internal Mock _netConfSessionMock { get; private set; } + + protected override void CreateMocks() + { + base.CreateMocks(); + + _netConfSessionMock = new Mock(MockBehavior.Strict); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Connect_NetConfSessionConnectFailure.cs b/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Connect_NetConfSessionConnectFailure.cs index 64ffb6ce..980c574a 100644 --- a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Connect_NetConfSessionConnectFailure.cs +++ b/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Connect_NetConfSessionConnectFailure.cs @@ -10,51 +10,29 @@ using Renci.SshNet.Security; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class NetConfClientTest_Connect_NetConfSessionConnectFailure + public class NetConfClientTest_Connect_NetConfSessionConnectFailure : NetConfClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; - private Mock _netConfSessionMock; private ConnectionInfo _connectionInfo; private ApplicationException _netConfSessionConnectionException; private NetConfClient _netConfClient; private ApplicationException _actualException; - [TestInitialize] - public void Setup() - { - Arrange(); - Act(); - } - - private void Arrange() - { - SetupData(); - CreateMocks(); - SetupMocks(); - - _netConfClient = new NetConfClient(_connectionInfo, false, _serviceFactoryMock.Object); - } - - private void SetupData() + protected override void SetupData() { _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); _netConfSessionConnectionException = new ApplicationException(); + _netConfClient = new NetConfClient(_connectionInfo, false, _serviceFactoryMock.Object); } - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _netConfSessionMock = new Mock(MockBehavior.Strict); - } - - private void SetupMocks() + protected override void SetupMocks() { var sequence = new MockSequence(); _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence) .Setup(p => p.Connect()); @@ -70,7 +48,7 @@ namespace Renci.SshNet.Tests.Classes .Setup(p => p.Dispose()); } - private void Act() + protected override void Act() { try { diff --git a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Connected.cs b/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Connected.cs index ad0cf531..ba86a7c9 100644 --- a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Connected.cs +++ b/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Connected.cs @@ -6,56 +6,55 @@ using System; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class NetConfClientTest_Dispose_Connected + public class NetConfClientTest_Dispose_Connected : NetConfClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; - private Mock _netConfSessionMock; private NetConfClient _netConfClient; private ConnectionInfo _connectionInfo; private int _operationTimeout; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); - } - - [TestCleanup] - public void Cleanup() - { - } - - protected void Arrange() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _netConfSessionMock = new Mock(MockBehavior.Strict); - _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); _operationTimeout = new Random().Next(1000, 10000); _netConfClient = new NetConfClient(_connectionInfo, false, _serviceFactoryMock.Object); _netConfClient.OperationTimeout = TimeSpan.FromMilliseconds(_operationTimeout); + } + protected override void SetupMocks() + { var sequence = new MockSequence(); + _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); - _sessionMock.InSequence(sequence).Setup(p => p.Connect()); + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout)) - .Returns(_netConfSessionMock.Object); - _netConfSessionMock.InSequence(sequence).Setup(p => p.Connect()); - _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting()); - _netConfSessionMock.InSequence(sequence).Setup(p => p.Disconnect()); - _sessionMock.InSequence(sequence).Setup(p => p.Dispose()); - _netConfSessionMock.InSequence(sequence).Setup(p => p.Dispose()); + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); + _sessionMock.InSequence(sequence) + .Setup(p => p.Connect()); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout)) + .Returns(_netConfSessionMock.Object); + _netConfSessionMock.InSequence(sequence) + .Setup(p => p.Connect()); + _sessionMock.InSequence(sequence) + .Setup(p => p.OnDisconnecting()); + _netConfSessionMock.InSequence(sequence) + .Setup(p => p.Disconnect()); + _sessionMock.InSequence(sequence) + .Setup(p => p.Dispose()); + _netConfSessionMock.InSequence(sequence) + .Setup(p => p.Dispose()); + } + + protected override void Arrange() + { + base.Arrange(); _netConfClient.Connect(); } - protected void Act() + protected override void Act() { _netConfClient.Dispose(); } @@ -66,10 +65,17 @@ namespace Renci.SshNet.Tests.Classes _serviceFactoryMock.Verify(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout), Times.Once); } + [TestMethod] + public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce() + { + _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once); + } + [TestMethod] public void CreateSessionOnServiceFactoryShouldBeInvokedOnce() { - _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once); + _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object), + Times.Once); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disconnected.cs b/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disconnected.cs index 24b495d7..8a4fbcd2 100644 --- a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disconnected.cs +++ b/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disconnected.cs @@ -6,11 +6,8 @@ using System; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class NetConfClientTest_Dispose_Disconnected + public class NetConfClientTest_Dispose_Disconnected : NetConfClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; - private Mock _netConfSessionMock; private NetConfClient _netConfClient; private ConnectionInfo _connectionInfo; private int _operationTimeout; @@ -27,37 +24,45 @@ namespace Renci.SshNet.Tests.Classes { } - protected void Arrange() + protected override void SetupData() { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _netConfSessionMock = new Mock(MockBehavior.Strict); - _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); _operationTimeout = new Random().Next(1000, 10000); _netConfClient = new NetConfClient(_connectionInfo, false, _serviceFactoryMock.Object); _netConfClient.OperationTimeout = TimeSpan.FromMilliseconds(_operationTimeout); + } + protected override void SetupMocks() + { var sequence = new MockSequence(); + _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout)) - .Returns(_netConfSessionMock.Object); + .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout)) + .Returns(_netConfSessionMock.Object); _netConfSessionMock.InSequence(sequence).Setup(p => p.Connect()); _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting()); _netConfSessionMock.InSequence(sequence).Setup(p => p.Disconnect()); _sessionMock.InSequence(sequence).Setup(p => p.Dispose()); _netConfSessionMock.InSequence(sequence).Setup(p => p.Disconnect()); _netConfSessionMock.InSequence(sequence).Setup(p => p.Dispose()); + } + + protected override void Arrange() + { + base.Arrange(); _netConfClient.Connect(); _netConfClient.Disconnect(); } - protected void Act() + protected override void Act() { _netConfClient.Dispose(); } @@ -68,10 +73,17 @@ namespace Renci.SshNet.Tests.Classes _serviceFactoryMock.Verify(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout), Times.Once); } + [TestMethod] + public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce() + { + _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once); + } + [TestMethod] public void CreateSessionOnServiceFactoryShouldBeInvokedOnce() { - _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once); + _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object), + Times.Once); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disposed.cs b/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disposed.cs index 7439ed75..9ed692a3 100644 --- a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disposed.cs +++ b/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Dispose_Disposed.cs @@ -6,57 +6,50 @@ using System; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class NetConfClientTest_Dispose_Disposed + public class NetConfClientTest_Dispose_Disposed : NetConfClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; - private Mock _netConfSessionMock; private NetConfClient _netConfClient; private ConnectionInfo _connectionInfo; private int _operationTimeout; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); - } - - [TestCleanup] - public void Cleanup() - { - } - - protected void Arrange() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _netConfSessionMock = new Mock(MockBehavior.Strict); - _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); _operationTimeout = new Random().Next(1000, 10000); _netConfClient = new NetConfClient(_connectionInfo, false, _serviceFactoryMock.Object); _netConfClient.OperationTimeout = TimeSpan.FromMilliseconds(_operationTimeout); + } + protected override void SetupMocks() + { var sequence = new MockSequence(); + _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout)) - .Returns(_netConfSessionMock.Object); + .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout)) + .Returns(_netConfSessionMock.Object); _netConfSessionMock.InSequence(sequence).Setup(p => p.Connect()); _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting()); _netConfSessionMock.InSequence(sequence).Setup(p => p.Disconnect()); _sessionMock.InSequence(sequence).Setup(p => p.Dispose()); _netConfSessionMock.InSequence(sequence).Setup(p => p.Dispose()); + } + + protected override void Arrange() + { + base.Arrange(); _netConfClient.Connect(); _netConfClient.Dispose(); } - protected void Act() + protected override void Act() { _netConfClient.Dispose(); } @@ -67,10 +60,17 @@ namespace Renci.SshNet.Tests.Classes _serviceFactoryMock.Verify(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout), Times.Once); } + [TestMethod] + public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce() + { + _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once); + } + [TestMethod] public void CreateSessionOnServiceFactoryShouldBeInvokedOnce() { - _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once); + _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object), + Times.Once); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Finalize_Connected.cs b/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Finalize_Connected.cs index fb17e353..44761e96 100644 --- a/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Finalize_Connected.cs +++ b/src/Renci.SshNet.Tests/Classes/NetConfClientTest_Finalize_Connected.cs @@ -1,59 +1,58 @@ using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Renci.SshNet.NetConf; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class NetConfClientTest_Finalize_Connected + public class NetConfClientTest_Finalize_Connected : NetConfClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; - private Mock _netConfSessionMock; private NetConfClient _netConfClient; private ConnectionInfo _connectionInfo; private int _operationTimeout; + private WeakReference _netConfClientWeakRefence; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); - } - - protected void Arrange() - { - _serviceFactoryMock = new Mock(MockBehavior.Loose); - _sessionMock = new Mock(MockBehavior.Loose); - _netConfSessionMock = new Mock(MockBehavior.Loose); - _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); _operationTimeout = new Random().Next(1000, 10000); _netConfClient = new NetConfClient(_connectionInfo, false, _serviceFactoryMock.Object); _netConfClient.OperationTimeout = TimeSpan.FromMilliseconds(_operationTimeout); + _netConfClientWeakRefence = new WeakReference(_netConfClient); + } + protected override void SetupMocks() + { var sequence = new MockSequence(); + _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); - _sessionMock.InSequence(sequence).Setup(p => p.Connect()); + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout)) - .Returns(_netConfSessionMock.Object); - _netConfSessionMock.InSequence(sequence).Setup(p => p.Connect()); + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); + _sessionMock.InSequence(sequence) + .Setup(p => p.Connect()); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateNetConfSession(_sessionMock.Object, _operationTimeout)) + .Returns(_netConfSessionMock.Object); + _netConfSessionMock.InSequence(sequence) + .Setup(p => p.Connect()); + } + + protected override void Arrange() + { + base.Arrange(); _netConfClient.Connect(); _netConfClient = null; - // we need to dereference all other mocks as they might otherwise hold the target alive - _sessionMock = null; - _connectionInfo = null; - _serviceFactoryMock = null; - + // We need to dereference all mocks as they might otherwise hold the target alive + //(through recorded invocations?) + CreateMocks(); } - protected void Act() + protected override void Act() { GC.Collect(); GC.WaitForPendingFinalizers(); @@ -62,13 +61,25 @@ namespace Renci.SshNet.Tests.Classes [TestMethod] public void DisconnectOnNetConfSessionShouldBeInvokedOnce() { + // Since we recreated the mocks, this test has no value + // We'll leaving ths test just in case we have a solution that does not require us + // to recreate the mocks _netConfSessionMock.Verify(p => p.Disconnect(), Times.Never); } [TestMethod] public void DisposeOnNetConfSessionShouldBeInvokedOnce() { + // Since we recreated the mocks, this test has no value + // We'll leaving ths test just in case we have a solution that does not require us + // to recreate the mocks _netConfSessionMock.Verify(p => p.Dispose(), Times.Never); } + + [TestMethod] + public void NetConfClientShouldHaveBeenFinalized() + { + Assert.IsNull(_netConfClientWeakRefence.Target); + } } } diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTestBase.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTestBase.cs index 8626d7d0..26aa22e3 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTestBase.cs +++ b/src/Renci.SshNet.Tests/Classes/ScpClientTestBase.cs @@ -1,45 +1,22 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; +using Moq; using Renci.SshNet.Channels; using Renci.SshNet.Common; namespace Renci.SshNet.Tests.Classes { - public abstract class ScpClientTestBase + public abstract class ScpClientTestBase : BaseClientTestBase { - internal Mock _serviceFactoryMock; internal Mock _remotePathTransformationMock; - internal Mock _sessionMock; internal Mock _channelSessionMock; internal Mock _pipeStreamMock; - protected abstract void SetupData(); - - protected void CreateMocks() + protected override void CreateMocks() { - _serviceFactoryMock = new Mock(MockBehavior.Strict); + base.CreateMocks(); + _remotePathTransformationMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); _channelSessionMock = new Mock(MockBehavior.Strict); _pipeStreamMock = new Mock(MockBehavior.Strict); } - - protected abstract void SetupMocks(); - - protected virtual void Arrange() - { - SetupData(); - CreateMocks(); - SetupMocks(); - } - - [TestInitialize] - public void Initialize() - { - Arrange(); - Act(); - } - - protected abstract void Act(); } } diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs index aa5d5cfb..9c5ed485 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs +++ b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndDirectoryInfo_SendExecRequestReturnsFalse.cs @@ -38,7 +38,10 @@ namespace Renci.SshNet.Tests.Classes .Setup(p => p.CreateRemotePathDoubleQuoteTransformation()) .Returns(_remotePathTransformationMock.Object); _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object); @@ -52,6 +55,10 @@ namespace Renci.SshNet.Tests.Classes .Returns(false); _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose()); _pipeStreamMock.As().InSequence(sequence).Setup(p => p.Dispose()); + + // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking + // an interface, we need to expect this call as well + _pipeStreamMock.Setup(p => p.Close()); } protected override void Arrange() diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs index 80165725..8ba4525b 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs +++ b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndFileInfo_SendExecRequestReturnsFalse.cs @@ -38,7 +38,10 @@ namespace Renci.SshNet.Tests.Classes .Setup(p => p.CreateRemotePathDoubleQuoteTransformation()) .Returns(_remotePathTransformationMock.Object); _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object); @@ -51,6 +54,10 @@ namespace Renci.SshNet.Tests.Classes .Setup(p => p.SendExecRequest(string.Format("scp -pf {0}", _transformedPath))).Returns(false); _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose()); _pipeStreamMock.As().InSequence(sequence).Setup(p => p.Dispose()); + + // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking + // an interface, we need to expect this call as well + _pipeStreamMock.Setup(p => p.Close()); } protected override void Arrange() diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs index 309e4566..c1504aec 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs +++ b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Download_PathAndStream_SendExecRequestReturnsFalse.cs @@ -19,15 +19,6 @@ namespace Renci.SshNet.Tests.Classes private IList _uploadingRegister; private SshException _actualException; - [TestCleanup] - public void Cleanup() - { - if (_destination != null) - { - _destination.Dispose(); - } - } - protected override void SetupData() { var random = new Random(); @@ -47,7 +38,10 @@ namespace Renci.SshNet.Tests.Classes .Setup(p => p.CreateRemotePathDoubleQuoteTransformation()) .Returns(_remotePathTransformationMock.Object); _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object); @@ -61,6 +55,10 @@ namespace Renci.SshNet.Tests.Classes .Returns(false); _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose()); _pipeStreamMock.As().InSequence(sequence).Setup(p => p.Dispose()); + + // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking + // an interface, we need to expect this call as well + _pipeStreamMock.Setup(p => p.Close()); } protected override void Arrange() @@ -72,6 +70,16 @@ namespace Renci.SshNet.Tests.Classes _scpClient.Connect(); } + protected override void TearDown() + { + base.TearDown(); + + if (_destination != null) + { + _destination.Dispose(); + } + } + protected override void Act() { try diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse.cs index 1c47aeae..6791e296 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse.cs +++ b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_DirectoryInfoAndPath_SendExecRequestReturnsFalse.cs @@ -37,7 +37,10 @@ namespace Renci.SshNet.Tests.Classes .Setup(p => p.CreateRemotePathDoubleQuoteTransformation()) .Returns(_remotePathTransformationMock.Object); _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object); @@ -51,6 +54,10 @@ namespace Renci.SshNet.Tests.Classes .Returns(false); _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose()); _pipeStreamMock.As().InSequence(sequence).Setup(p => p.Dispose()); + + // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking + // an interface, we need to expect this call as well + _pipeStreamMock.Setup(p => p.Close()); } protected override void Arrange() diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs index 0c842a21..9a065ee2 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs +++ b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_SendExecRequestReturnsFalse.cs @@ -21,16 +21,6 @@ namespace Renci.SshNet.Tests.Classes private IList _uploadingRegister; private SshException _actualException; - [TestCleanup] - public void Cleanup() - { - if (_fileName != null) - { - File.Delete(_fileName); - _fileName = null; - } - } - protected override void SetupData() { var random = new Random(); @@ -53,7 +43,10 @@ namespace Renci.SshNet.Tests.Classes .Setup(p => p.CreateRemotePathDoubleQuoteTransformation()) .Returns(_remotePathTransformationMock.Object); _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object); @@ -67,7 +60,11 @@ namespace Renci.SshNet.Tests.Classes .Returns(false); _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose()); _pipeStreamMock.As().InSequence(sequence).Setup(p => p.Dispose()); - } + + // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking + // an interface, we need to expect this call as well + _pipeStreamMock.Setup(p => p.Close()); +} protected override void Arrange() { @@ -78,6 +75,17 @@ namespace Renci.SshNet.Tests.Classes _scpClient.Connect(); } + protected override void TearDown() + { + base.TearDown(); + + if (_fileName != null) + { + File.Delete(_fileName); + _fileName = null; + } + } + protected override void Act() { try diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs index c04525e0..d86191e5 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs +++ b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_FileInfoAndPath_Success.cs @@ -25,16 +25,6 @@ namespace Renci.SshNet.Tests.Classes private int _fileSize; private IList _uploadingRegister; - [TestCleanup] - public void Cleanup() - { - if (_fileName != null) - { - File.Delete(_fileName); - _fileName = null; - } - } - protected override void SetupData() { var random = new Random(); @@ -60,7 +50,10 @@ namespace Renci.SshNet.Tests.Classes .Setup(p => p.CreateRemotePathDoubleQuoteTransformation()) .Returns(_remotePathTransformationMock.Object); _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object); @@ -92,6 +85,10 @@ namespace Renci.SshNet.Tests.Classes _pipeStreamMock.InSequence(sequence).Setup(p => p.ReadByte()).Returns(0); _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose()); _pipeStreamMock.As().InSequence(sequence).Setup(p => p.Dispose()); + + // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking + // an interface, we need to expect this call as well + _pipeStreamMock.Setup(p => p.Close()); } protected override void Arrange() @@ -106,6 +103,17 @@ namespace Renci.SshNet.Tests.Classes _scpClient.Connect(); } + protected override void TearDown() + { + base.TearDown(); + + if (_fileName != null) + { + File.Delete(_fileName); + _fileName = null; + } + } + protected override void Act() { _scpClient.Upload(_fileInfo, _remotePath); diff --git a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs index d2c6992b..5facca29 100644 --- a/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs +++ b/src/Renci.SshNet.Tests/Classes/ScpClientTest_Upload_StreamAndPath_SendExecRequestReturnsFalse.cs @@ -20,15 +20,6 @@ namespace Renci.SshNet.Tests.Classes private IList _uploadingRegister; private SshException _actualException; - [TestCleanup] - public void Cleanup() - { - if (_source != null) - { - _source.Dispose(); - } - } - protected override void SetupData() { var random = new Random(); @@ -50,7 +41,10 @@ namespace Renci.SshNet.Tests.Classes .Setup(p => p.CreateRemotePathDoubleQuoteTransformation()) .Returns(_remotePathTransformationMock.Object); _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreatePipeStream()).Returns(_pipeStreamMock.Object); @@ -64,6 +58,10 @@ namespace Renci.SshNet.Tests.Classes .Returns(false); _channelSessionMock.InSequence(sequence).Setup(p => p.Dispose()); _pipeStreamMock.As().InSequence(sequence).Setup(p => p.Dispose()); + + // On .NET Core, Dispose() in turn invokes Close() and since we're not mocking + // an interface, we need to expect this call as well + _pipeStreamMock.Setup(p => p.Close()); } protected override void Arrange() @@ -75,6 +73,16 @@ namespace Renci.SshNet.Tests.Classes _scpClient.Connect(); } + protected override void TearDown() + { + base.TearDown(); + + if (_source != null) + { + _source.Dispose(); + } + } + protected override void Act() { try diff --git a/src/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateConnector.cs b/src/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateConnector.cs new file mode 100644 index 00000000..22d2d4c0 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/ServiceFactoryTest_CreateConnector.cs @@ -0,0 +1,140 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Renci.SshNet.Connection; +using System; + +namespace Renci.SshNet.Tests.Classes +{ + [TestClass] + public class ServiceFactoryTest_CreateConnector + { + private ServiceFactory _serviceFactory; + private Mock _connectionInfoMock; + private Mock _socketFactoryMock; + + [TestInitialize] + public void Setup() + { + _serviceFactory = new ServiceFactory(); + _connectionInfoMock = new Mock(MockBehavior.Strict); + _socketFactoryMock = new Mock(MockBehavior.Strict); + } + + [TestMethod] + public void ConnectionInfoIsNull() + { + const IConnectionInfo connectionInfo = null; + + try + { + _serviceFactory.CreateConnector(connectionInfo, _socketFactoryMock.Object); + Assert.Fail(); + } + catch (ArgumentNullException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("connectionInfo", ex.ParamName); + } + } + + [TestMethod] + public void SocketFactoryIsNull() + { + const ISocketFactory socketFactory = null; + + try + { + _serviceFactory.CreateConnector(_connectionInfoMock.Object, socketFactory); + Assert.Fail(); + } + catch (ArgumentNullException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("socketFactory", ex.ParamName); + } + } + + [TestMethod] + public void ProxyType_Http() + { + _connectionInfoMock.Setup(p => p.ProxyType).Returns(ProxyTypes.Http); + + var actual = _serviceFactory.CreateConnector(_connectionInfoMock.Object, _socketFactoryMock.Object); + + Assert.IsNotNull(actual); + Assert.AreEqual(typeof(HttpConnector), actual.GetType()); + + var httpConnector = (HttpConnector) actual; + Assert.AreSame(_socketFactoryMock.Object, httpConnector.SocketFactory); + + _connectionInfoMock.Verify(p => p.ProxyType, Times.Once); + } + + [TestMethod] + public void ProxyType_None() + { + _connectionInfoMock.Setup(p => p.ProxyType).Returns(ProxyTypes.None); + + var actual = _serviceFactory.CreateConnector(_connectionInfoMock.Object, _socketFactoryMock.Object); + + Assert.IsNotNull(actual); + Assert.AreEqual(typeof(DirectConnector), actual.GetType()); + + var directConnector = (DirectConnector) actual; + Assert.AreSame(_socketFactoryMock.Object, directConnector.SocketFactory); + + _connectionInfoMock.Verify(p => p.ProxyType, Times.Once); + } + + [TestMethod] + public void ProxyType_Socks4() + { + _connectionInfoMock.Setup(p => p.ProxyType).Returns(ProxyTypes.Socks4); + + var actual = _serviceFactory.CreateConnector(_connectionInfoMock.Object, _socketFactoryMock.Object); + + Assert.IsNotNull(actual); + Assert.AreEqual(typeof(Socks4Connector), actual.GetType()); + + var socks4Connector = (Socks4Connector) actual; + Assert.AreSame(_socketFactoryMock.Object, socks4Connector.SocketFactory); + + _connectionInfoMock.Verify(p => p.ProxyType, Times.Once); + } + + [TestMethod] + public void ProxyType_Socks5() + { + _connectionInfoMock.Setup(p => p.ProxyType).Returns(ProxyTypes.Socks5); + + var actual = _serviceFactory.CreateConnector(_connectionInfoMock.Object, _socketFactoryMock.Object); + + Assert.IsNotNull(actual); + Assert.AreEqual(typeof(Socks5Connector), actual.GetType()); + + var socks5Connector = (Socks5Connector) actual; + Assert.AreSame(_socketFactoryMock.Object, socks5Connector.SocketFactory); + + _connectionInfoMock.Verify(p => p.ProxyType, Times.Once); + } + + [TestMethod] + public void ProxyType_Undefined() + { + _connectionInfoMock.Setup(p => p.ProxyType).Returns((ProxyTypes) 666); + + try + { + _serviceFactory.CreateConnector(_connectionInfoMock.Object, _socketFactoryMock.Object); + Assert.Fail(); + } + catch (NotSupportedException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("ProxyTypes '666' is not supported.", ex.Message); + } + + _connectionInfoMock.Verify(p => p.ProxyType, Times.Exactly(2)); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs b/src/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs index 7780d2d8..03ce2491 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest.HttpProxy.cs @@ -5,240 +5,13 @@ using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Common; using Renci.SshNet.Tests.Common; +using Renci.SshNet.Connection; namespace Renci.SshNet.Tests.Classes { public partial class SessionTest { - [TestMethod] - public void ConnectShouldThrowProxyExceptionWhenHttpProxyResponseDoesNotContainStatusLine() - { - var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123); - var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); - - using (var proxyStub = new HttpProxyStub(proxyEndPoint)) - { - proxyStub.Responses.Add(Encoding.ASCII.GetBytes("Whatever\r\n")); - proxyStub.Start(); - - using (var session = new Session(CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon"), _serviceFactoryMock.Object)) - { - try - { - session.Connect(); - Assert.Fail(); - } - catch (ProxyException ex) - { - Assert.IsNull(ex.InnerException); - Assert.AreEqual("HTTP response does not contain status line.", ex.Message); - } - } - } - } - - [TestMethod] - public void ConnectShouldThrowProxyExceptionWhenHttpProxyReturnsHttpStatusOtherThan200() - { - var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123); - var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); - - using (var proxyStub = new HttpProxyStub(proxyEndPoint)) - { - proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n")); - proxyStub.Start(); - - using (var session = new Session(CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon"), _serviceFactoryMock.Object)) - { - try - { - session.Connect(); - Assert.Fail(); - } - catch (ProxyException ex) - { - Assert.IsNull(ex.InnerException); - Assert.AreEqual("HTTP: Status code 501, \"Custom\"", ex.Message); - } - } - } - } - - [TestMethod] - public void ConnectShouldSkipHeadersWhenHttpProxyReturnsHttpStatus200() - { - var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123); - var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); - - using (var proxyStub = new HttpProxyStub(proxyEndPoint)) - { - proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n")); - proxyStub.Responses.Add(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n")); - proxyStub.Responses.Add(Encoding.ASCII.GetBytes("\r\n")); - proxyStub.Responses.Add(Encoding.ASCII.GetBytes("SSH-666-SshStub")); - proxyStub.Start(); - - using (var session = new Session(CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon"), _serviceFactoryMock.Object)) - { - try - { - session.Connect(); - Assert.Fail(); - } - catch (SshConnectionException ex) - { - Assert.IsNull(ex.InnerException); - Assert.AreEqual("Server version '666' is not supported.", ex.Message); - } - } - } - } - - [TestMethod] - public void ConnectShouldSkipContentWhenHttpProxyReturnsHttpStatus200() - { - var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123); - var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); - - using (var proxyStub = new HttpProxyStub(proxyEndPoint)) - { - proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 200 OK\r\n")); - proxyStub.Responses.Add(Encoding.ASCII.GetBytes("Content-Length: 13\r\n")); - proxyStub.Responses.Add(Encoding.ASCII.GetBytes("Content-Type: application/octet-stream\r\n")); - proxyStub.Responses.Add(Encoding.ASCII.GetBytes("\r\n")); - proxyStub.Responses.Add(Encoding.ASCII.GetBytes("DUMMY_CONTENT")); - proxyStub.Responses.Add(Encoding.ASCII.GetBytes("SSH-666-SshStub")); - proxyStub.Start(); - - using (var session = new Session(CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon"), _serviceFactoryMock.Object)) - { - try - { - session.Connect(); - Assert.Fail(); - } - catch (SshConnectionException ex) - { - Assert.IsNull(ex.InnerException); - Assert.AreEqual("Server version '666' is not supported.", ex.Message); - } - } - } - } - - [TestMethod] - public void ConnectShouldWriteConnectMethodToHttpProxy() - { - var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123); - var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); - - using (var proxyStub = new HttpProxyStub(proxyEndPoint)) - { - proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n")); - proxyStub.Start(); - - using (var session = new Session(CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon"), _serviceFactoryMock.Object)) - { - try - { - session.Connect(); - Assert.Fail(); - } - catch (ProxyException) - { - } - } - - Assert.AreEqual(string.Format("CONNECT {0} HTTP/1.0", serverEndPoint), proxyStub.HttpRequest.RequestLine); - } - } - - [TestMethod] - public void ConnectShouldWriteProxyAuthorizationToHttpProxyWhenProxyUserNameIsNotNullAndNotEmpty() - { - var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123); - var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); - - using (var proxyStub = new HttpProxyStub(proxyEndPoint)) - { - proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n")); - proxyStub.Start(); - - var connectionInfo = CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, "anon"); - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - try - { - session.Connect(); - Assert.Fail(); - } - catch (ProxyException) - { - } - } - - var expectedProxyAuthorizationHeader = CreateProxyAuthorizationHeader(connectionInfo); - Assert.IsNotNull(proxyStub.HttpRequest.Headers.SingleOrDefault(p => p == expectedProxyAuthorizationHeader)); - } - } - - [TestMethod] - public void ConnectShouldNotWriteProxyAuthorizationToHttpProxyWhenProxyUserNameIsEmpty() - { - var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123); - var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); - - using (var proxyStub = new HttpProxyStub(proxyEndPoint)) - { - proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n")); - proxyStub.Start(); - - var connectionInfo = CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, string.Empty); - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - try - { - session.Connect(); - Assert.Fail(); - } - catch (ProxyException) - { - } - } - - Assert.IsFalse(proxyStub.HttpRequest.Headers.Any(p => p.StartsWith("Proxy-Authorization:"))); - } - } - - [TestMethod] - public void ConnectShouldNotWriteProxyAuthorizationToHttpProxyWhenProxyUserNameIsNull() - { - var proxyEndPoint = new IPEndPoint(IPAddress.Loopback, 8123); - var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); - - using (var proxyStub = new HttpProxyStub(proxyEndPoint)) - { - proxyStub.Responses.Add(Encoding.ASCII.GetBytes("HTTP/1.0 501 Custom\r\n")); - proxyStub.Start(); - - var connectionInfo = CreateConnectionInfoWithProxy(proxyEndPoint, serverEndPoint, null); - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - try - { - session.Connect(); - Assert.Fail(); - } - catch (ProxyException) - { - } - } - - Assert.IsFalse(proxyStub.HttpRequest.Headers.Any(p => p.StartsWith("Proxy-Authorization:"))); - } - } - - private static ConnectionInfo CreateConnectionInfoWithProxy(IPEndPoint proxyEndPoint, IPEndPoint serverEndPoint, string proxyUserName) + private static ConnectionInfo CreateConnectionInfoWithHttpProxy(IPEndPoint proxyEndPoint, IPEndPoint serverEndPoint, string proxyUserName) { return new ConnectionInfo( serverEndPoint.Address.ToString(), diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest.cs b/src/Renci.SshNet.Tests/Classes/SessionTest.cs index de523769..972757e4 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest.cs @@ -1,15 +1,9 @@ using System; -using System.Globalization; using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -using Renci.SshNet.Common; -using Renci.SshNet.Messages.Transport; +using Renci.SshNet.Connection; using Renci.SshNet.Tests.Common; -using Renci.SshNet.Tests.Properties; namespace Renci.SshNet.Tests.Classes { @@ -20,23 +14,28 @@ namespace Renci.SshNet.Tests.Classes public partial class SessionTest : TestBase { private Mock _serviceFactoryMock; + private Mock _socketFactoryMock; + private Mock _connectorMock; + private Mock _protocolVersionExchangeMock; protected override void OnInit() { base.OnInit(); _serviceFactoryMock = new Mock(MockBehavior.Strict); + _socketFactoryMock = new Mock(MockBehavior.Strict); + _connectorMock = new Mock(MockBehavior.Strict); + _protocolVersionExchangeMock = new Mock(MockBehavior.Strict); } [TestMethod] public void ConstructorShouldThrowArgumentNullExceptionWhenConnectionInfoIsNull() { - ConnectionInfo connectionInfo = null; - var serviceFactory = new Mock(MockBehavior.Strict).Object; + const ConnectionInfo connectionInfo = null; try { - new Session(connectionInfo, serviceFactory); + new Session(connectionInfo, _serviceFactoryMock.Object, _socketFactoryMock.Object); Assert.Fail(); } catch (ArgumentNullException ex) @@ -55,7 +54,7 @@ namespace Renci.SshNet.Tests.Classes try { - new Session(connectionInfo, serviceFactory); + new Session(connectionInfo, serviceFactory, _socketFactoryMock.Object); Assert.Fail(); } catch (ArgumentNullException ex) @@ -66,338 +65,24 @@ namespace Renci.SshNet.Tests.Classes } [TestMethod] - public void ConnectShouldSkipLinesBeforeProtocolIdentificationString() + public void ConstructorShouldThrowArgumentNullExceptionWhenSocketFactoryIsNull() { var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); var connectionInfo = CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5)); - - using (var serverStub = new AsyncSocketListener(serverEndPoint)) - { - serverStub.Connected += socket => - { - socket.Send(Encoding.ASCII.GetBytes("\r\n")); - socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n")); - socket.Send(Encoding.ASCII.GetBytes("SSH-666-SshStub\r\n")); - socket.Shutdown(SocketShutdown.Send); - }; - serverStub.Start(); - - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - try - { - session.Connect(); - Assert.Fail(); - } - catch (SshConnectionException ex) - { - Assert.IsNull(ex.InnerException); - Assert.AreEqual("Server version '666' is not supported.", ex.Message); - - Assert.AreEqual("SSH-666-SshStub", connectionInfo.ServerVersion); - } - } - } - } - - [TestMethod] - public void ConnectShouldImmediatelySendIdentificationStringWhenConnectionHasBeenEstablised() - { - var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); - var connectionInfo = CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5)); - - using (var serverStub = new AsyncSocketListener(serverEndPoint)) - { - serverStub.Connected += socket => - { - var identificationBytes = new byte[2048]; - var bytesReceived = socket.Receive(identificationBytes); - - if (bytesReceived > 0) - { - var identificationSttring = Encoding.ASCII.GetString(identificationBytes, 0, bytesReceived); - Console.WriteLine("STRING=" + identificationSttring); - Console.WriteLine("DONE"); - - socket.Send(Encoding.ASCII.GetBytes("\r\n")); - socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n")); - socket.Send(Encoding.ASCII.GetBytes("SSH-666-SshStub\r\n")); - } - - socket.Shutdown(SocketShutdown.Send); - }; - serverStub.Start(); - - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - try - { - session.Connect(); - Assert.Fail(); - } - catch (SshConnectionException ex) - { - Assert.IsNull(ex.InnerException); - Assert.AreEqual("Server version '666' is not supported.", ex.Message); - - Assert.AreEqual("SSH-666-SshStub", connectionInfo.ServerVersion); - } - } - } - } - - - [TestMethod] - public void ConnectShouldSupportProtocolIdentificationStringThatDoesNotEndWithCrlf() - { - var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); - var connectionInfo = CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5)); - - using (var serverStub = new AsyncSocketListener(serverEndPoint)) - { - serverStub.Connected += socket => - { - socket.Send(Encoding.ASCII.GetBytes("\r\n")); - socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n")); - socket.Send(Encoding.ASCII.GetBytes("SSH-666-SshStub")); - socket.Shutdown(SocketShutdown.Send); - }; - serverStub.Start(); - - using (var session = new Session(connectionInfo, _serviceFactoryMock.Object)) - { - try - { - session.Connect(); - Assert.Fail(); - } - catch (SshConnectionException ex) - { - Assert.IsNull(ex.InnerException); - Assert.AreEqual("Server version '666' is not supported.", ex.Message); - - Assert.AreEqual("SSH-666-SshStub", connectionInfo.ServerVersion); - } - } - } - } - - [TestMethod] - public void ConnectShouldThrowSshOperationExceptionWhenServerDoesNotRespondWithinConnectionTimeout() - { - var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); - var timeout = TimeSpan.FromMilliseconds(500); - Socket clientSocket = null; - - using (var serverStub = new AsyncSocketListener(serverEndPoint)) - { - serverStub.Connected += socket => - { - socket.Send(Encoding.ASCII.GetBytes("\r\n")); - socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n")); - clientSocket = socket; - }; - serverStub.Start(); - - using (var session = new Session(CreateConnectionInfo(serverEndPoint, TimeSpan.FromMilliseconds(500)), _serviceFactoryMock.Object)) - { - try - { - session.Connect(); - Assert.Fail(); - } - catch (SshOperationTimeoutException ex) - { - Assert.IsNull(ex.InnerException); - Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, "Socket read operation has timed out after {0:F0} milliseconds.", timeout.TotalMilliseconds), ex.Message); - - Assert.IsNotNull(clientSocket); - Assert.IsTrue(clientSocket.Connected); - - // shut down socket - clientSocket.Shutdown(SocketShutdown.Send); - } - } - } - } - - [TestMethod] - public void ConnectShouldSshConnectionExceptionWhenServerResponseDoesNotContainProtocolIdentificationString() - { - var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); - - // response ends with CRLF - using (var serverStub = new AsyncSocketListener(serverEndPoint)) - { - serverStub.Connected += socket => - { - socket.Send(Encoding.ASCII.GetBytes("\r\n")); - socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n")); - socket.Shutdown(SocketShutdown.Send); - }; - serverStub.Start(); - - using (var session = new Session(CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5)), _serviceFactoryMock.Object)) - { - try - { - session.Connect(); - Assert.Fail(); - } - catch (SshConnectionException ex) - { - Assert.IsNull(ex.InnerException); - Assert.AreEqual("Server response does not contain SSH protocol identification.", ex.Message); - } - } - } - - // response does not end with CRLF - using (var serverStub = new AsyncSocketListener(serverEndPoint)) - { - serverStub.Connected += socket => - { - socket.Send(Encoding.ASCII.GetBytes("\r\n")); - socket.Send(Encoding.ASCII.GetBytes("WELCOME banner")); - socket.Shutdown(SocketShutdown.Send); - }; - serverStub.Start(); - - using (var session = new Session(CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5)), _serviceFactoryMock.Object)) - { - try - { - session.Connect(); - Assert.Fail(); - } - catch (SshConnectionException ex) - { - Assert.IsNull(ex.InnerException); - Assert.AreEqual("Server response does not contain SSH protocol identification.", ex.Message); - } - } - } - - // last line is empty - using (var serverStub = new AsyncSocketListener(serverEndPoint)) - { - serverStub.Connected += socket => - { - socket.Send(Encoding.ASCII.GetBytes("\r\n")); - socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n")); - socket.Send(Encoding.ASCII.GetBytes("\r\n")); - socket.Shutdown(SocketShutdown.Send); - }; - serverStub.Start(); - - using (var session = new Session(CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5)), _serviceFactoryMock.Object)) - { - try - { - session.Connect(); - Assert.Fail(); - } - catch (SshConnectionException ex) - { - Assert.IsNull(ex.InnerException); - Assert.AreEqual("Server response does not contain SSH protocol identification.", ex.Message); - } - } - } - } - - [TestMethod] - public void Connect_HostNameInvalid_ShouldThrowSocketExceptionWithErrorCodeHostNotFound() - { - var connectionInfo = new ConnectionInfo("invalid.", 40, "user", - new KeyboardInteractiveAuthenticationMethod("user")); - var session = new Session(connectionInfo, _serviceFactoryMock.Object); + const ISocketFactory socketFactory = null; try { - session.Connect(); + new Session(connectionInfo, _serviceFactoryMock.Object, socketFactory); Assert.Fail(); } - catch (SocketException ex) + catch (ArgumentNullException ex) { - Assert.AreEqual(ex.ErrorCode, (int)SocketError.HostNotFound); + Assert.IsNull(ex.InnerException); + Assert.AreEqual("socketFactory", ex.ParamName); } } - [TestMethod] - public void Connect_ProxyHostNameInvalid_ShouldThrowSocketExceptionWithErrorCodeHostNotFound() - { - var connectionInfo = new ConnectionInfo("localhost", 40, "user", ProxyTypes.Http, "invalid.", 80, - "proxyUser", "proxyPwd", new KeyboardInteractiveAuthenticationMethod("user")); - var session = new Session(connectionInfo, _serviceFactoryMock.Object); - - try - { - session.Connect(); - Assert.Fail(); - } - catch (SocketException ex) - { - Assert.AreEqual(ex.ErrorCode, (int)SocketError.HostNotFound); - } - } - - [TestMethod] - public void DisconnectShouldNotThrowExceptionWhenSocketIsNotConnected() - { - var connectionInfo = new ConnectionInfo("localhost", 6767, Resources.USERNAME, - new KeyboardInteractiveAuthenticationMethod(Resources.USERNAME)); - var session = new Session(connectionInfo, _serviceFactoryMock.Object); - - try - { - session.Connect(); - Assert.Fail(); - } - catch (SocketException) - { - session.Disconnect(); - } - } - - [TestMethod] - public void DisconnectShouldNotThrowExceptionWhenConnectHasNotBeenInvoked() - { - var connectionInfo = new ConnectionInfo("localhost", 6767, Resources.USERNAME, - new KeyboardInteractiveAuthenticationMethod(Resources.USERNAME)); - var session = new Session(connectionInfo, _serviceFactoryMock.Object); - - session.Disconnect(); - } - - [TestMethod] - public void DisposeShouldNotThrowExceptionWhenSocketIsNotConnected() - { - var connectionInfo = new ConnectionInfo("localhost", 6767, Resources.USERNAME, - new KeyboardInteractiveAuthenticationMethod(Resources.USERNAME)); - var session = new Session(connectionInfo, _serviceFactoryMock.Object); - - try - { - session.Connect(); - Assert.Fail(); - } - catch (SocketException) - { - session.Dispose(); - } - } - - [TestMethod] - public void DisposeShouldNotThrowExceptionWhenConenectHasNotBeenInvoked() - { - var connectionInfo = new ConnectionInfo("localhost", 6767, Resources.USERNAME, - new KeyboardInteractiveAuthenticationMethod(Resources.USERNAME)); - var session = new Session(connectionInfo, _serviceFactoryMock.Object); - - session.Disconnect(); - } - private static ConnectionInfo CreateConnectionInfo(IPEndPoint serverEndPoint, TimeSpan timeout) { var connectionInfo = new ConnectionInfo( diff --git a/src/Renci.SshNet.Tests/Classes/SessionTestBase.cs b/src/Renci.SshNet.Tests/Classes/SessionTestBase.cs new file mode 100644 index 00000000..cbe69e2a --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/SessionTestBase.cs @@ -0,0 +1,35 @@ +using Moq; +using Renci.SshNet.Connection; +using Renci.SshNet.Tests.Common; + +namespace Renci.SshNet.Tests.Classes +{ + public abstract class SessionTestBase : TripleATestBase + { + internal Mock _serviceFactoryMock { get; private set; } + internal Mock _socketFactoryMock { get; private set; } + internal Mock _connectorMock { get; private set; } + + protected virtual void CreateMocks() + { + _serviceFactoryMock = new Mock(MockBehavior.Strict); + _socketFactoryMock = new Mock(MockBehavior.Strict); + _connectorMock = new Mock(MockBehavior.Strict); + } + + protected virtual void SetupData() + { + } + + protected virtual void SetupMocks() + { + } + + protected override void Arrange() + { + CreateMocks(); + SetupData(); + SetupMocks(); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_ConnectToServerFails.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_ConnectToServerFails.cs new file mode 100644 index 00000000..df19c8ec --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/SessionTest_ConnectToServerFails.cs @@ -0,0 +1,265 @@ +using System; +using System.Net; +using System.Threading; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Renci.SshNet.Common; +using Renci.SshNet.Messages.Transport; + +namespace Renci.SshNet.Tests.Classes +{ + [TestClass] + public class SessionTest_ConnectToServerFails : SessionTestBase + { + private ConnectionInfo _connectionInfo; + private Session _session; + private SshConnectionException _connectException; + private SshConnectionException _actualException; + + protected override void SetupData() + { + base.SetupData(); + + var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); + _connectionInfo = CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5)); + _session = new Session(_connectionInfo, _serviceFactoryMock.Object, _socketFactoryMock.Object); + _connectException = new SshConnectionException(); + } + + protected override void SetupMocks() + { + base.SetupMocks(); + + _serviceFactoryMock.Setup(p => p.CreateConnector(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_connectorMock.Object); + _connectorMock.Setup(p => p.Connect(_connectionInfo)) + .Throws(_connectException); + } + + protected override void Act() + { + try + { + _session.Connect(); + Assert.Fail(); + } + catch (SshConnectionException ex) + { + _actualException = ex; + } + } + + [TestMethod] + public void ClientVersionIsRenciSshNet() + { + Assert.AreEqual("SSH-2.0-Renci.SshNet.SshClient.0.0.1", _session.ClientVersion); + } + + [TestMethod] + public void ConnectionInfoShouldReturnConnectionInfoPassedThroughConstructor() + { + Assert.AreSame(_connectionInfo, _session.ConnectionInfo); + } + + public void ConnectShouldHaveRethrownException() + { + Assert.IsNotNull(_actualException); + Assert.AreSame(_connectException, _actualException); + } + + [TestMethod] + public void DisconnectShouldNotThrowAnException() + { + _session.Disconnect(); + } + + [TestMethod] + public void DisposeShouldNotThrowException() + { + _session.Dispose(); + } + + [TestMethod] + public void IsConnectedShouldReturnFalse() + { + Assert.IsFalse(_session.IsConnected); + } + + [TestMethod] + public void SendMessageShouldThrowShhConnectionException() + { + try + { + _session.SendMessage(new IgnoreMessage()); + Assert.Fail(); + } + catch (SshConnectionException ex) + { + Assert.AreEqual(DisconnectReason.None, ex.DisconnectReason); + Assert.IsNull(ex.InnerException); + Assert.AreEqual("Client not connected.", ex.Message); + } + } + + [TestMethod] + public void SessionIdShouldReturnNull() + { + Assert.IsNull(_session.SessionId); + } + + [TestMethod] + public void ServerVersionShouldReturnNull() + { + Assert.IsNull(_session.ServerVersion); + } + + [TestMethod] + public void WaitOnHandle_WaitOnHandle_WaitHandle_ShouldThrowArgumentNullExceptionWhenWaitHandleIsNull() + { + const WaitHandle waitHandle = null; + + try + { + _session.WaitOnHandle(waitHandle); + Assert.Fail(); + } + catch (ArgumentNullException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("waitHandle", ex.ParamName); + } + } + + [TestMethod] + public void WaitOnHandle_WaitOnHandle_WaitHandleAndTimeout_ShouldThrowArgumentNullExceptionWhenWaitHandleIsNull() + { + const WaitHandle waitHandle = null; + var timeout = TimeSpan.FromMinutes(5); + + try + { + _session.WaitOnHandle(waitHandle, timeout); + Assert.Fail(); + } + catch (ArgumentNullException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("waitHandle", ex.ParamName); + } + } + + [TestMethod] + public void ISession_TryWait_WaitHandleAndTimeout_ShouldReturnDisconnected() + { + var session = (ISession)_session; + var waitHandle = new ManualResetEvent(false); + + var result = session.TryWait(waitHandle, Session.InfiniteTimeSpan); + + Assert.AreEqual(WaitResult.Disconnected, result); + } + + [TestMethod] + public void ISession_TryWait_WaitHandleAndTimeoutAndException_ShouldReturnDisconnected() + { + var session = (ISession)_session; + var waitHandle = new ManualResetEvent(false); + Exception exception; + + var result = session.TryWait(waitHandle, Session.InfiniteTimeSpan, out exception); + + Assert.AreEqual(WaitResult.Disconnected, result); + Assert.IsNull(exception); + } + + [TestMethod] + public void ISession_ConnectionInfoShouldReturnConnectionInfoPassedThroughConstructor() + { + var session = (ISession)_session; + Assert.AreSame(_connectionInfo, session.ConnectionInfo); + } + + [TestMethod] + public void ISession_MessageListenerCompletedShouldBeSignaled() + { + var session = (ISession)_session; + + Assert.IsNotNull(session.MessageListenerCompleted); + Assert.IsTrue(session.MessageListenerCompleted.WaitOne(0)); + } + + [TestMethod] + public void ISession_SendMessageShouldThrowShhConnectionException() + { + var session = (ISession)_session; + + try + { + session.SendMessage(new IgnoreMessage()); + Assert.Fail(); + } + catch (SshConnectionException ex) + { + Assert.AreEqual(DisconnectReason.None, ex.DisconnectReason); + Assert.IsNull(ex.InnerException); + Assert.AreEqual("Client not connected.", ex.Message); + } + } + + [TestMethod] + public void ISession_TrySendMessageShouldReturnFalse() + { + var session = (ISession)_session; + + var actual = session.TrySendMessage(new IgnoreMessage()); + + Assert.IsFalse(actual); + } + + [TestMethod] + public void ISession_WaitOnHandle_WaitHandle_ShouldThrowArgumentNullExceptionWhenWaitHandleIsNull() + { + const WaitHandle waitHandle = null; + var session = (ISession)_session; + + try + { + session.WaitOnHandle(waitHandle); + Assert.Fail(); + } + catch (ArgumentNullException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("waitHandle", ex.ParamName); + } + } + + [TestMethod] + public void ISession_WaitOnHandle_WaitHandleAndTimeout_ShouldThrowArgumentNullExceptionWhenWaitHandleIsNull() + { + const WaitHandle waitHandle = null; + var session = (ISession)_session; + + try + { + session.WaitOnHandle(waitHandle, Session.InfiniteTimeSpan); + Assert.Fail(); + } + catch (ArgumentNullException ex) + { + Assert.IsNull(ex.InnerException); + Assert.AreEqual("waitHandle", ex.ParamName); + } + } + + private static ConnectionInfo CreateConnectionInfo(IPEndPoint serverEndPoint, TimeSpan timeout) + { + var connectionInfo = new ConnectionInfo( + serverEndPoint.Address.ToString(), + serverEndPoint.Port, + "eric", + new NoneAuthenticationMethod("eric")); + connectionInfo.Timeout = timeout; + return connectionInfo; + } + } +} \ No newline at end of file diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected.cs index 0d8f2412..fa36aed5 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; using Renci.SshNet.Messages.Transport; namespace Renci.SshNet.Tests.Classes @@ -65,7 +66,7 @@ namespace Renci.SshNet.Tests.Classes public void ServerVersionShouldNotReturnNull() { Assert.IsNotNull(Session.ServerVersion); - Assert.AreEqual("SSH-2.0-SshStub", Session.ServerVersion); + Assert.AreEqual("SSH-2.0-OurServerStub", Session.ServerVersion); } [TestMethod] @@ -251,5 +252,24 @@ namespace Renci.SshNet.Tests.Classes Assert.IsNull(exception); } } + + [TestMethod] + public void ClientSocketShouldBeConnected() + { + Assert.IsNotNull(ClientSocket); + Assert.IsTrue(ClientSocket.Connected); + } + + [TestMethod] + public void CreateConnectorOnServiceFactoryShouldHaveBeenInvokedOnce() + { + ServiceFactoryMock.Verify(p => p.CreateConnector(ConnectionInfo, SocketFactoryMock.Object), Times.Once()); + } + + [TestMethod] + public void ConnectorOnConnectorShouldHaveBeenInvokedOnce() + { + ConnectorMock.Verify(p => p.Connect(ConnectionInfo), Times.Once()); + } } } \ No newline at end of file diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_ConnectedBase.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_ConnectedBase.cs index 3e42dac1..a920b190 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_ConnectedBase.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest_ConnectedBase.cs @@ -9,6 +9,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Renci.SshNet.Common; using Renci.SshNet.Compression; +using Renci.SshNet.Connection; using Renci.SshNet.Messages; using Renci.SshNet.Messages.Transport; using Renci.SshNet.Security; @@ -20,11 +21,17 @@ namespace Renci.SshNet.Tests.Classes [TestClass] public abstract class SessionTest_ConnectedBase { - private Mock _serviceFactoryMock; + internal Mock ServiceFactoryMock { get; private set; } + internal Mock SocketFactoryMock { get; private set; } + internal Mock ConnectorMock { get; private set; } + + private Mock _protocolVersionExchangeMock; private Mock _keyExchangeMock; private Mock _clientAuthenticationMock; private IPEndPoint _serverEndPoint; private string _keyExchangeAlgorithm; + private bool _authenticationStarted; + private SocketFactory _socketFactory; protected Random Random { get; private set; } protected byte[] SessionId { get; private set; } @@ -35,7 +42,9 @@ namespace Renci.SshNet.Tests.Classes protected AsyncSocketListener ServerListener { get; private set; } protected IList ServerBytesReceivedRegister { get; private set; } protected Session Session { get; private set; } + protected Socket ClientSocket { get; private set; } protected Socket ServerSocket { get; private set; } + internal SshIdentification ServerIdentification { get; private set; } [TestInitialize] public void Setup() @@ -64,6 +73,12 @@ namespace Renci.SshNet.Tests.Classes Session.Dispose(); Session = null; } + + if (ClientSocket != null && ClientSocket.Connected) + { + ClientSocket.Shutdown(SocketShutdown.Both); + ClientSocket.Dispose(); + } } protected virtual void SetupData() @@ -84,8 +99,11 @@ namespace Renci.SshNet.Tests.Classes DisconnectReceivedRegister = new List>(); ErrorOccurredRegister = new List(); ServerBytesReceivedRegister = new List(); + ServerIdentification = new SshIdentification("2.0", "OurServerStub"); + _authenticationStarted = false; + _socketFactory = new SocketFactory(); - Session = new Session(ConnectionInfo, _serviceFactoryMock.Object); + Session = new Session(ConnectionInfo, ServiceFactoryMock.Object, SocketFactoryMock.Object); Session.Disconnected += (sender, args) => DisconnectedRegister.Add(args); Session.DisconnectReceived += (sender, args) => DisconnectReceivedRegister.Add(args); Session.ErrorOccured += (sender, args) => ErrorOccurredRegister.Add(args); @@ -102,56 +120,65 @@ namespace Renci.SshNet.Tests.Classes { ServerSocket = socket; - socket.Send(Encoding.ASCII.GetBytes("\r\n")); - socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n")); - socket.Send(Encoding.ASCII.GetBytes("SSH-2.0-SshStub\r\n")); + // Since we're mocking the protocol version exchange, we'll immediately stat KEX upon + // having established the connection instead of when the client has been identified + + var keyExchangeInitMessage = new KeyExchangeInitMessage + { + CompressionAlgorithmsClientToServer = new string[0], + CompressionAlgorithmsServerToClient = new string[0], + EncryptionAlgorithmsClientToServer = new string[0], + EncryptionAlgorithmsServerToClient = new string[0], + KeyExchangeAlgorithms = new[] { _keyExchangeAlgorithm }, + LanguagesClientToServer = new string[0], + LanguagesServerToClient = new string[0], + MacAlgorithmsClientToServer = new string[0], + MacAlgorithmsServerToClient = new string[0], + ServerHostKeyAlgorithms = new string[0] + }; + var keyExchangeInit = keyExchangeInitMessage.GetPacket(8, null); + ServerSocket.Send(keyExchangeInit, 4, keyExchangeInit.Length - 4, SocketFlags.None); }; - - var counter = 0; - ServerListener.BytesReceived += (received, socket) => { ServerBytesReceivedRegister.Add(received); - switch (counter++) + if (!_authenticationStarted) { - case 0: - var keyExchangeInitMessage = new KeyExchangeInitMessage - { - CompressionAlgorithmsClientToServer = new string[0], - CompressionAlgorithmsServerToClient = new string[0], - EncryptionAlgorithmsClientToServer = new string[0], - EncryptionAlgorithmsServerToClient = new string[0], - KeyExchangeAlgorithms = new[] {_keyExchangeAlgorithm}, - LanguagesClientToServer = new string[0], - LanguagesServerToClient = new string[0], - MacAlgorithmsClientToServer = new string[0], - MacAlgorithmsServerToClient = new string[0], - ServerHostKeyAlgorithms = new string[0] - }; - var keyExchangeInit = keyExchangeInitMessage.GetPacket(8, null); - ServerSocket.Send(keyExchangeInit, 4, keyExchangeInit.Length - 4, SocketFlags.None); - break; - case 1: - var serviceAcceptMessage = - ServiceAcceptMessageBuilder.Create(ServiceName.UserAuthentication) - .Build(); - ServerSocket.Send(serviceAcceptMessage, 0, serviceAcceptMessage.Length, SocketFlags.None); - break; + var serviceAcceptMessage = ServiceAcceptMessageBuilder.Create(ServiceName.UserAuthentication) + .Build(); + ServerSocket.Send(serviceAcceptMessage, 0, serviceAcceptMessage.Length, SocketFlags.None); + + _authenticationStarted = true; } }; + ServerListener.Start(); + + ClientSocket = new DirectConnector(_socketFactory).Connect(ConnectionInfo); } private void CreateMocks() { - _serviceFactoryMock = new Mock(MockBehavior.Strict); + ServiceFactoryMock = new Mock(MockBehavior.Strict); + SocketFactoryMock = new Mock(MockBehavior.Strict); + ConnectorMock = new Mock(MockBehavior.Strict); + _protocolVersionExchangeMock = new Mock(MockBehavior.Strict); _keyExchangeMock = new Mock(MockBehavior.Strict); _clientAuthenticationMock = new Mock(MockBehavior.Strict); } private void SetupMocks() { - _serviceFactoryMock.Setup( + ServiceFactoryMock.Setup(p => p.CreateConnector(ConnectionInfo, SocketFactoryMock.Object)) + .Returns(ConnectorMock.Object); + ConnectorMock.Setup(p => p.Connect(ConnectionInfo)) + .Returns(ClientSocket); + ServiceFactoryMock.Setup(p => p.CreateProtocolVersionExchange()) + .Returns(_protocolVersionExchangeMock.Object); + _protocolVersionExchangeMock.Setup(p => p.Start(Session.ClientVersion, ClientSocket, ConnectionInfo.Timeout)) + .Returns(ServerIdentification); + + ServiceFactoryMock.Setup( p => p.CreateKeyExchange(ConnectionInfo.KeyExchangeAlgorithms, new[] { _keyExchangeAlgorithm })).Returns(_keyExchangeMock.Object); _keyExchangeMock.Setup(p => p.Name).Returns(_keyExchangeAlgorithm); @@ -164,19 +191,18 @@ namespace Renci.SshNet.Tests.Classes _keyExchangeMock.Setup(p => p.CreateCompressor()).Returns((Compressor) null); _keyExchangeMock.Setup(p => p.CreateDecompressor()).Returns((Compressor) null); _keyExchangeMock.Setup(p => p.Dispose()); - _serviceFactoryMock.Setup(p => p.CreateClientAuthentication()) + ServiceFactoryMock.Setup(p => p.CreateClientAuthentication()) .Callback(ClientAuthentication_Callback) .Returns(_clientAuthenticationMock.Object); _clientAuthenticationMock.Setup(p => p.Authenticate(ConnectionInfo, Session)); } - protected virtual void Arrange() + protected void Arrange() { CreateMocks(); SetupData(); SetupMocks(); - ServerListener.Start(); Session.Connect(); } diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_Disconnect.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_Disconnect.cs index 3aed3027..eacb27c3 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_Disconnect.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_Disconnect.cs @@ -5,6 +5,7 @@ using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Common; using Renci.SshNet.Messages.Transport; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes { @@ -49,7 +50,7 @@ namespace Renci.SshNet.Tests.Classes [TestMethod] public void ErrorOccurredIsNeverRaised() { - Assert.AreEqual(0, ErrorOccurredRegister.Count); + Assert.AreEqual(0, ErrorOccurredRegister.Count, ErrorOccurredRegister.AsString()); } [TestMethod] @@ -188,5 +189,12 @@ namespace Renci.SshNet.Tests.Classes Assert.AreEqual(WaitResult.Disconnected, result); Assert.IsNull(exception); } + + [TestMethod] + public void ClientSocketShouldNotBeConnected() + { + Assert.IsNotNull(ClientSocket); + Assert.IsFalse(ClientSocket.Connected); + } } } \ No newline at end of file diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_GlobalRequestMessageAfterAuthenticationRace.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_GlobalRequestMessageAfterAuthenticationRace.cs index a78154f7..a8ce5c96 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_GlobalRequestMessageAfterAuthenticationRace.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_GlobalRequestMessageAfterAuthenticationRace.cs @@ -2,6 +2,7 @@ using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Messages.Connection; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes { @@ -33,7 +34,7 @@ namespace Renci.SshNet.Tests.Classes [TestMethod] public void ErrorOccurredShouldNotBeRaised() { - Assert.AreEqual(0, ErrorOccurredRegister.Count); + Assert.AreEqual(0, ErrorOccurredRegister.Count, ErrorOccurredRegister.AsString()); } } } diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerAndClientDisconnectRace.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerAndClientDisconnectRace.cs index 5729c022..68bcbdaa 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerAndClientDisconnectRace.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerAndClientDisconnectRace.cs @@ -4,11 +4,11 @@ using System.Globalization; using System.Net; using System.Net.Sockets; using System.Security.Cryptography; -using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Renci.SshNet.Common; using Renci.SshNet.Compression; +using Renci.SshNet.Connection; using Renci.SshNet.Messages; using Renci.SshNet.Messages.Transport; using Renci.SshNet.Security; @@ -21,11 +21,16 @@ namespace Renci.SshNet.Tests.Classes public class SessionTest_Connected_ServerAndClientDisconnectRace { private Mock _serviceFactoryMock; + private Mock _socketFactoryMock; + private Mock _connectorMock; + private Mock _protocolVersionExchangeMock; private Mock _keyExchangeMock; private Mock _clientAuthenticationMock; private IPEndPoint _serverEndPoint; private string _keyExchangeAlgorithm; private DisconnectMessage _disconnectMessage; + private SocketFactory _socketFactory; + private bool _authenticationStarted; protected Random Random { get; private set; } protected byte[] SessionId { get; private set; } @@ -36,7 +41,9 @@ namespace Renci.SshNet.Tests.Classes protected AsyncSocketListener ServerListener { get; private set; } protected IList ServerBytesReceivedRegister { get; private set; } protected Session Session { get; private set; } + protected Socket ClientSocket { get; private set; } protected Socket ServerSocket { get; private set; } + internal SshIdentification ServerIdentification { get; private set; } private void TearDown() { @@ -49,6 +56,12 @@ namespace Renci.SshNet.Tests.Classes { Session.Dispose(); } + + if (ClientSocket != null && ClientSocket.Connected) + { + ClientSocket.Shutdown(SocketShutdown.Both); + ClientSocket.Dispose(); + } } protected virtual void SetupData() @@ -69,39 +82,31 @@ namespace Renci.SshNet.Tests.Classes DisconnectReceivedRegister = new List>(); ErrorOccurredRegister = new List(); ServerBytesReceivedRegister = new List(); + ServerIdentification = new SshIdentification("2.0", "OurServerStub"); + _authenticationStarted = false; _disconnectMessage = new DisconnectMessage(DisconnectReason.ServiceNotAvailable, "Not today!"); + _socketFactory = new SocketFactory(); - Session = new Session(ConnectionInfo, _serviceFactoryMock.Object); + Session = new Session(ConnectionInfo, _serviceFactoryMock.Object, _socketFactoryMock.Object); Session.Disconnected += (sender, args) => DisconnectedRegister.Add(args); Session.DisconnectReceived += (sender, args) => DisconnectReceivedRegister.Add(args); Session.ErrorOccured += (sender, args) => ErrorOccurredRegister.Add(args); Session.KeyExchangeInitReceived += (sender, args) => - { - var newKeysMessage = new NewKeysMessage(); - var newKeys = newKeysMessage.GetPacket(8, null); - ServerSocket.Send(newKeys, 4, newKeys.Length - 4, SocketFlags.None); - }; + { + var newKeysMessage = new NewKeysMessage(); + var newKeys = newKeysMessage.GetPacket(8, null); + ServerSocket.Send(newKeys, 4, newKeys.Length - 4, SocketFlags.None); + }; ServerListener = new AsyncSocketListener(_serverEndPoint); ServerListener.Connected += socket => - { - ServerSocket = socket; - - socket.Send(Encoding.ASCII.GetBytes("\r\n")); - socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n")); - socket.Send(Encoding.ASCII.GetBytes("SSH-2.0-SshStub\r\n")); - }; - - var counter = 0; - - ServerListener.BytesReceived += (received, socket) => - { - ServerBytesReceivedRegister.Add(received); - - switch (counter++) { - case 0: - var keyExchangeInitMessage = new KeyExchangeInitMessage + ServerSocket = socket; + + // Since we're mocking the protocol version exchange, we'll immediately stat KEX upon + // having established the connection instead of when the client has been identified + + var keyExchangeInitMessage = new KeyExchangeInitMessage { CompressionAlgorithmsClientToServer = new string[0], CompressionAlgorithmsServerToClient = new string[0], @@ -114,26 +119,46 @@ namespace Renci.SshNet.Tests.Classes MacAlgorithmsServerToClient = new string[0], ServerHostKeyAlgorithms = new string[0] }; - var keyExchangeInit = keyExchangeInitMessage.GetPacket(8, null); - ServerSocket.Send(keyExchangeInit, 4, keyExchangeInit.Length - 4, SocketFlags.None); - break; - case 1: + var keyExchangeInit = keyExchangeInitMessage.GetPacket(8, null); + ServerSocket.Send(keyExchangeInit, 4, keyExchangeInit.Length - 4, SocketFlags.None); + }; + ServerListener.BytesReceived += (received, socket) => + { + ServerBytesReceivedRegister.Add(received); + + if (!_authenticationStarted) + { var serviceAcceptMessage =ServiceAcceptMessageBuilder.Create(ServiceName.UserAuthentication).Build(); ServerSocket.Send(serviceAcceptMessage, 0, serviceAcceptMessage.Length, SocketFlags.None); - break; - } - }; + _authenticationStarted = true; + } + }; + + ServerListener.Start(); + + ClientSocket = new DirectConnector(_socketFactory).Connect(ConnectionInfo); } private void CreateMocks() { _serviceFactoryMock = new Mock(MockBehavior.Strict); + _socketFactoryMock = new Mock(MockBehavior.Strict); + _connectorMock = new Mock(MockBehavior.Strict); + _protocolVersionExchangeMock = new Mock(MockBehavior.Strict); _keyExchangeMock = new Mock(MockBehavior.Strict); _clientAuthenticationMock = new Mock(MockBehavior.Strict); } private void SetupMocks() { + _serviceFactoryMock.Setup(p => p.CreateConnector(ConnectionInfo, _socketFactoryMock.Object)) + .Returns(_connectorMock.Object); + _connectorMock.Setup(p => p.Connect(ConnectionInfo)) + .Returns(ClientSocket); + _serviceFactoryMock.Setup(p => p.CreateProtocolVersionExchange()) + .Returns(_protocolVersionExchangeMock.Object); + _protocolVersionExchangeMock.Setup(p => p.Start(Session.ClientVersion, ClientSocket, ConnectionInfo.Timeout)) + .Returns(ServerIdentification); _serviceFactoryMock.Setup( p => p.CreateKeyExchange(ConnectionInfo.KeyExchangeAlgorithms, new[] { _keyExchangeAlgorithm })).Returns(_keyExchangeMock.Object); @@ -157,7 +182,6 @@ namespace Renci.SshNet.Tests.Classes SetupData(); SetupMocks(); - ServerListener.Start(); Session.Connect(); } diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessage.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessage.cs index 1b887dc6..36a73d82 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessage.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessage.cs @@ -6,6 +6,7 @@ using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Common; using Renci.SshNet.Messages.Transport; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes { @@ -70,7 +71,7 @@ namespace Renci.SshNet.Tests.Classes [TestMethod] public void ErrorOccurredIsNeverRaised() { - Assert.AreEqual(0, ErrorOccurredRegister.Count); + Assert.AreEqual(0, ErrorOccurredRegister.Count, ErrorOccurredRegister.AsString()); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessageAndShutsDownSocket.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessageAndShutsDownSocket.cs index d8a50a3d..f3bb9877 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessageAndShutsDownSocket.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsDisconnectMessageAndShutsDownSocket.cs @@ -6,6 +6,7 @@ using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Common; using Renci.SshNet.Messages.Transport; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes { @@ -73,7 +74,7 @@ namespace Renci.SshNet.Tests.Classes [TestMethod] public void ErrorOccurredIsNeverRaised() { - Assert.AreEqual(0, ErrorOccurredRegister.Count); + Assert.AreEqual(0, ErrorOccurredRegister.Count, ErrorOccurredRegister.AsString()); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsUnsupportedMessageType.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsUnsupportedMessageType.cs index 02e998a4..a40f6e03 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsUnsupportedMessageType.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest_Connected_ServerSendsUnsupportedMessageType.cs @@ -5,6 +5,7 @@ using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using Renci.SshNet.Common; using Renci.SshNet.Messages.Transport; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes { @@ -66,7 +67,7 @@ namespace Renci.SshNet.Tests.Classes [TestMethod] public void ErrorOccurredIsRaisedOnce() { - Assert.AreEqual(1, ErrorOccurredRegister.Count); + Assert.AreEqual(1, ErrorOccurredRegister.Count, ErrorOccurredRegister.AsString()); var errorOccurred = ErrorOccurredRegister[0]; Assert.IsNotNull(errorOccurred); diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_NotConnected.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_NotConnected.cs index 4a8b3ded..97a49a0e 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_NotConnected.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest_NotConnected.cs @@ -4,34 +4,27 @@ using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Renci.SshNet.Common; +using Renci.SshNet.Connection; using Renci.SshNet.Messages.Transport; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SessionTest_NotConnected + public class SessionTest_NotConnected : SessionTestBase { private ConnectionInfo _connectionInfo; - private IServiceFactory _serviceFactory; private Session _session; - [TestInitialize] - public void Setup() - { - Arrange(); - Act(); - } - - protected void Arrange() + protected override void SetupData() { var serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); _connectionInfo = CreateConnectionInfo(serverEndPoint, TimeSpan.FromSeconds(5)); - _serviceFactory = new Mock(MockBehavior.Strict).Object; } - protected void Act() + protected override void Act() { - _session = new Session(_connectionInfo, _serviceFactory); + _session = new Session(_connectionInfo, _serviceFactoryMock.Object, _socketFactoryMock.Object); } [TestMethod] @@ -46,6 +39,18 @@ namespace Renci.SshNet.Tests.Classes Assert.AreSame(_connectionInfo, _session.ConnectionInfo); } + [TestMethod] + public void DisconnectShouldNotThrowException() + { + _session.Disconnect(); + } + + [TestMethod] + public void DisposeShouldNotThrowException() + { + _session.Dispose(); + } + [TestMethod] public void IsConnectedShouldReturnFalse() { diff --git a/src/Renci.SshNet.Tests/Classes/SessionTest_SocketConnected_BadPacketAndDispose.cs b/src/Renci.SshNet.Tests/Classes/SessionTest_SocketConnected_BadPacketAndDispose.cs index a13de281..f5fb4d79 100644 --- a/src/Renci.SshNet.Tests/Classes/SessionTest_SocketConnected_BadPacketAndDispose.cs +++ b/src/Renci.SshNet.Tests/Classes/SessionTest_SocketConnected_BadPacketAndDispose.cs @@ -1,10 +1,10 @@ using System; using System.Net; using System.Net.Sockets; -using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Renci.SshNet.Common; +using Renci.SshNet.Connection; using Renci.SshNet.Messages.Transport; using Renci.SshNet.Tests.Common; @@ -14,12 +14,17 @@ namespace Renci.SshNet.Tests.Classes public class SessionTest_SocketConnected_BadPacketAndDispose { private Mock _serviceFactoryMock; + private Mock _socketFactoryMock; + private Mock _connectorMock; + private Mock _protocolVersionExchangeMock; private ConnectionInfo _connectionInfo; private Session _session; private AsyncSocketListener _serverListener; private IPEndPoint _serverEndPoint; private Socket _serverSocket; + private Socket _clientSocket; private SshConnectionException _actualException; + private SocketFactory _socketFactory; [TestInitialize] public void Setup() @@ -37,7 +42,15 @@ namespace Renci.SshNet.Tests.Classes } } - protected void Arrange() + protected void CreateMocks() + { + _serviceFactoryMock = new Mock(MockBehavior.Strict); + _socketFactoryMock = new Mock(MockBehavior.Strict); + _connectorMock = new Mock(MockBehavior.Strict); + _protocolVersionExchangeMock = new Mock(MockBehavior.Strict); + } + + protected void SetupData() { _serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8122); _connectionInfo = new ConnectionInfo( @@ -47,32 +60,51 @@ namespace Renci.SshNet.Tests.Classes new PasswordAuthenticationMethod("user", "password")); _connectionInfo.Timeout = TimeSpan.FromMilliseconds(200); _actualException = null; - - _serviceFactoryMock = new Mock(MockBehavior.Strict); + _socketFactory = new SocketFactory(); _serverListener = new AsyncSocketListener(_serverEndPoint); _serverListener.Connected += (socket) => { _serverSocket = socket; - socket.Send(Encoding.ASCII.GetBytes("\r\n")); - socket.Send(Encoding.ASCII.GetBytes("WELCOME banner\r\n")); - socket.Send(Encoding.ASCII.GetBytes("SSH-2.0-SshStub\r\n")); - }; - _serverListener.BytesReceived += (received, socket) => - { - var badPacket = new byte[] {0x0a, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05}; + // Since we're mocking the protocol version exchange, we can immediately send the bad + // packet upon establishing the connection + + var badPacket = new byte[] { 0x0a, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05 }; _serverSocket.Send(badPacket, 0, badPacket.Length, SocketFlags.None); _serverSocket.Shutdown(SocketShutdown.Send); }; _serverListener.Start(); + + _session = new Session(_connectionInfo, _serviceFactoryMock.Object, _socketFactoryMock.Object); + + _clientSocket = new DirectConnector(_socketFactory).Connect(_connectionInfo); + } + + protected void SetupMocks() + { + _serviceFactoryMock.Setup(p => p.CreateConnector(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_connectorMock.Object); + _connectorMock.Setup(p => p.Connect(_connectionInfo)) + .Returns(_clientSocket); + _serviceFactoryMock.Setup(p => p.CreateProtocolVersionExchange()) + .Returns(_protocolVersionExchangeMock.Object); + _protocolVersionExchangeMock.Setup(p => p.Start(_session.ClientVersion, _clientSocket, _connectionInfo.Timeout)) + .Returns(new SshIdentification("2.0", "XXX")); + } + + protected void Arrange() + { + CreateMocks(); + SetupData(); + SetupMocks(); } protected virtual void Act() { try { - using (_session = new Session(_connectionInfo, _serviceFactoryMock.Object)) + { _session.Connect(); } diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTestBase.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTestBase.cs index fe73f9a6..2f2f1bbf 100644 --- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTestBase.cs +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTestBase.cs @@ -38,7 +38,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp protected static SftpFileAttributes CreateSftpFileAttributes(long size) { - return new SftpFileAttributes(default(DateTime), default(DateTime), size, default(int), default(int), default(uint), null); + var utcDefault = DateTime.SpecifyKind(default(DateTime), DateTimeKind.Utc); + return new SftpFileAttributes(utcDefault, utcDefault, size, default(int), default(int), default(uint), null); } protected static byte[] CreateByteArray(Random random, int length) diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Closed_FileAccessRead.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Closed_FileAccessRead.cs index 002b97b0..aaa62c85 100644 --- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Closed_FileAccessRead.cs +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_CanRead_Closed_FileAccessRead.cs @@ -24,7 +24,7 @@ namespace Renci.SshNet.Tests.Classes.Sftp var random = new Random(); _path = random.Next().ToString(); _handle = GenerateRandom(3, random); - _bufferSize = (uint) random.Next(0, 1000); + _bufferSize = (uint) random.Next(1, 1000); _readBufferSize = (uint) random.Next(0, 1000); _writeBufferSize = (uint) random.Next(0, 1000); } diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeAppend_FileAccessWrite.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeAppend_FileAccessWrite.cs index d8c4904c..87183067 100644 --- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeAppend_FileAccessWrite.cs +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Ctor_FileModeAppend_FileAccessWrite.cs @@ -34,8 +34,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp _readBufferSize = (uint) _random.Next(5, 1000); _writeBufferSize = (uint) _random.Next(5, 1000); _handle = GenerateRandom(_random.Next(1, 10), _random); - _fileAttributes = new SftpFileAttributesBuilder().WithLastAccessTime(DateTime.Now.AddSeconds(_random.Next())) - .WithLastWriteTime(DateTime.Now.AddSeconds(_random.Next())) + _fileAttributes = new SftpFileAttributesBuilder().WithLastAccessTime(DateTime.UtcNow.AddSeconds(_random.Next())) + .WithLastWriteTime(DateTime.UtcNow.AddSeconds(_random.Next())) .WithSize(_random.Next()) .WithUserId(_random.Next()) .WithGroupId(_random.Next()) diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs index aee4b276..59a0135f 100644 --- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthGreatherThanPosition.cs @@ -53,8 +53,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp _fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC") .WithExtension("V", "VValue") .WithGroupId(random.Next()) - .WithLastAccessTime(DateTime.Now.AddSeconds(random.Next())) - .WithLastWriteTime(DateTime.Now.AddSeconds(random.Next())) + .WithLastAccessTime(DateTime.UtcNow.AddSeconds(random.Next())) + .WithLastWriteTime(DateTime.UtcNow.AddSeconds(random.Next())) .WithPermissions((uint) random.Next()) .WithSize(_length + 100) .WithUserId(random.Next()) @@ -201,4 +201,4 @@ namespace Renci.SshNet.Tests.Classes.Sftp SftpSessionMock.Verify(p => p.IsOpen, Times.Exactly(4)); } } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs index ae3952cc..a6116339 100644 --- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInReadBuffer_NewLengthLessThanPosition.cs @@ -50,8 +50,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp _fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC") .WithExtension("V", "VValue") .WithGroupId(random.Next()) - .WithLastAccessTime(DateTime.Now.AddSeconds(random.Next())) - .WithLastWriteTime(DateTime.Now.AddSeconds(random.Next())) + .WithLastAccessTime(DateTime.UtcNow.AddSeconds(random.Next())) + .WithLastWriteTime(DateTime.UtcNow.AddSeconds(random.Next())) .WithPermissions((uint)random.Next()) .WithSize(_length + 100) .WithUserId(random.Next()) diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs index 5a19aa3e..c71b43c8 100644 --- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthGreatherThanPosition.cs @@ -54,8 +54,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp _fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC") .WithExtension("V", "VValue") .WithGroupId(random.Next()) - .WithLastAccessTime(DateTime.Now.AddSeconds(random.Next())) - .WithLastWriteTime(DateTime.Now.AddSeconds(random.Next())) + .WithLastAccessTime(DateTime.UtcNow.AddSeconds(random.Next())) + .WithLastWriteTime(DateTime.UtcNow.AddSeconds(random.Next())) .WithPermissions((uint)random.Next()) .WithSize(_length + 100) .WithUserId(random.Next()) diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs index cfcf2315..96ad14b1 100644 --- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_DataInWriteBuffer_NewLengthLessThanPosition.cs @@ -54,8 +54,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp _fileAttributes = new SftpFileAttributesBuilder().WithExtension("X", "ABC") .WithExtension("V", "VValue") .WithGroupId(random.Next()) - .WithLastAccessTime(DateTime.Now.AddSeconds(random.Next())) - .WithLastWriteTime(DateTime.Now.AddSeconds(random.Next())) + .WithLastAccessTime(DateTime.UtcNow.AddSeconds(random.Next())) + .WithLastWriteTime(DateTime.UtcNow.AddSeconds(random.Next())) .WithPermissions((uint) random.Next()) .WithSize(_length + 100) .WithUserId(random.Next()) diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessReadWrite.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessReadWrite.cs index 0fde319a..ef89483b 100644 --- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessReadWrite.cs +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessReadWrite.cs @@ -48,8 +48,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp _writeBufferSize = (uint) random.Next(0, 1000); _length = random.Next(); - _fileAttributesLastAccessTime = DateTime.Now.AddSeconds(random.Next()); - _fileAttributesLastWriteTime = DateTime.Now.AddSeconds(random.Next()); + _fileAttributesLastAccessTime = DateTime.UtcNow.AddSeconds(random.Next()); + _fileAttributesLastWriteTime = DateTime.UtcNow.AddSeconds(random.Next()); _fileAttributesSize = random.Next(); _fileAttributesUserId = random.Next(); _fileAttributesGroupId = random.Next(); diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessWrite.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessWrite.cs index ce2619a5..73b5a953 100644 --- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessWrite.cs +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_SetLength_SessionOpen_FIleAccessWrite.cs @@ -48,8 +48,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp _writeBufferSize = (uint) random.Next(0, 1000); _length = random.Next(); - _fileAttributesLastAccessTime = DateTime.Now.AddSeconds(random.Next()); - _fileAttributesLastWriteTime = DateTime.Now.AddSeconds(random.Next()); + _fileAttributesLastAccessTime = DateTime.UtcNow.AddSeconds(random.Next()); + _fileAttributesLastWriteTime = DateTime.UtcNow.AddSeconds(random.Next()); _fileAttributesSize = random.Next(); _fileAttributesUserId = random.Next(); _fileAttributesGroupId = random.Next(); diff --git a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Write_SessionOpen_CountGreatherThanTwoTimesTheWriteBufferSize.cs b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Write_SessionOpen_CountGreatherThanTwoTimesTheWriteBufferSize.cs index 68020d55..e7d53706 100644 --- a/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Write_SessionOpen_CountGreatherThanTwoTimesTheWriteBufferSize.cs +++ b/src/Renci.SshNet.Tests/Classes/Sftp/SftpFileStreamTest_Write_SessionOpen_CountGreatherThanTwoTimesTheWriteBufferSize.cs @@ -119,8 +119,8 @@ namespace Renci.SshNet.Tests.Classes.Sftp [TestMethod] public void LengthShouldFlushBufferAndReturnSizeOfFile() { - var lengthFileAttributes = new SftpFileAttributes(DateTime.Now, - DateTime.Now, + var lengthFileAttributes = new SftpFileAttributes(DateTime.UtcNow, + DateTime.UtcNow, _random.Next(), _random.Next(), _random.Next(), diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest.cs index 3edd7c7b..f562f66b 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest.cs +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest.cs @@ -1076,7 +1076,9 @@ namespace Renci.SshNet.Tests.Classes SftpClient target = new SftpClient(connectionInfo); // TODO: Initialize to an appropriate value string path = string.Empty; // TODO: Initialize to an appropriate value DateTime lastAccessTime = new DateTime(); // TODO: Initialize to an appropriate value +#pragma warning disable CS0618 // Type or member is obsolete target.SetLastAccessTime(path, lastAccessTime); +#pragma warning restore CS0618 // Type or member is obsolete Assert.Inconclusive("A method that does not return a value cannot be verified."); } @@ -1091,7 +1093,9 @@ namespace Renci.SshNet.Tests.Classes SftpClient target = new SftpClient(connectionInfo); // TODO: Initialize to an appropriate value string path = string.Empty; // TODO: Initialize to an appropriate value DateTime lastAccessTimeUtc = new DateTime(); // TODO: Initialize to an appropriate value +#pragma warning disable CS0618 // Type or member is obsolete target.SetLastAccessTimeUtc(path, lastAccessTimeUtc); +#pragma warning restore CS0618 // Type or member is obsolete Assert.Inconclusive("A method that does not return a value cannot be verified."); } @@ -1106,7 +1110,9 @@ namespace Renci.SshNet.Tests.Classes SftpClient target = new SftpClient(connectionInfo); // TODO: Initialize to an appropriate value string path = string.Empty; // TODO: Initialize to an appropriate value DateTime lastWriteTime = new DateTime(); // TODO: Initialize to an appropriate value +#pragma warning disable CS0618 // Type or member is obsolete target.SetLastWriteTime(path, lastWriteTime); +#pragma warning restore CS0618 // Type or member is obsolete Assert.Inconclusive("A method that does not return a value cannot be verified."); } @@ -1121,7 +1127,9 @@ namespace Renci.SshNet.Tests.Classes SftpClient target = new SftpClient(connectionInfo); // TODO: Initialize to an appropriate value string path = string.Empty; // TODO: Initialize to an appropriate value DateTime lastWriteTimeUtc = new DateTime(); // TODO: Initialize to an appropriate value +#pragma warning disable CS0618 // Type or member is obsolete target.SetLastWriteTimeUtc(path, lastWriteTimeUtc); +#pragma warning restore CS0618 // Type or member is obsolete Assert.Inconclusive("A method that does not return a value cannot be verified."); } @@ -1403,4 +1411,4 @@ namespace Renci.SshNet.Tests.Classes public SftpDownloadAsyncResult DownloadResult { get; set; } } } -} \ No newline at end of file +} diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTestBase.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTestBase.cs new file mode 100644 index 00000000..11657962 --- /dev/null +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTestBase.cs @@ -0,0 +1,19 @@ +using Moq; +using Renci.SshNet.Sftp; + +namespace Renci.SshNet.Tests.Classes +{ + public abstract class SftpClientTestBase : BaseClientTestBase + { + internal Mock _sftpResponseFactoryMock { get; private set; } + internal Mock _sftpSessionMock { get; private set; } + + protected override void CreateMocks() + { + base.CreateMocks(); + + _sftpResponseFactoryMock = new Mock(MockBehavior.Strict); + _sftpSessionMock = new Mock(MockBehavior.Strict); + } + } +} diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Connect_SftpSessionConnectFailure.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Connect_SftpSessionConnectFailure.cs index afbc247b..b3bcb8e8 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Connect_SftpSessionConnectFailure.cs +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Connect_SftpSessionConnectFailure.cs @@ -4,59 +4,35 @@ using System.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Renci.SshNet.Common; +using Renci.SshNet.Connection; using Renci.SshNet.Security; using Renci.SshNet.Sftp; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SftpClientTest_Connect_SftpSessionConnectFailure + public class SftpClientTest_Connect_SftpSessionConnectFailure : SftpClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; - private Mock _sftpResponseFactoryMock; - private Mock _sftpSessionMock; private ConnectionInfo _connectionInfo; private ApplicationException _sftpSessionConnectionException; private SftpClient _sftpClient; private ApplicationException _actualException; - [TestInitialize] - public void Setup() - { - Arrange(); - Act(); - } - - private void Arrange() - { - SetupData(); - CreateMocks(); - SetupMocks(); - - _sftpClient = new SftpClient(_connectionInfo, false, _serviceFactoryMock.Object); - } - - private void SetupData() + protected override void SetupData() { _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); _sftpSessionConnectionException = new ApplicationException(); } - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _sftpResponseFactoryMock = new Mock(MockBehavior.Strict); - _sftpSessionMock = new Mock(MockBehavior.Strict); - } - - private void SetupMocks() + protected override void SetupMocks() { var sequence = new MockSequence(); _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence) .Setup(p => p.Connect()); @@ -75,7 +51,14 @@ namespace Renci.SshNet.Tests.Classes .Setup(p => p.Dispose()); } - private void Act() + protected override void Arrange() + { + base.Arrange(); + + _sftpClient = new SftpClient(_connectionInfo, false, _serviceFactoryMock.Object); + } + + protected override void Act() { try { diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Connected.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Connected.cs index ba7d080f..239c8493 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Connected.cs +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Connected.cs @@ -6,44 +6,30 @@ using Renci.SshNet.Sftp; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SftpClientTest_Dispose_Connected + public class SftpClientTest_Dispose_Connected : SftpClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; - private Mock _sftpResponseFactoryMock; - private Mock _sftpSessionMock; private SftpClient _sftpClient; private ConnectionInfo _connectionInfo; private int _operationTimeout; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); - } - - [TestCleanup] - public void Cleanup() - { - } - - protected void Arrange() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _sftpResponseFactoryMock = new Mock(MockBehavior.Strict); - _sftpSessionMock = new Mock(MockBehavior.Strict); - _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); _operationTimeout = new Random().Next(1000, 10000); _sftpClient = new SftpClient(_connectionInfo, false, _serviceFactoryMock.Object); _sftpClient.OperationTimeout = TimeSpan.FromMilliseconds(_operationTimeout); + } + protected override void SetupMocks() + { var sequence = new MockSequence(); + _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _serviceFactoryMock.InSequence(sequence) .Setup(p => p.CreateSftpResponseFactory()) @@ -55,11 +41,16 @@ namespace Renci.SshNet.Tests.Classes _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting()); _sftpSessionMock.InSequence(sequence).Setup(p => p.Dispose()); _sessionMock.InSequence(sequence).Setup(p => p.Dispose()); + } + + protected override void Arrange() + { + base.Arrange(); _sftpClient.Connect(); } - protected void Act() + protected override void Act() { _sftpClient.Dispose(); } @@ -78,10 +69,17 @@ namespace Renci.SshNet.Tests.Classes Times.Once); } + [TestMethod] + public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce() + { + _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once); + } + [TestMethod] public void CreateSessionOnServiceFactoryShouldBeInvokedOnce() { - _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once); + _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object), + Times.Once); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Disconnected.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Disconnected.cs index 516b8aeb..192c5e95 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Disconnected.cs +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Disconnected.cs @@ -6,40 +6,31 @@ using Renci.SshNet.Sftp; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SftpClientTest_Dispose_Disconnected + public class SftpClientTest_Dispose_Disconnected : SftpClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; - private Mock _sftpResponseFactoryMock; - private Mock _sftpSessionMock; private SftpClient _sftpClient; private ConnectionInfo _connectionInfo; private int _operationTimeout; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); - } - - protected void Arrange() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _sftpResponseFactoryMock = new Mock(MockBehavior.Strict); - _sftpSessionMock = new Mock(MockBehavior.Strict); - _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); _operationTimeout = new Random().Next(1000, 10000); _sftpClient = new SftpClient(_connectionInfo, false, _serviceFactoryMock.Object); _sftpClient.OperationTimeout = TimeSpan.FromMilliseconds(_operationTimeout); + } + protected override void SetupMocks() + { var sequence = new MockSequence(); + _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _serviceFactoryMock.InSequence(sequence) .Setup(p => p.CreateSftpResponseFactory()) @@ -51,12 +42,17 @@ namespace Renci.SshNet.Tests.Classes _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting()); _sftpSessionMock.InSequence(sequence).Setup(p => p.Dispose()); _sessionMock.InSequence(sequence).Setup(p => p.Dispose()); + } + + protected override void Arrange() + { + base.Arrange(); _sftpClient.Connect(); _sftpClient.Disconnect(); } - protected void Act() + protected override void Act() { _sftpClient.Dispose(); } @@ -75,10 +71,17 @@ namespace Renci.SshNet.Tests.Classes Times.Once); } + [TestMethod] + public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce() + { + _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once); + } + [TestMethod] public void CreateSessionOnServiceFactoryShouldBeInvokedOnce() { - _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once); + _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object), + Times.Once); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Disposed.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Disposed.cs index 73b07ad4..11e00ad0 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Disposed.cs +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Dispose_Disposed.cs @@ -6,44 +6,30 @@ using Renci.SshNet.Sftp; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SftpClientTest_Dispose_Disposed + public class SftpClientTest_Dispose_Disposed : SftpClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; - private Mock _sftpResponseFactoryMock; - private Mock _sftpSessionMock; private SftpClient _sftpClient; private ConnectionInfo _connectionInfo; private int _operationTimeout; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); - } - - [TestCleanup] - public void Cleanup() - { - } - - protected void Arrange() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _sftpResponseFactoryMock = new Mock(MockBehavior.Strict); - _sftpSessionMock = new Mock(MockBehavior.Strict); - _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); _operationTimeout = new Random().Next(1000, 10000); _sftpClient = new SftpClient(_connectionInfo, false, _serviceFactoryMock.Object); _sftpClient.OperationTimeout = TimeSpan.FromMilliseconds(_operationTimeout); + } + protected override void SetupMocks() + { var sequence = new MockSequence(); + _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _serviceFactoryMock.InSequence(sequence) .Setup(p => p.CreateSftpResponseFactory()) @@ -55,12 +41,17 @@ namespace Renci.SshNet.Tests.Classes _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting()); _sftpSessionMock.InSequence(sequence).Setup(p => p.Dispose()); _sessionMock.InSequence(sequence).Setup(p => p.Dispose()); + } + + protected override void Arrange() + { + base.Arrange(); _sftpClient.Connect(); _sftpClient.Dispose(); } - protected void Act() + protected override void Act() { _sftpClient.Dispose(); } @@ -79,10 +70,17 @@ namespace Renci.SshNet.Tests.Classes Times.Once); } + [TestMethod] + public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce() + { + _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once); + } + [TestMethod] public void CreateSessionOnServiceFactoryShouldBeInvokedOnce() { - _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once); + _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object), + Times.Once); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Finalize_Connected.cs b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Finalize_Connected.cs index 1c1328d7..033f0ea0 100644 --- a/src/Renci.SshNet.Tests/Classes/SftpClientTest_Finalize_Connected.cs +++ b/src/Renci.SshNet.Tests/Classes/SftpClientTest_Finalize_Connected.cs @@ -6,54 +6,49 @@ using Renci.SshNet.Sftp; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SftpClientTest_Finalize_Connected + public class SftpClientTest_Finalize_Connected : SftpClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; - private Mock _sftpResponseFactoryMock; - private Mock _sftpSessionMock; private SftpClient _sftpClient; private ConnectionInfo _connectionInfo; private int _operationTimeout; + private WeakReference _sftpClientWeakRefence; - [TestInitialize] - public void Setup() + protected override void SetupData() { - Arrange(); - Act(); - } - - protected void Arrange() - { - _serviceFactoryMock = new Mock(MockBehavior.Loose); - _sessionMock = new Mock(MockBehavior.Strict); - _sftpResponseFactoryMock = new Mock(MockBehavior.Strict); - _sftpSessionMock = new Mock(MockBehavior.Strict); - _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); _operationTimeout = new Random().Next(1000, 10000); _sftpClient = new SftpClient(_connectionInfo, false, _serviceFactoryMock.Object); _sftpClient.OperationTimeout = TimeSpan.FromMilliseconds(_operationTimeout); + _sftpClientWeakRefence = new WeakReference(_sftpClient); + } - _serviceFactoryMock.Setup(p => p.CreateSession(_connectionInfo)) - .Returns(_sessionMock.Object); + protected override void SetupMocks() + { + _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.CreateSftpResponseFactory()) .Returns(_sftpResponseFactoryMock.Object); _serviceFactoryMock.Setup(p => p.CreateSftpSession(_sessionMock.Object, _operationTimeout, _connectionInfo.Encoding, _sftpResponseFactoryMock.Object)) .Returns(_sftpSessionMock.Object); _sftpSessionMock.Setup(p => p.Connect()); + } + + protected override void Arrange() + { + base.Arrange(); _sftpClient.Connect(); _sftpClient = null; - // we need to dereference all other mocks as they might otherwise hold the target alive - _sessionMock = null; - _connectionInfo = null; - _serviceFactoryMock = null; + // We need to dereference all mocks as they might otherwise hold the target alive + //(through recorded invocations?) + CreateMocks(); } - protected void Act() + protected override void Act() { GC.Collect(); GC.WaitForPendingFinalizers(); @@ -62,13 +57,25 @@ namespace Renci.SshNet.Tests.Classes [TestMethod] public void DisconnectOnSftpSessionShouldNeverBeInvoked() { + // Since we recreated the mocks, this test has no value + // We'll leaving ths test just in case we have a solution that does not require us + // to recreate the mocks _sftpSessionMock.Verify(p => p.Disconnect(), Times.Never); } [TestMethod] public void DisposeOnSftpSessionShouldNeverBeInvoked() { + // Since we recreated the mocks, this test has no value + // We'll leaving ths test just in case we have a solution that does not require us + // to recreate the mocks _sftpSessionMock.Verify(p => p.Dispose(), Times.Never); } + + [TestMethod] + public void SftpClientShouldHaveBeenFinalized() + { + Assert.IsNull(_sftpClientWeakRefence.Target); + } } } diff --git a/src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs b/src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs index 99aba81f..df1ea835 100644 --- a/src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs +++ b/src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected.cs @@ -8,11 +8,8 @@ using Renci.SshNet.Common; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class - SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected + public class SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSizeAndTerminalModes_Connected : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private SshClient _sshClient; private ConnectionInfo _connectionInfo; private string _terminalName; @@ -25,14 +22,7 @@ namespace Renci.SshNet.Tests.Classes private ShellStream _expected; private ShellStream _actual; - [TestInitialize] - public void Setup() - { - Arrange(); - Act(); - } - - private void SetupData() + protected override void SetupData() { var random = new Random(); @@ -49,18 +39,15 @@ namespace Renci.SshNet.Tests.Classes _expected = CreateShellStream(); } - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - } - - private void SetupMocks() + protected override void SetupMocks() { var sequence = new MockSequence(); _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence) .Setup(p => p.Connect()); @@ -76,17 +63,15 @@ namespace Renci.SshNet.Tests.Classes .Returns(_expected); } - private void Arrange() + protected override void Arrange() { - SetupData(); - CreateMocks(); - SetupMocks(); + base.Arrange(); _sshClient = new SshClient(_connectionInfo, false, _serviceFactoryMock.Object); _sshClient.Connect(); } - protected void Act() + protected override void Act() { _actual = _sshClient.CreateShellStream(_terminalName, _widthColumns, diff --git a/src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs b/src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs index 14f52887..337f9bce 100644 --- a/src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs +++ b/src/Renci.SshNet.Tests/Classes/SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected.cs @@ -8,10 +8,8 @@ using Renci.SshNet.Common; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected + public class SshClientTest_CreateShellStream_TerminalNameAndColumnsAndRowsAndWidthAndHeightAndBufferSize_Connected : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private SshClient _sshClient; private ConnectionInfo _connectionInfo; private string _terminalName; @@ -23,14 +21,7 @@ namespace Renci.SshNet.Tests.Classes private ShellStream _expected; private ShellStream _actual; - [TestInitialize] - public void Setup() - { - Arrange(); - Act(); - } - - private void SetupData() + protected override void SetupData() { var random = new Random(); @@ -46,18 +37,15 @@ namespace Renci.SshNet.Tests.Classes _expected = CreateShellStream(); } - private void CreateMocks() - { - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - } - - private void SetupMocks() + protected override void SetupMocks() { var sequence = new MockSequence(); _serviceFactoryMock.InSequence(sequence) - .Setup(p => p.CreateSession(_connectionInfo)) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence) .Setup(p => p.Connect()); @@ -73,17 +61,15 @@ namespace Renci.SshNet.Tests.Classes .Returns(_expected); } - private void Arrange() + protected override void Arrange() { - SetupData(); - CreateMocks(); - SetupMocks(); + base.Arrange(); _sshClient = new SshClient(_connectionInfo, false, _serviceFactoryMock.Object); _sshClient.Connect(); } - protected void Act() + protected override void Act() { _actual = _sshClient.CreateShellStream(_terminalName, _widthColumns, diff --git a/src/Renci.SshNet.Tests/Classes/SshClientTest_Disconnect_ForwardedPortStarted.cs b/src/Renci.SshNet.Tests/Classes/SshClientTest_Disconnect_ForwardedPortStarted.cs index a5f19697..6b1259f0 100644 --- a/src/Renci.SshNet.Tests/Classes/SshClientTest_Disconnect_ForwardedPortStarted.cs +++ b/src/Renci.SshNet.Tests/Classes/SshClientTest_Disconnect_ForwardedPortStarted.cs @@ -5,42 +5,44 @@ using Moq; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SshClientTest_Disconnect_ForwardedPortStarted + public class SshClientTest_Disconnect_ForwardedPortStarted : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private Mock _forwardedPortMock; private SshClient _sshClient; private ConnectionInfo _connectionInfo; - [TestInitialize] - public void Setup() + protected override void CreateMocks() { - Arrange(); - Act(); + base.CreateMocks(); + + _forwardedPortMock = new Mock(MockBehavior.Strict); } - [TestCleanup] - public void Cleanup() - { - } - - protected void Arrange() + protected override void SetupData() { _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); + } + protected override void SetupMocks() + { var sequence = new MockSequence(); - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _forwardedPortMock = new Mock(MockBehavior.Strict); - - _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreateSession(_connectionInfo)).Returns(_sessionMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _forwardedPortMock.InSequence(sequence).Setup(p => p.Start()); _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting()); _forwardedPortMock.InSequence(sequence).Setup(p => p.Stop()); _sessionMock.InSequence(sequence).Setup(p => p.Dispose()); + } + + protected override void Arrange() + { + base.Arrange(); _sshClient = new SshClient(_connectionInfo, false, _serviceFactoryMock.Object); _sshClient.Connect(); @@ -49,7 +51,7 @@ namespace Renci.SshNet.Tests.Classes _forwardedPortMock.Object.Start(); } - protected void Act() + protected override void Act() { _sshClient.Disconnect(); } diff --git a/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Connected.cs b/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Connected.cs index a2133775..55108df6 100644 --- a/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Connected.cs +++ b/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Connected.cs @@ -4,51 +4,55 @@ using Moq; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SshClientTest_Dispose_Connected + public class SshClientTest_Dispose_Connected : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private SshClient _sshClient; private ConnectionInfo _connectionInfo; - [TestInitialize] - public void Setup() - { - Arrange(); - Act(); - } - - [TestCleanup] - public void Cleanup() - { - } - - protected void Arrange() + protected override void SetupData() { _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); + } - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - + protected override void SetupMocks() + { var sequence = new MockSequence(); - _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreateSession(_connectionInfo)).Returns(_sessionMock.Object); + + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting()); _sessionMock.InSequence(sequence).Setup(p => p.Dispose()); + } + + protected override void Arrange() + { + base.Arrange(); _sshClient = new SshClient(_connectionInfo, false, _serviceFactoryMock.Object); _sshClient.Connect(); } - protected void Act() + protected override void Act() { _sshClient.Dispose(); } + [TestMethod] + public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce() + { + _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once); + } + [TestMethod] public void CreateSessionOnServiceFactoryShouldBeInvokedOnce() { - _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once); + _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object), + Times.Once); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disconnected.cs b/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disconnected.cs index b4d28cb0..1d78561b 100644 --- a/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disconnected.cs +++ b/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disconnected.cs @@ -4,52 +4,56 @@ using Moq; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SshClientTest_Dispose_Disconnected + public class SshClientTest_Dispose_Disconnected : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private SshClient _sshClient; private ConnectionInfo _connectionInfo; - [TestInitialize] - public void Setup() - { - Arrange(); - Act(); - } - - [TestCleanup] - public void Cleanup() - { - } - - protected void Arrange() + protected override void SetupData() { _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); + } - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - + protected override void SetupMocks() + { var sequence = new MockSequence(); - _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreateSession(_connectionInfo)).Returns(_sessionMock.Object); + + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting()); _sessionMock.InSequence(sequence).Setup(p => p.Dispose()); + } + + protected override void Arrange() + { + base.Arrange(); _sshClient = new SshClient(_connectionInfo, false, _serviceFactoryMock.Object); _sshClient.Connect(); _sshClient.Disconnect(); } - protected void Act() + protected override void Act() { _sshClient.Dispose(); } + [TestMethod] + public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce() + { + _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once); + } + [TestMethod] public void CreateSessionOnServiceFactoryShouldBeInvokedOnce() { - _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once); + _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object), + Times.Once); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disposed.cs b/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disposed.cs index b5ffd003..54e07be9 100644 --- a/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disposed.cs +++ b/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_Disposed.cs @@ -4,52 +4,56 @@ using Moq; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SshClientTest_Dispose_Disposed + public class SshClientTest_Dispose_Disposed : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private SshClient _sshClient; private ConnectionInfo _connectionInfo; - [TestInitialize] - public void Setup() - { - Arrange(); - Act(); - } - - [TestCleanup] - public void Cleanup() - { - } - - protected void Arrange() + protected override void SetupData() { _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); + } - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - + protected override void SetupMocks() + { var sequence = new MockSequence(); - _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreateSession(_connectionInfo)).Returns(_sessionMock.Object); + + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting()); _sessionMock.InSequence(sequence).Setup(p => p.Dispose()); + } + + protected override void Arrange() + { + base.Arrange(); _sshClient = new SshClient(_connectionInfo, false, _serviceFactoryMock.Object); _sshClient.Connect(); _sshClient.Dispose(); } - protected void Act() + protected override void Act() { _sshClient.Dispose(); } + [TestMethod] + public void CreateSocketFactoryOnServiceFactoryShouldBeInvokedOnce() + { + _serviceFactoryMock.Verify(p => p.CreateSocketFactory(), Times.Once); + } + [TestMethod] public void CreateSessionOnServiceFactoryShouldBeInvokedOnce() { - _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo), Times.Once); + _serviceFactoryMock.Verify(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object), + Times.Once); } [TestMethod] diff --git a/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_ForwardedPortStarted.cs b/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_ForwardedPortStarted.cs index 28df578d..0b6da3cd 100644 --- a/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_ForwardedPortStarted.cs +++ b/src/Renci.SshNet.Tests/Classes/SshClientTest_Dispose_ForwardedPortStarted.cs @@ -6,42 +6,44 @@ using Moq; namespace Renci.SshNet.Tests.Classes { [TestClass] - public class SshClientTest_Dispose_ForwardedPortStarted + public class SshClientTest_Dispose_ForwardedPortStarted : BaseClientTestBase { - private Mock _serviceFactoryMock; - private Mock _sessionMock; private Mock _forwardedPortMock; private SshClient _sshClient; private ConnectionInfo _connectionInfo; - [TestInitialize] - public void Setup() + protected override void CreateMocks() { - Arrange(); - Act(); + base.CreateMocks(); + + _forwardedPortMock = new Mock(MockBehavior.Strict); } - [TestCleanup] - public void Cleanup() - { - } - - protected void Arrange() + protected override void SetupData() { _connectionInfo = new ConnectionInfo("host", "user", new NoneAuthenticationMethod("userauth")); + } + protected override void SetupMocks() + { var sequence = new MockSequence(); - _serviceFactoryMock = new Mock(MockBehavior.Strict); - _sessionMock = new Mock(MockBehavior.Strict); - _forwardedPortMock = new Mock(MockBehavior.Strict); - - _serviceFactoryMock.InSequence(sequence).Setup(p => p.CreateSession(_connectionInfo)).Returns(_sessionMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSocketFactory()) + .Returns(_socketFactoryMock.Object); + _serviceFactoryMock.InSequence(sequence) + .Setup(p => p.CreateSession(_connectionInfo, _socketFactoryMock.Object)) + .Returns(_sessionMock.Object); _sessionMock.InSequence(sequence).Setup(p => p.Connect()); _forwardedPortMock.InSequence(sequence).Setup(p => p.Start()); _sessionMock.InSequence(sequence).Setup(p => p.OnDisconnecting()); _forwardedPortMock.InSequence(sequence).Setup(p => p.Stop()); _sessionMock.InSequence(sequence).Setup(p => p.Dispose()); + } + + protected override void Arrange() + { + base.Arrange(); _sshClient = new SshClient(_connectionInfo, false, _serviceFactoryMock.Object); _sshClient.Connect(); @@ -50,7 +52,7 @@ namespace Renci.SshNet.Tests.Classes _forwardedPortMock.Object.Start(); } - protected void Act() + protected override void Act() { _sshClient.Dispose(); } diff --git a/src/Renci.SshNet.Tests/Common/AsyncSocketListener.cs b/src/Renci.SshNet.Tests/Common/AsyncSocketListener.cs index dbb6128b..5512b15c 100644 --- a/src/Renci.SshNet.Tests/Common/AsyncSocketListener.cs +++ b/src/Renci.SshNet.Tests/Common/AsyncSocketListener.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Threading; @@ -12,9 +13,12 @@ namespace Renci.SshNet.Tests.Common { private readonly IPEndPoint _endPoint; private readonly ManualResetEvent _acceptCallbackDone; + private List _connectedClients; private Socket _listener; private Thread _receiveThread; private bool _started; + private object _syncLock; + private string _stackTrace; public delegate void BytesReceivedHandler(byte[] bytesReceived, Socket socket); public delegate void ConnectedHandler(Socket socket); @@ -27,6 +31,8 @@ namespace Renci.SshNet.Tests.Common { _endPoint = endPoint; _acceptCallbackDone = new ManualResetEvent(false); + _connectedClients = new List(); + _syncLock = new object(); ShutdownRemoteCommunicationSocket = true; } @@ -51,16 +57,41 @@ namespace Renci.SshNet.Tests.Common _receiveThread = new Thread(StartListener); _receiveThread.Start(_listener); + + _stackTrace = Environment.StackTrace; } public void Stop() { _started = false; + + lock (_syncLock) + { + foreach (var connectedClient in _connectedClients) + { + try + { + connectedClient.Shutdown(SocketShutdown.Send); + } + catch (Exception ex) + { + Console.Error.WriteLine("[{0}] Failure shutting down socket: {1}", + typeof(AsyncSocketListener).FullName, + ex); + } + + DrainSocket(connectedClient); + connectedClient.Dispose(); + } + + _connectedClients.Clear(); + } + if (_listener != null) { _listener.Dispose(); - _listener = null; } + if (_receiveThread != null) { _receiveThread.Join(); @@ -87,30 +118,74 @@ namespace Renci.SshNet.Tests.Common private void AcceptCallback(IAsyncResult ar) { - // Signal the main thread to continue. + // Signal the main thread to continue _acceptCallbackDone.Set(); - // Get the socket that handles the client request. + // Get the socket that listens for inbound connections var listener = (Socket)ar.AsyncState; + + // Get the socket that handles the client request + Socket handler; + + try + { + handler = listener.EndAccept(ar); + } + catch (ObjectDisposedException ex) + { + // The listener is stopped through a Dispose() call, which in turn causes + // Socket.EndAccept(IAsyncResult) to throw an ObjectDisposedException + // + // Since we consider this ObjectDisposedException normal when the listener + // is being stopped, we only write a message to stderr if the listener + // is considered to be up and running + if (_started) + { + Console.Error.WriteLine("[{0}] Failure accepting new connection: {1}", + typeof(AsyncSocketListener).FullName, + ex); + } + + return; + } + + // Signal new connection + SignalConnected(handler); + + lock (_syncLock) + { + // Register client socket + _connectedClients.Add(handler); + } + + var state = new SocketStateObject(handler); + try { - var handler = listener.EndAccept(ar); - SignalConnected(handler); - var state = new SocketStateObject(handler); handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state); } - catch (ObjectDisposedException) + catch (ObjectDisposedException ex) { - // when the socket is closed, an ObjectDisposedException is thrown - // by Socket.EndAccept(IAsyncResult) + // The listener is stopped through a Dispose() call, which in turn causes + // Socket.BeginReceive(...) to throw an ObjectDisposedException + // + // Since we consider this ObjectDisposedException normal when the listener + // is being stopped, we only write a message to stderr if the listener + // is considered to be up and running + if (_started) + { + Console.Error.WriteLine("[{0}] Failure receiving new data: {1}", + typeof(AsyncSocketListener).FullName, + ex); + } } } private void ReadCallback(IAsyncResult ar) { // Retrieve the state object and the handler socket - // from the asynchronous state object. - var state = (SocketStateObject) ar.AsyncState; + // from the asynchronous state object + var state = (SocketStateObject)ar.AsyncState; var handler = state.Socket; int bytesRead; @@ -119,11 +194,38 @@ namespace Renci.SshNet.Tests.Common // Read data from the client socket. bytesRead = handler.EndReceive(ar); } - catch (ObjectDisposedException) + catch (SocketException ex) { - // when the socket is closed, the callback will be invoked for any pending BeginReceive - // we could use the Socket.Connected property to detect this here, but the proper thing - // to do is invoke EndReceive knowing that it will throw an ObjectDisposedException + // The listener is stopped through a Dispose() call, which in turn causes + // Socket.EndReceive(...) to throw a SocketException or + // ObjectDisposedException + // + // Since we consider such an exception normal when the listener is being + // stopped, we only write a message to stderr if the listener is considered + // to be up and running + if (_started) + { + Console.Error.WriteLine("[{0}] Failure receiving new data: {1}", + typeof(AsyncSocketListener).FullName, + ex); + } + return; + } + catch (ObjectDisposedException ex) + { + // The listener is stopped through a Dispose() call, which in turn causes + // Socket.EndReceive(...) to throw a SocketException or + // ObjectDisposedException + // + // Since we consider such an exception normal when the listener is being + // stopped, we only write a message to stderr if the listener is considered + // to be up and running + if (_started) + { + Console.Error.WriteLine("[{0}] Failure receiving new data: {1}", + typeof(AsyncSocketListener).FullName, + ex); + } return; } @@ -133,8 +235,20 @@ namespace Renci.SshNet.Tests.Common Array.Copy(state.Buffer, bytesReceived, bytesRead); SignalBytesReceived(bytesReceived, handler); - // prepare to receive more bytes - handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state); + try + { + handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state); + } + catch (SocketException ex) + { + if (!_started) + { + throw new Exception("BeginReceive while stopping!", ex); + } + + throw new Exception("BeginReceive while started!: " + ex.SocketErrorCode + " " + _stackTrace, ex); + } + } else { @@ -142,8 +256,28 @@ namespace Renci.SshNet.Tests.Common if (ShutdownRemoteCommunicationSocket) { - handler.Shutdown(SocketShutdown.Both); - handler.Close(); + lock (_syncLock) + { + if (!_started) + { + return; + } + try + { + handler.Shutdown(SocketShutdown.Send); + handler.Close(); + } + catch (SocketException ex) + { + throw new Exception("Exception in ReadCallback: " + ex.SocketErrorCode + " " + _stackTrace, ex); + } + catch (Exception ex) + { + throw new Exception("Exception in ReadCallback: " + _stackTrace, ex); + } + + _connectedClients.Remove(handler); + } } } } @@ -169,6 +303,30 @@ namespace Renci.SshNet.Tests.Common subscribers(client); } + private static void DrainSocket(Socket socket) + { + var buffer = new byte[128]; + + try + { + while (true && socket.Connected) + { + var bytesRead = socket.Receive(buffer); + if (bytesRead == 0) + { + break; + } + } + } + catch (SocketException ex) + { + Console.Error.WriteLine("[{0}] Failure draining socket ({1}): {2}", + typeof(AsyncSocketListener).FullName, + ex.SocketErrorCode.ToString("G"), + ex); + } + } + private class SocketStateObject { public Socket Socket { get; private set; } diff --git a/src/Renci.SshNet.Tests/Common/Extensions.cs b/src/Renci.SshNet.Tests/Common/Extensions.cs index 911f3413..4e88a790 100644 --- a/src/Renci.SshNet.Tests/Common/Extensions.cs +++ b/src/Renci.SshNet.Tests/Common/Extensions.cs @@ -49,8 +49,8 @@ namespace Renci.SshNet.Tests.Common clonedExtensions = null; } - return new SftpFileAttributes(value.LastAccessTime, - value.LastWriteTime, + return new SftpFileAttributes(value.LastAccessTimeUtc, + value.LastWriteTimeUtc, value.Size, value.UserId, value.GroupId, diff --git a/src/Renci.SshNet.Tests/Common/SftpFileAttributesBuilder.cs b/src/Renci.SshNet.Tests/Common/SftpFileAttributesBuilder.cs index 1f2d3324..2cf1b11a 100644 --- a/src/Renci.SshNet.Tests/Common/SftpFileAttributesBuilder.cs +++ b/src/Renci.SshNet.Tests/Common/SftpFileAttributesBuilder.cs @@ -64,9 +64,15 @@ namespace Renci.SshNet.Tests.Common public SftpFileAttributes Build() { if (_lastAccessTime == null) - _lastAccessTime = DateTime.MinValue; + _lastAccessTime = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); + else if (_lastAccessTime.Value.Kind != DateTimeKind.Utc) + _lastAccessTime = _lastAccessTime.Value.ToUniversalTime(); + if (_lastWriteTime == null) - _lastWriteTime = DateTime.MinValue; + _lastWriteTime = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); + else if (_lastWriteTime.Value.Kind != DateTimeKind.Utc) + _lastWriteTime = _lastWriteTime.Value.ToUniversalTime(); + if (_size == null) _size = 0; if (_userId == null) diff --git a/src/Renci.SshNet.Tests/Common/TripleATestBase.cs b/src/Renci.SshNet.Tests/Common/TripleATestBase.cs new file mode 100644 index 00000000..8e8f6005 --- /dev/null +++ b/src/Renci.SshNet.Tests/Common/TripleATestBase.cs @@ -0,0 +1,29 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Renci.SshNet.Tests.Common +{ + public abstract class TripleATestBase + { + [TestInitialize] + public void Init() + { + Arrange(); + Act(); + } + + [TestCleanup] + public void Cleanup() + { + TearDown(); + } + + protected virtual void TearDown() + { + } + + protected abstract void Arrange(); + + protected abstract void Act(); + } +} + diff --git a/src/Renci.SshNet.UAP10/Renci.SshNet.UAP10.csproj b/src/Renci.SshNet.UAP10/Renci.SshNet.UAP10.csproj index 0e66873e..bc78664d 100644 --- a/src/Renci.SshNet.UAP10/Renci.SshNet.UAP10.csproj +++ b/src/Renci.SshNet.UAP10/Renci.SshNet.UAP10.csproj @@ -381,6 +381,9 @@ ISession.cs + + ISftpClient.cs + ISubsystemSession.cs diff --git a/src/Renci.SshNet.WindowsPhone/Renci.SshNet.WindowsPhone.csproj b/src/Renci.SshNet.WindowsPhone/Renci.SshNet.WindowsPhone.csproj index 9e1d2da0..6f7d4576 100644 --- a/src/Renci.SshNet.WindowsPhone/Renci.SshNet.WindowsPhone.csproj +++ b/src/Renci.SshNet.WindowsPhone/Renci.SshNet.WindowsPhone.csproj @@ -1168,6 +1168,9 @@ SftpClient.cs + + + ISftpClient.cs Sftp\Flags.cs diff --git a/src/Renci.SshNet.WindowsPhone8/Renci.SshNet.WindowsPhone8.csproj b/src/Renci.SshNet.WindowsPhone8/Renci.SshNet.WindowsPhone8.csproj index 9fe3202e..cac46177 100644 --- a/src/Renci.SshNet.WindowsPhone8/Renci.SshNet.WindowsPhone8.csproj +++ b/src/Renci.SshNet.WindowsPhone8/Renci.SshNet.WindowsPhone8.csproj @@ -358,6 +358,9 @@ ISession.cs + + ISftpClient.cs + ISubsystemSession.cs @@ -1441,7 +1444,7 @@ - +