Added GetAttributesAsync to SftpClient (#1648)

* Added GetAttributesAsync to SftpClient

* Adding integration tests + unit test

* Address warnings in test classes.

---------

Co-authored-by: William Decker <william.decker@syndigo.com>
This commit is contained in:
William Decker
2025-06-05 16:20:13 -05:00
committed by GitHub
parent 03e28219ba
commit 9cf282731a
7 changed files with 238 additions and 0 deletions
+15
View File
@@ -700,6 +700,21 @@ namespace Renci.SshNet
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
SftpFileAttributes GetAttributes(string path);
/// <summary>
/// Gets the <see cref="SftpFileAttributes"/> of the file on the path.
/// </summary>
/// <param name="path">The path to the file.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
/// <returns>
/// A <see cref="Task{SftpFileAttributes}"/> that represents the attribute retrieval operation.
/// The task result contains the <see cref="SftpFileAttributes"/> of the file on the path.
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <see langword="null"/>.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPathNotFoundException"><paramref name="path"/> was not found on the remote host.</exception>
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
Task<SftpFileAttributes> GetAttributesAsync(string path, CancellationToken cancellationToken);
/// <summary>
/// Returns the date and time the specified file or directory was last accessed.
/// </summary>
+27
View File
@@ -2094,6 +2094,33 @@ namespace Renci.SshNet
return _sftpSession.RequestLStat(fullPath);
}
/// <summary>
/// Gets the <see cref="SftpFileAttributes"/> of the file on the path.
/// </summary>
/// <param name="path">The path to the file.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to observe.</param>
/// <returns>
/// A <see cref="Task{SftpFileAttributes}"/> that represents the attribute retrieval operation.
/// The task result contains the <see cref="SftpFileAttributes"/> of the file on the path.
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <see langword="null"/>.</exception>
/// <exception cref="SshConnectionException">Client is not connected.</exception>
/// <exception cref="SftpPathNotFoundException"><paramref name="path"/> was not found on the remote host.</exception>
/// <exception cref="ObjectDisposedException">The method was called after the client was disposed.</exception>
public async Task<SftpFileAttributes> GetAttributesAsync(string path, CancellationToken cancellationToken)
{
CheckDisposed();
if (_sftpSession is null)
{
throw new SshConnectionException("Client not connected.");
}
var fullPath = await _sftpSession.GetCanonicalPathAsync(path, cancellationToken).ConfigureAwait(false);
return await _sftpSession.RequestLStatAsync(fullPath, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Sets the specified <see cref="SftpFileAttributes"/> of the file on the specified path.
/// </summary>
@@ -0,0 +1,47 @@
using Renci.SshNet.Common;
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
public partial class SftpClientTest
{
[TestMethod]
[TestCategory("Sftp")]
public void Test_Sftp_GetAttributes_Not_Exists()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
Assert.ThrowsException<SftpPathNotFoundException>(() => sftp.GetAttributes("/asdfgh"));
}
}
[TestMethod]
[TestCategory("Sftp")]
public void Test_Sftp_GetAttributes_Null()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
Assert.ThrowsException<ArgumentNullException>(() => sftp.GetAttributes(null));
}
}
[TestMethod]
[TestCategory("Sftp")]
public void Test_Sftp_GetAttributes_Current()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
var attributes = sftp.GetAttributes(".");
Assert.IsNotNull(attributes);
sftp.Disconnect();
}
}
}
}
@@ -0,0 +1,59 @@
using Renci.SshNet.Common;
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
/// <summary>
/// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
/// </summary>
public partial class SftpClientTest
{
[TestMethod]
[TestCategory("Sftp")]
public async Task Test_Sftp_GetAttributesAsync_Not_Exists()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromMinutes(1));
await sftp.ConnectAsync(cts.Token);
await Assert.ThrowsExceptionAsync<SftpPathNotFoundException>(async () => await sftp.GetAttributesAsync("/asdfgh", cts.Token));
}
}
[TestMethod]
[TestCategory("Sftp")]
public async Task Test_Sftp_GetAttributesAsync_Null()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromMinutes(1));
await sftp.ConnectAsync(cts.Token);
await Assert.ThrowsExceptionAsync<ArgumentNullException>(async () => await sftp.GetAttributesAsync(null, cts.Token));
}
}
[TestMethod]
[TestCategory("Sftp")]
public async Task Test_Sftp_GetAttributesAsync_Current()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromMinutes(1));
await sftp.ConnectAsync(cts.Token);
var fileAttributes = await sftp.GetAttributesAsync(".", cts.Token);
Assert.IsNotNull(fileAttributes);
sftp.Disconnect();
}
}
}
}
@@ -177,5 +177,33 @@ namespace Renci.SshNet.IntegrationTests
Assert.IsFalse(await _sftpClient.ExistsAsync(testFileName).ConfigureAwait(false));
}
[TestMethod]
public void Create_file_and_use_GetAttributes()
{
var testFileName = "test-file.txt";
var testContent = "file content";
using var fileStream = new MemoryStream(Encoding.UTF8.GetBytes(testContent));
_sftpClient.UploadFile(fileStream, testFileName);
var attributes = _sftpClient.GetAttributes(testFileName);
Assert.IsNotNull(attributes);
Assert.IsTrue(attributes.IsRegularFile);
}
[TestMethod]
public async Task Create_file_and_use_GetAttributesAsync()
{
var testFileName = "test-file.txt";
var testContent = "file content";
using var fileStream = new MemoryStream(Encoding.UTF8.GetBytes(testContent));
await _sftpClient.UploadFileAsync(fileStream, testFileName).ConfigureAwait(false);
var attributes = await _sftpClient.GetAttributesAsync(testFileName, CancellationToken.None).ConfigureAwait(false);
Assert.IsNotNull(attributes);
Assert.IsTrue(attributes.IsRegularFile);
}
}
}
@@ -0,0 +1,30 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Renci.SshNet.Common;
using Renci.SshNet.Tests.Properties;
namespace Renci.SshNet.Tests.Classes
{
public partial class SftpClientTest
{
[TestMethod]
public void GetAttributes_Throws_WhenNotConnected()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
{
Assert.ThrowsException<SshConnectionException>(() => sftp.GetAttributes("."));
}
}
[TestMethod]
public void GetAttributes_Throws_WhenDisposed()
{
var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD);
sftp.Dispose();
Assert.ThrowsException<ObjectDisposedException>(() => sftp.GetAttributes("."));
}
}
}
@@ -0,0 +1,32 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Renci.SshNet.Common;
using Renci.SshNet.Tests.Properties;
namespace Renci.SshNet.Tests.Classes
{
public partial class SftpClientTest
{
[TestMethod]
public async Task GetAttributesAsync_Throws_WhenNotConnected()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
{
await Assert.ThrowsExceptionAsync<SshConnectionException>(() => sftp.GetAttributesAsync(".", CancellationToken.None));
}
}
[TestMethod]
public async Task GetAttributesAsync_Throws_WhenDisposed()
{
var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD);
sftp.Dispose();
await Assert.ThrowsExceptionAsync<ObjectDisposedException>(() => sftp.GetAttributesAsync(".", CancellationToken.None));
}
}
}