76 lines
2.3 KiB
C#
76 lines
2.3 KiB
C#
using McpSsh.Server;
|
|
using McpSsh.Server.Audit;
|
|
using McpSsh.Server.Sftp;
|
|
using McpSsh.Server.Ssh;
|
|
using Renci.SshNet;
|
|
|
|
namespace McpSsh.Tests;
|
|
|
|
public sealed class SftpServiceTests
|
|
{
|
|
[Fact]
|
|
public async Task PutAsync_ReturnsErrorWhenLocalFileIsOutsideWorkingDirectory()
|
|
{
|
|
var service = CreateService();
|
|
|
|
var result = await service.PutAsync("host", "user", "/tmp/file.txt", "/tmp/file.txt", null, null, null, null, null, CancellationToken.None);
|
|
|
|
Assert.Equal("unsafe_local_path", result.Error);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PutAsync_ReturnsErrorWhenLocalFileIsMissing()
|
|
{
|
|
var service = CreateService();
|
|
|
|
var result = await service.PutAsync("host", "user", "missing-file.txt", "/tmp/file.txt", null, null, null, null, null, CancellationToken.None);
|
|
|
|
Assert.Equal("local_file_not_found", result.Error);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetAsync_ReturnsErrorWhenLocalFileExistsAndOverwriteIsFalse()
|
|
{
|
|
var path = Path.Combine("sftp-test-existing.txt");
|
|
await File.WriteAllTextAsync(path, "existing");
|
|
try
|
|
{
|
|
var service = CreateService();
|
|
|
|
var result = await service.GetAsync("host", "user", "/tmp/file.txt", path, null, null, null, overwrite: false, maxBytes: null, CancellationToken.None);
|
|
|
|
Assert.Equal("local_file_exists", result.Error);
|
|
}
|
|
finally
|
|
{
|
|
File.Delete(path);
|
|
}
|
|
}
|
|
|
|
private static SftpService CreateService()
|
|
{
|
|
return new SftpService(new ThrowingClientFactory(), new CapturingAuditLogger(), new FixedClock());
|
|
}
|
|
|
|
private sealed class ThrowingClientFactory : ISshClientFactory
|
|
{
|
|
public SshClient CreateSshClient(SshConnectionRequest request) => throw new NotSupportedException();
|
|
|
|
public SftpClient CreateSftpClient(SshConnectionRequest request) => throw new NotSupportedException();
|
|
|
|
public ScpClient CreateScpClient(SshConnectionRequest request) => throw new NotSupportedException();
|
|
}
|
|
|
|
private sealed class CapturingAuditLogger : IAuditLogger
|
|
{
|
|
public void Log(AuditEvent auditEvent)
|
|
{
|
|
}
|
|
}
|
|
|
|
private sealed class FixedClock : ISystemClock
|
|
{
|
|
public DateTimeOffset UtcNow => DateTimeOffset.Parse("2026-05-24T12:00:00Z");
|
|
}
|
|
}
|