2 * ============LICENSE_START======================================================================
3 * Copyright (C) 2018-2023 Nordix Foundation. All rights reserved.
4 * Copyright (C) 2020 Nokia. All rights reserved.
5 * ===============================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7 * in compliance with the License. You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software distributed under the License
12 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 * or implied. See the License for the specific language governing permissions and limitations under
15 * ============LICENSE_END========================================================================
18 package org.oran.datafile.ftp;
20 import static org.assertj.core.api.Assertions.assertThatThrownBy;
21 import static org.junit.jupiter.api.Assertions.assertNotNull;
22 import static org.junit.jupiter.api.Assertions.assertNull;
23 import static org.junit.jupiter.api.Assertions.assertThrows;
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.ArgumentMatchers.eq;
26 import static org.mockito.Mockito.doReturn;
27 import static org.mockito.Mockito.doThrow;
28 import static org.mockito.Mockito.mock;
29 import static org.mockito.Mockito.spy;
30 import static org.mockito.Mockito.times;
31 import static org.mockito.Mockito.verify;
32 import static org.mockito.Mockito.verifyNoMoreInteractions;
33 import static org.mockito.Mockito.when;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.OutputStream;
38 import java.net.URISyntaxException;
39 import java.nio.file.Path;
40 import java.nio.file.Paths;
41 import java.security.KeyStoreException;
42 import java.security.NoSuchAlgorithmException;
43 import java.security.cert.CertificateException;
44 import javax.net.ssl.KeyManager;
45 import javax.net.ssl.TrustManager;
46 import org.apache.commons.net.ftp.FTP;
47 import org.apache.commons.net.ftp.FTPSClient;
48 import org.junit.jupiter.api.BeforeEach;
49 import org.junit.jupiter.api.Test;
50 import org.mockito.ArgumentMatchers;
51 import org.oran.datafile.exceptions.DatafileTaskException;
52 import org.oran.datafile.exceptions.NonRetryableDatafileTaskException;
53 import org.oran.datafile.model.FileServerData;
54 import org.springframework.http.HttpStatus;
56 class FtpesClientTest {
58 private static final String REMOTE_FILE_PATH = "/dir/sample.txt";
59 private static final Path LOCAL_FILE_PATH = Paths.get("target/sample.txt");
60 private static final String XNF_ADDRESS = "127.0.0.1";
61 private static final int PORT = 8021;
62 private static final String FTP_KEY_PATH = "ftpKeyPath";
63 private static final String FTP_KEY_PASSWORD_PATH = "ftpKeyPasswordPath";
64 private static final Path TRUSTED_CA_PATH = Paths.get("trustedCaPath");
65 private static final String TRUSTED_CA_PASSWORD = "trustedCaPassword";
67 private static final String USERNAME = "bob";
68 private static final String PASSWORD = "123";
70 private FTPSClient ftpsClientMock = mock(FTPSClient.class);
71 private KeyManager keyManagerMock = mock(KeyManager.class);
72 private TrustManager trustManagerMock = mock(TrustManager.class);
73 private InputStream inputStreamMock = mock(InputStream.class);
74 private OutputStream outputStreamMock = mock(OutputStream.class);
76 FtpesClient clientUnderTestSpy;
78 private FileServerData createFileServerData() {
79 return FileServerData.builder() //
80 .serverAddress(XNF_ADDRESS) //
81 .userId(USERNAME).password(PASSWORD) //
87 protected void setUp() throws Exception {
88 clientUnderTestSpy = spy(new FtpesClient(createFileServerData(), Paths.get(FTP_KEY_PATH), FTP_KEY_PASSWORD_PATH,
89 TRUSTED_CA_PATH, TRUSTED_CA_PASSWORD));
90 clientUnderTestSpy.realFtpsClient = ftpsClientMock;
93 private void verifyFtpsClientMock_openOk() throws Exception {
94 doReturn(outputStreamMock).when(clientUnderTestSpy).createOutputStream(LOCAL_FILE_PATH);
96 when(ftpsClientMock.retrieveFile(eq(REMOTE_FILE_PATH),
97 ArgumentMatchers.any(OutputStream.class))).thenReturn(true);
98 verify(ftpsClientMock).setNeedClientAuth(true);
99 verify(ftpsClientMock).setKeyManager(keyManagerMock);
100 verify(ftpsClientMock).setTrustManager(trustManagerMock);
101 verify(ftpsClientMock).connect(XNF_ADDRESS, PORT);
102 verify(ftpsClientMock).login(USERNAME, PASSWORD);
103 verify(ftpsClientMock).getReplyCode();
104 verify(ftpsClientMock, times(1)).enterLocalPassiveMode();
105 verify(ftpsClientMock).execPBSZ(0);
106 verify(ftpsClientMock).execPROT("P");
107 verify(ftpsClientMock).setFileType(FTP.BINARY_FILE_TYPE);
108 verify(ftpsClientMock).setBufferSize(1024 * 1024);
112 void collectFile_allOk() throws Exception {
114 doReturn(keyManagerMock).when(clientUnderTestSpy).getKeyManager(Paths.get(FTP_KEY_PATH), FTP_KEY_PASSWORD_PATH);
115 doReturn(trustManagerMock).when(clientUnderTestSpy).getTrustManager(TRUSTED_CA_PATH, TRUSTED_CA_PASSWORD);
116 doReturn(outputStreamMock).when(clientUnderTestSpy).createOutputStream(LOCAL_FILE_PATH);
117 doReturn(true).when(ftpsClientMock).login(USERNAME, PASSWORD);
118 doReturn(HttpStatus.OK.value()).when(ftpsClientMock).getReplyCode();
120 clientUnderTestSpy.open();
122 doReturn(true).when(ftpsClientMock).retrieveFile(REMOTE_FILE_PATH, outputStreamMock);
123 clientUnderTestSpy.collectFile(REMOTE_FILE_PATH, LOCAL_FILE_PATH);
125 doReturn(true).when(ftpsClientMock).isConnected();
126 clientUnderTestSpy.close();
128 verifyFtpsClientMock_openOk();
129 verify(ftpsClientMock, times(1)).isConnected();
130 verify(ftpsClientMock, times(1)).logout();
131 verify(ftpsClientMock, times(1)).disconnect();
132 verify(ftpsClientMock, times(1)).retrieveFile(eq(REMOTE_FILE_PATH), any());
133 verifyNoMoreInteractions(ftpsClientMock);
137 void collectFileFaultyOwnKey_shouldFail() throws Exception {
139 doReturn(outputStreamMock).when(clientUnderTestSpy).createOutputStream(LOCAL_FILE_PATH);
140 assertThatThrownBy(() -> clientUnderTestSpy.open()).hasMessageContaining("Could not open connection:");
142 verify(ftpsClientMock).setNeedClientAuth(true);
144 doReturn(false).when(ftpsClientMock).isConnected();
145 clientUnderTestSpy.close();
146 verify(ftpsClientMock).isConnected();
147 verifyNoMoreInteractions(ftpsClientMock);
151 void collectFileFaultTrustedCA_shouldFail_no_trustedCA_file() throws Exception {
153 doReturn(keyManagerMock).when(clientUnderTestSpy).getKeyManager(Paths.get(FTP_KEY_PATH), FTP_KEY_PASSWORD_PATH);
154 doThrow(new IOException("problem")).when(clientUnderTestSpy).createInputStream(TRUSTED_CA_PATH);
156 assertThatThrownBy(() -> clientUnderTestSpy.open()).hasMessageContaining("Could not open connection:");
161 void collectFileFaultTrustedCA_shouldFail_empty_trustedCA_file() throws Exception {
163 doReturn(keyManagerMock).when(clientUnderTestSpy).getKeyManager(Paths.get(FTP_KEY_PATH), FTP_KEY_PASSWORD_PATH);
164 doReturn(inputStreamMock).when(clientUnderTestSpy).createInputStream(TRUSTED_CA_PATH);
166 assertThatThrownBy(() -> clientUnderTestSpy.open()).hasMessageContaining("Could not open connection: ");
170 void collectFileFaultyLogin_shouldFail() throws Exception {
172 doReturn(keyManagerMock).when(clientUnderTestSpy).getKeyManager(Paths.get(FTP_KEY_PATH), FTP_KEY_PASSWORD_PATH);
173 doReturn(trustManagerMock).when(clientUnderTestSpy).getTrustManager(TRUSTED_CA_PATH, TRUSTED_CA_PASSWORD);
174 doReturn(outputStreamMock).when(clientUnderTestSpy).createOutputStream(LOCAL_FILE_PATH);
175 doReturn(false).when(ftpsClientMock).login(USERNAME, PASSWORD);
177 assertThatThrownBy(() -> clientUnderTestSpy.open()).hasMessage("Unable to log in to xNF. 127.0.0.1");
179 verify(ftpsClientMock).setNeedClientAuth(true);
180 verify(ftpsClientMock).setKeyManager(keyManagerMock);
181 verify(ftpsClientMock).setTrustManager(trustManagerMock);
182 verify(ftpsClientMock).connect(XNF_ADDRESS, PORT);
183 verify(ftpsClientMock).login(USERNAME, PASSWORD);
187 void collectFileBadRequestResponse_shouldFail() throws Exception {
188 doReturn(keyManagerMock).when(clientUnderTestSpy).getKeyManager(Paths.get(FTP_KEY_PATH), FTP_KEY_PASSWORD_PATH);
189 doReturn(trustManagerMock).when(clientUnderTestSpy).getTrustManager(TRUSTED_CA_PATH, TRUSTED_CA_PASSWORD);
190 doReturn(outputStreamMock).when(clientUnderTestSpy).createOutputStream(LOCAL_FILE_PATH);
191 doReturn(true).when(ftpsClientMock).login(USERNAME, PASSWORD);
192 doReturn(503).when(ftpsClientMock).getReplyCode();
194 assertThatThrownBy(() -> clientUnderTestSpy.open())
195 .hasMessage("Unable to connect to xNF. 127.0.0.1 xNF reply code: 503");
197 verify(ftpsClientMock).setNeedClientAuth(true);
198 verify(ftpsClientMock).setKeyManager(keyManagerMock);
199 verify(ftpsClientMock).setTrustManager(trustManagerMock);
200 verify(ftpsClientMock).connect(XNF_ADDRESS, PORT);
201 verify(ftpsClientMock).login(USERNAME, PASSWORD);
202 verify(ftpsClientMock, times(2)).getReplyCode();
203 verifyNoMoreInteractions(ftpsClientMock);
207 void collectFile_shouldFail() throws Exception {
208 doReturn(keyManagerMock).when(clientUnderTestSpy).getKeyManager(Paths.get(FTP_KEY_PATH), FTP_KEY_PASSWORD_PATH);
209 doReturn(trustManagerMock).when(clientUnderTestSpy).getTrustManager(TRUSTED_CA_PATH, TRUSTED_CA_PASSWORD);
210 doReturn(outputStreamMock).when(clientUnderTestSpy).createOutputStream(LOCAL_FILE_PATH);
211 doReturn(true).when(ftpsClientMock).login(USERNAME, PASSWORD);
212 doReturn(HttpStatus.OK.value()).when(ftpsClientMock).getReplyCode();
213 clientUnderTestSpy.open();
215 doReturn(false).when(ftpsClientMock).retrieveFile(REMOTE_FILE_PATH, outputStreamMock);
217 assertThatThrownBy(() -> clientUnderTestSpy.collectFile(REMOTE_FILE_PATH, LOCAL_FILE_PATH))
218 .hasMessageContaining(REMOTE_FILE_PATH).hasMessageContaining("No retry");
220 verifyFtpsClientMock_openOk();
221 verify(ftpsClientMock, times(1)).retrieveFile(eq(REMOTE_FILE_PATH), any());
222 verifyNoMoreInteractions(ftpsClientMock);
226 void collectFile_shouldFail_ioexception() throws Exception {
227 doReturn(keyManagerMock).when(clientUnderTestSpy).getKeyManager(Paths.get(FTP_KEY_PATH), FTP_KEY_PASSWORD_PATH);
228 doReturn(trustManagerMock).when(clientUnderTestSpy).getTrustManager(TRUSTED_CA_PATH, TRUSTED_CA_PASSWORD);
229 doReturn(outputStreamMock).when(clientUnderTestSpy).createOutputStream(LOCAL_FILE_PATH);
230 doReturn(true).when(ftpsClientMock).login(USERNAME, PASSWORD);
231 doReturn(HttpStatus.OK.value()).when(ftpsClientMock).getReplyCode();
232 clientUnderTestSpy.open();
233 when(ftpsClientMock.isConnected()).thenReturn(false);
235 doThrow(new IOException("problem")).when(ftpsClientMock).retrieveFile(REMOTE_FILE_PATH, outputStreamMock);
237 assertThatThrownBy(() -> clientUnderTestSpy.collectFile(REMOTE_FILE_PATH, LOCAL_FILE_PATH))
238 .hasMessage("Could not fetch file: java.io.IOException: problem");
240 verifyFtpsClientMock_openOk();
241 verify(ftpsClientMock, times(1)).retrieveFile(eq(REMOTE_FILE_PATH), any());
242 verifyNoMoreInteractions(ftpsClientMock);
246 void testCreateInputStream() throws IOException, URISyntaxException {
247 Path trustCaPath = Paths.get(getClass().getResource("/org/oran/datafile/datastore/file.txt").toURI());
248 InputStream actualCreateInputStreamResult = clientUnderTestSpy.createInputStream(trustCaPath);
249 assertNotNull(actualCreateInputStreamResult);
253 void testCreateOutputStream() throws IOException, URISyntaxException, DatafileTaskException {
254 Path trustCaPath = Paths.get(getClass().getResource("/org/oran/datafile/datastore/file.txt").toURI());
255 assertThrows(NonRetryableDatafileTaskException.class, () -> clientUnderTestSpy.createOutputStream(trustCaPath));
259 void testGetTrustManager2() throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
260 FileServerData fileServerData = FileServerData.builder()
261 .password("password123")
263 .serverAddress("42 Main St")
266 assertNull((new FtpesClient(fileServerData, Paths.get(System.getProperty("java.io.tmpdir"), "test.txt"),
267 "Key Cert Password Path", Paths.get(System.getProperty("java.io.tmpdir"), "test.txt"),
268 "Trusted Ca Password Path")).getTrustManager(null, "foo"));