Skip to content

Commit 69a42d3

Browse files
Integration tests
1 parent 4577899 commit 69a42d3

17 files changed

+533
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[*.cs]
2+
3+
#### SYSLIB diagnostics ####
4+
5+
# SYSLIB1045: Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time
6+
#
7+
# TODO: Remove this when https://github.com/sshnet/SSH.NET/issues/1131 is implemented.
8+
dotnet_diagnostic.SYSLIB1045.severity = none
9+
10+
### StyleCop Analyzers rules ###
11+
12+
#### .NET Compiler Platform analysers rules ####
13+
14+
# IDE0007: Use var instead of explicit type
15+
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0007
16+
dotnet_diagnostic.IDE0007.severity = suggestion
17+
18+
# IDE0028: Use collection initializers
19+
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0028
20+
dotnet_diagnostic.IDE0028.severity = suggestion
21+
22+
# IDE0058: Remove unnecessary expression value
23+
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0058
24+
dotnet_diagnostic.IDE0058.severity = suggestion
25+
26+
# IDE0059: Remove unnecessary value assignment
27+
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0059
28+
dotnet_diagnostic.IDE0059.severity = suggestion
29+
30+
# IDE0230: Use UTF-8 string literal
31+
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0230
32+
dotnet_diagnostic.IDE0230.severity = suggestion
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
FROM alpine:latest
2+
3+
COPY --chown=root:root server/ssh /etc/ssh/
4+
COPY --chown=root:root server/script /opt/sshnet
5+
COPY user/sshnet /home/sshnet/.ssh
6+
7+
RUN apk update && apk upgrade --no-cache && \
8+
apk add --no-cache syslog-ng && \
9+
# install and configure sshd
10+
apk add --no-cache openssh && \
11+
# install openssh-server-pam to allow for keyboard-interactive authentication
12+
apk add --no-cache openssh-server-pam && \
13+
dos2unix /etc/ssh/* && \
14+
chmod 400 /etc/ssh/ssh*key && \
15+
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
16+
sed -i 's/#LogLevel\s*INFO/LogLevel DEBUG3/' /etc/ssh/sshd_config && \
17+
echo 'PubkeyAcceptedAlgorithms ssh-rsa' >> /etc/ssh/sshd_config && \
18+
chmod 646 /etc/ssh/sshd_config && \
19+
# install and configure sudo
20+
apk add --no-cache sudo && \
21+
addgroup sudo && \
22+
# allow root to run any command
23+
echo 'root ALL=(ALL) ALL' > /etc/sudoers && \
24+
# allow everyone in the 'sudo' group to run any command without a password
25+
echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \
26+
# add user to run most of the integration tests
27+
adduser -D sshnet && \
28+
passwd -u sshnet && \
29+
echo 'sshnet:ssh4ever' | chpasswd && \
30+
dos2unix /home/sshnet/.ssh/* && \
31+
chown -R sshnet:sshnet /home/sshnet && \
32+
chmod -R 700 /home/sshnet/.ssh && \
33+
chmod -R 644 /home/sshnet/.ssh/authorized_keys && \
34+
# add user to administer container (update configs, restart sshd)
35+
adduser -D sshnetadm && \
36+
passwd -u sshnetadm && \
37+
echo 'sshnetadm:ssh4ever' | chpasswd && \
38+
addgroup sshnetadm sudo && \
39+
dos2unix /opt/sshnet/* && \
40+
# install shadow package; we use chage command in this package to expire/unexpire password of the sshnet user
41+
apk add --no-cache shadow && \
42+
# allow us to use telnet command; we use this in the remote port forwarding tests
43+
apk --no-cache add busybox-extras && \
44+
# install full-fledged ps command
45+
apk add --no-cache procps
46+
47+
EXPOSE 22 22
48+
49+
ENTRYPOINT ["/opt/sshnet/start.sh"]
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net7.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
8+
<IsPackable>false</IsPackable>
9+
<IsTestProject>true</IsTestProject>
10+
<!--
11+
Even though we're not interested in producing XML docs for test projects, we have to enable this in order to have the .NET Compiler
12+
Platform analyzers produce the IDE0005 (Remove unnecessary import) diagnostic.
13+
14+
To avoid warnings for missing XML docs, we add CS1591 (Missing XML comment for publicly visible type or member) to the NoWarn property.
15+
16+
We can stop producing XML docs for test projects (and remove the NoWarn for CS1591) once the following issue is fixed:
17+
https://github.com/dotnet/roslyn/issues/41640.
18+
-->
19+
<NoWarn>$(NoWarn);CS1591</NoWarn>
20+
</PropertyGroup>
21+
22+
<ItemGroup>
23+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
24+
<PackageReference Include="MSTest.TestAdapter" Version="3.0.3" />
25+
<PackageReference Include="MSTest.TestFramework" Version="3.0.3" />
26+
<PackageReference Include="Testcontainers" Version="3.2.0" />
27+
<PackageReference Include="coverlet.collector" Version="6.0.0">
28+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
29+
<PrivateAssets>all</PrivateAssets>
30+
</PackageReference>
31+
</ItemGroup>
32+
33+
</Project>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System.Text;
2+
3+
namespace Renci.SshNet.IntegrationTests
4+
{
5+
/// <summary>
6+
/// The SCP client integration tests
7+
/// </summary>
8+
[TestClass]
9+
public class ScpClientTests : IntegrationTestBase, IDisposable
10+
{
11+
private readonly ScpClient _scpClient;
12+
13+
public ScpClientTests()
14+
{
15+
_scpClient = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password);
16+
_scpClient.Connect();
17+
}
18+
19+
[TestMethod]
20+
21+
public void Scp_Upload_And_Download_FileStream()
22+
{
23+
var file = $"/tmp/{Guid.NewGuid()}.txt";
24+
var fileContent = "File content !@#$%^&*()_+{}:,./<>[];'\\|";
25+
26+
using var uploadStream = new MemoryStream(Encoding.UTF8.GetBytes(fileContent));
27+
_scpClient.Upload(uploadStream, file);
28+
29+
using var downloadStream = new MemoryStream();
30+
_scpClient.Download(file, downloadStream);
31+
32+
var result = Encoding.UTF8.GetString(downloadStream.ToArray());
33+
34+
Assert.AreEqual(fileContent, result);
35+
}
36+
37+
public void Dispose()
38+
{
39+
_scpClient.Disconnect();
40+
_scpClient.Dispose();
41+
}
42+
}
43+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System.Text;
2+
3+
using Renci.SshNet.Common;
4+
5+
namespace Renci.SshNet.IntegrationTests
6+
{
7+
/// <summary>
8+
/// The SFTP client integration tests
9+
/// </summary>
10+
[TestClass]
11+
public class SftpClientTests : IntegrationTestBase, IDisposable
12+
{
13+
private readonly SftpClient _sftpClient;
14+
15+
public SftpClientTests()
16+
{
17+
_sftpClient = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password);
18+
_sftpClient.Connect();
19+
}
20+
21+
[TestMethod]
22+
public void Test_Sftp_ListDirectory_Home_Directory()
23+
{
24+
var builder = new StringBuilder();
25+
var files = _sftpClient.ListDirectory("/");
26+
foreach (var file in files)
27+
{
28+
builder.AppendLine($"{file.FullName}");
29+
}
30+
31+
Assert.AreEqual(@"/usr
32+
/var
33+
/.
34+
/bin
35+
/mnt
36+
/opt
37+
/tmp
38+
/etc
39+
/root
40+
/media
41+
/..
42+
/dev
43+
/proc
44+
/sys
45+
/home
46+
/lib
47+
/sbin
48+
/run
49+
/srv
50+
/.dockerenv
51+
", builder.ToString());
52+
}
53+
54+
[TestMethod]
55+
[ExpectedException(typeof(SftpPermissionDeniedException), "Permission denied")]
56+
public void Test_Sftp_ListDirectory_Permission_Denied()
57+
{
58+
_sftpClient.ListDirectory("/root");
59+
}
60+
61+
public void Dispose()
62+
{
63+
_sftpClient.Disconnect();
64+
_sftpClient.Dispose();
65+
}
66+
}
67+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System.Text;
2+
3+
namespace Renci.SshNet.IntegrationTests
4+
{
5+
/// <summary>
6+
/// The SSH client integration tests
7+
/// </summary>
8+
[TestClass]
9+
public class SshClientTests : IntegrationTestBase, IDisposable
10+
{
11+
private readonly SshClient _sshClient;
12+
13+
public SshClientTests()
14+
{
15+
_sshClient = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password);
16+
_sshClient.Connect();
17+
}
18+
19+
[TestMethod]
20+
public void Test_SSH_Echo_Command()
21+
{
22+
var builder = new StringBuilder();
23+
var response = _sshClient.RunCommand("echo $'test !@#$%^&*()_+{}:,./<>[];\\|'");
24+
25+
Assert.AreEqual("test !@#$%^&*()_+{}:,./<>[];\\|\n", response.Result);
26+
}
27+
28+
public void Dispose()
29+
{
30+
_sshClient.Disconnect();
31+
_sshClient.Dispose();
32+
}
33+
}
34+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace Renci.SshNet.IntegrationTests
2+
{
3+
[TestClass]
4+
public class TestInitializer
5+
{
6+
[AssemblyInitialize]
7+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "MSTests requires context parameter")]
8+
public static async Task Initialize(TestContext context)
9+
{
10+
11+
await InfrastructureFixture.Instance.InitializeAsync();
12+
}
13+
14+
[AssemblyCleanup]
15+
public static async Task Cleanup()
16+
{
17+
await InfrastructureFixture.Instance.DisposeAsync();
18+
}
19+
}
20+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using DotNet.Testcontainers.Images;
2+
using DotNet.Testcontainers.Builders;
3+
using DotNet.Testcontainers.Containers;
4+
5+
namespace Renci.SshNet.IntegrationTests.TestsFixtures
6+
{
7+
public sealed class InfrastructureFixture : IDisposable
8+
{
9+
private InfrastructureFixture()
10+
{
11+
12+
}
13+
14+
private static readonly Lazy<InfrastructureFixture> InstanceLazy = new Lazy<InfrastructureFixture>(() => new InfrastructureFixture());
15+
16+
public static InfrastructureFixture Instance
17+
{
18+
get
19+
{
20+
return InstanceLazy.Value;
21+
}
22+
}
23+
24+
private IContainer? _sshServer;
25+
26+
private IFutureDockerImage? _sshServerImage;
27+
28+
public string? SshServerHostName { get; set; }
29+
30+
public ushort SshServerPort { get; set; }
31+
32+
// TODO the user name and password can be injected to dockerfile via arguments
33+
public SshUser AdminUser = new SshUser("sshnetadm", "ssh4ever");
34+
35+
// TODO the user name and password can be injected to dockerfile via arguments
36+
public SshUser User = new SshUser("sshnet", "ssh4ever");
37+
38+
public async Task InitializeAsync()
39+
{
40+
_sshServerImage = new ImageFromDockerfileBuilder()
41+
.WithName("renci-ssh-tests-server-image")
42+
.WithDockerfileDirectory(CommonDirectoryPath.GetSolutionDirectory(), "Renci.SshNet.IntegrationTests")
43+
.WithDockerfile("Dockerfile")
44+
.WithDeleteIfExists(true)
45+
46+
.Build();
47+
48+
await _sshServerImage.CreateAsync();
49+
50+
_sshServer = new ContainerBuilder()
51+
.WithHostname("renci-ssh-tests-server")
52+
.WithImage(_sshServerImage)
53+
//.WithPrivileged(true)
54+
.WithPortBinding(22, true)
55+
.Build();
56+
57+
await _sshServer.StartAsync();
58+
59+
SshServerPort = _sshServer.GetMappedPublicPort(22);
60+
SshServerHostName = _sshServer.Hostname;
61+
}
62+
63+
public async Task DisposeAsync()
64+
{
65+
if (_sshServer != null)
66+
{
67+
await _sshServer.DisposeAsync();
68+
}
69+
70+
if (_sshServerImage != null)
71+
{
72+
await _sshServerImage.DisposeAsync();
73+
}
74+
}
75+
76+
public void Dispose()
77+
{
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)