diff --git a/src/main/java/org/metricshub/ssh/SshClient.java b/src/main/java/org/metricshub/ssh/SshClient.java index b71501f..f85d534 100644 --- a/src/main/java/org/metricshub/ssh/SshClient.java +++ b/src/main/java/org/metricshub/ssh/SshClient.java @@ -346,6 +346,34 @@ public String readFileAttributes(String filePath) throws IOException { return pslFileResult.toString(); } + /** + * Returns the file size. + * + * @param filePath Path to the file on the remote system + * @return the file size + * @throws IOException if the file does not exist + */ + public long fileSize(final String filePath) throws IOException { + SFTPv3Client sftpClient = null; + try { + // Sanity check + checkIfAuthenticated(); + + // Create the SFTP client + sftpClient = new SFTPv3Client(sshConnection); + + // Read the file attributes + final SFTPv3FileAttributes attributes = sftpClient.stat(filePath); + + return attributes.size; + } finally { + // Deallocate + if (sftpClient != null) { + sftpClient.close(); + } + } + } + private StringBuilder listSubDirectory( SFTPv3Client sftpClient, String remoteDirectoryPath, @@ -1009,7 +1037,7 @@ void checkIfConnected() { * Check if already authenticate. */ void checkIfAuthenticated() { - if (!getSshConnection().isAuthenticationComplete()) { + if (getSshConnection() == null || !getSshConnection().isAuthenticationComplete()) { throw new IllegalStateException("Authentication is required first"); } } diff --git a/src/test/java/org/metricshub/ssh/SSHClientTest.java b/src/test/java/org/metricshub/ssh/SSHClientTest.java index a183875..5ff8fd2 100644 --- a/src/test/java/org/metricshub/ssh/SSHClientTest.java +++ b/src/test/java/org/metricshub/ssh/SSHClientTest.java @@ -2,13 +2,17 @@ import com.trilead.ssh2.ChannelCondition; import com.trilead.ssh2.Connection; +import com.trilead.ssh2.SFTPv3Client; +import com.trilead.ssh2.SFTPv3FileAttributes; import com.trilead.ssh2.Session; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.nio.charset.Charset; import java.util.Optional; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.mockito.MockedConstruction; import org.mockito.Mockito; class SSHClientTest { @@ -525,4 +529,95 @@ void testRead() throws Exception { Assertions.assertEquals(Optional.of("Hello WorldError"), sshClient.read(0, 5)); } } + + @Test + void testFileSize() throws Exception { + final Connection sshConnection = Mockito.mock(Connection.class); + final String filePath = "/path/to/file.txt"; + + // Case not Connected + try (final SshClient sshClient = Mockito.spy(new SshClient(HOSTNAME))) { + Mockito.verify(sshClient, Mockito.never()).checkIfAuthenticated(); + + Assertions.assertThrows(IllegalStateException.class, () -> sshClient.fileSize(filePath)); + } + + // Case not authenticated + try (final SshClient sshClient = Mockito.spy(new SshClient(HOSTNAME))) { + Mockito.doReturn(sshConnection).when(sshClient).getSshConnection(); + Mockito.doReturn(false).when(sshConnection).isAuthenticationComplete(); + + Assertions.assertThrows(IllegalStateException.class, () -> sshClient.fileSize(filePath)); + } + + // Case file does not exist (IOException) + try ( + final SshClient sshClient = Mockito.spy(new SshClient(HOSTNAME)); + final MockedConstruction mockedConstruction = Mockito.mockConstruction( + SFTPv3Client.class, + (mock, context) -> { + Mockito.when(mock.stat(filePath)).thenThrow(new IOException("File not found")); + } + ) + ) { + Mockito.doReturn(sshConnection).when(sshClient).getSshConnection(); + Mockito.doReturn(true).when(sshConnection).isAuthenticationComplete(); + + Assertions.assertThrows(IOException.class, () -> sshClient.fileSize(filePath)); + } + + // Case file size is 0 + try ( + final SshClient sshClient = Mockito.spy(new SshClient(HOSTNAME)); + final MockedConstruction mockedConstruction = Mockito.mockConstruction( + SFTPv3Client.class, + (mock, context) -> { + final SFTPv3FileAttributes attributes = new SFTPv3FileAttributes(); + attributes.size = 0L; + Mockito.when(mock.stat(filePath)).thenReturn(attributes); + } + ) + ) { + Mockito.doReturn(sshConnection).when(sshClient).getSshConnection(); + Mockito.doReturn(true).when(sshConnection).isAuthenticationComplete(); + + Assertions.assertEquals(0L, sshClient.fileSize(filePath)); + } + + // Case file size is small (100 bytes) + try ( + final SshClient sshClient = Mockito.spy(new SshClient(HOSTNAME)); + final MockedConstruction mockedConstruction = Mockito.mockConstruction( + SFTPv3Client.class, + (mock, context) -> { + final SFTPv3FileAttributes attributes = new SFTPv3FileAttributes(); + attributes.size = 100L; + Mockito.when(mock.stat(filePath)).thenReturn(attributes); + } + ) + ) { + Mockito.doReturn(sshConnection).when(sshClient).getSshConnection(); + Mockito.doReturn(true).when(sshConnection).isAuthenticationComplete(); + + Assertions.assertEquals(100L, sshClient.fileSize(filePath)); + } + + // Case file size is large (1GB) + try ( + final SshClient sshClient = Mockito.spy(new SshClient(HOSTNAME)); + final MockedConstruction mockedConstruction = Mockito.mockConstruction( + SFTPv3Client.class, + (mock, context) -> { + final SFTPv3FileAttributes attributes = new SFTPv3FileAttributes(); + attributes.size = 1073741824L; + Mockito.when(mock.stat(filePath)).thenReturn(attributes); + } + ) + ) { + Mockito.doReturn(sshConnection).when(sshClient).getSshConnection(); + Mockito.doReturn(true).when(sshConnection).isAuthenticationComplete(); + + Assertions.assertEquals(1073741824L, sshClient.fileSize(filePath)); + } + } }