/**
* Copyright (c) Codice Foundation
* <p/>
* This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or any later version.
* <p/>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
*/
package ddf.catalog.ftp.ftplets;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.FtpFile;
import org.apache.ftpserver.ftplet.FtpReply;
import org.apache.ftpserver.ftplet.FtpRequest;
import org.apache.ftpserver.ftplet.FtpSession;
import org.apache.ftpserver.ftplet.FtpletResult;
import org.apache.ftpserver.impl.IODataConnectionFactory;
import org.codice.ddf.platform.util.TemporaryFileBackedOutputStream;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import com.google.common.io.FileBackedOutputStream;
import ddf.catalog.CatalogFramework;
import ddf.catalog.content.operation.CreateStorageRequest;
import ddf.catalog.data.Metacard;
import ddf.catalog.operation.CreateResponse;
import ddf.catalog.source.IngestException;
import ddf.catalog.source.SourceUnavailableException;
import ddf.mime.MimeTypeMapper;
import ddf.mime.MimeTypeResolutionException;
import ddf.security.Subject;
public class FtpRequestHandlerTest {
private static final String FILE_NAME = "SomeFile.xml";
private static final String SUBJECT = "subject";
private static final String METACARD_ID = "00000000000000000000000";
private FtpRequestHandler ftplet;
private FtpSession session;
private FtpRequest request;
private CatalogFramework catalogFramework;
private MimeTypeMapper mimeTypeMapper;
@Before
public void setUp() {
session = mock(FtpSession.class, RETURNS_DEEP_STUBS);
request = mock(FtpRequest.class);
catalogFramework = mock(CatalogFramework.class);
mimeTypeMapper = mock(MimeTypeMapper.class);
ftplet = new FtpRequestHandler(catalogFramework, mimeTypeMapper);
}
@Test(expected = FtpException.class)
public void testOnUploadStartNullFtpFile() throws FtpException, IOException {
Subject subject = mock(Subject.class);
when(request.getArgument()).thenReturn(FILE_NAME);
when(session.getAttribute(SUBJECT)).thenReturn(subject);
when(session.getFileSystemView()
.getFile(FILE_NAME)).thenReturn(null);
ftplet.onUploadStart(session, request);
}
@Test(expected = FtpException.class)
public void testOnUploadStartNoClientAddress() throws FtpException, IOException {
Subject subject = mock(Subject.class);
IODataConnectionFactory dataConnectionFactory = mock(IODataConnectionFactory.class);
when(session.getAttribute(SUBJECT)).thenReturn(subject);
when(session.getDataConnection()).thenReturn(dataConnectionFactory);
when(dataConnectionFactory.getInetAddress()).thenReturn(null);
ftplet.onUploadStart(session, request);
}
@Test(expected = FtpException.class)
public void testOnUploadStartNoFileWritePermission() throws FtpException, IOException {
Subject subject = mock(Subject.class);
when(request.getArgument()).thenReturn(FILE_NAME);
when(session.getAttribute(SUBJECT)).thenReturn(subject);
when(session.getFileSystemView()
.getFile(FILE_NAME)
.isWritable()).thenReturn(false);
ftplet.onUploadStart(session, request);
}
@Test(expected = IOException.class)
public void testOnUploadStartFailFileTransfer() throws Exception {
Subject subject = mock(Subject.class);
when(request.getArgument()).thenReturn(FILE_NAME);
when(session.getAttribute(SUBJECT)).thenReturn(subject);
when(session.getFileSystemView()
.getFile(FILE_NAME)
.isWritable()).thenReturn(true);
when(session.getDataConnection()
.openConnection()
.transferFromClient(eq(session),
any(FileBackedOutputStream.class))).thenThrow(new IOException());
ftplet.onUploadStart(session, request);
}
@SuppressWarnings("unchecked")
@Test(expected = FtpException.class)
public void testCreateStorageRequestFail() throws Exception {
Subject subject = mock(Subject.class);
FtpFile ftpFile = mock(FtpFile.class);
when(session.getAttribute(SUBJECT)).thenReturn(subject);
when(request.getArgument()).thenReturn(FILE_NAME);
when(session.getFileSystemView()
.getFile(FILE_NAME)).thenReturn(ftpFile);
when(ftpFile.isWritable()).thenReturn(true);
when(ftpFile.getAbsolutePath()).thenReturn(FILE_NAME);
when(subject.execute(any(Callable.class))).thenAnswer(invocationOnMock -> ((Callable) invocationOnMock.getArguments()[0]).call());
when(catalogFramework.create((CreateStorageRequest) anyObject())).thenThrow(new IngestException());
ftplet.onUploadStart(session, request);
}
@Test
public void testGetMimeTypeXml() throws MimeTypeResolutionException {
String mimeType;
when(mimeTypeMapper.guessMimeType(any(InputStream.class),
eq("xml"))).thenReturn("text/xml");
mimeType = ftplet.getMimeType("xml", new TemporaryFileBackedOutputStream());
assertEquals("text/xml", mimeType);
}
@Test
public void testGetMimeTypeOther() throws MimeTypeResolutionException {
String mimeType;
when(mimeTypeMapper.getMimeTypeForFileExtension("txt")).thenReturn("text/plain");
mimeType = ftplet.getMimeType("txt", new TemporaryFileBackedOutputStream());
assertEquals("text/plain", mimeType);
}
@Test(expected = IllegalArgumentException.class)
public void testMimeTypeMapperNull() {
String mimeType;
catalogFramework = mock(CatalogFramework.class);
FtpRequestHandler ftplett = new FtpRequestHandler(catalogFramework, null);
mimeType = ftplett.getMimeType("xml", new TemporaryFileBackedOutputStream());
assertEquals("", mimeType);
}
@Test
public void testMimeTypeResolutionFailure() throws MimeTypeResolutionException {
String mimeType;
when(mimeTypeMapper.guessMimeType(any(InputStream.class),
eq("xml"))).thenThrow(new MimeTypeResolutionException());
mimeType = ftplet.getMimeType("xml", new TemporaryFileBackedOutputStream());
assertEquals("", mimeType);
}
@SuppressWarnings("unchecked")
private void setupIngest() throws FtpException, SourceUnavailableException, IngestException {
Subject subject = mock(Subject.class);
FtpFile ftpFile = mock(FtpFile.class);
CreateResponse createResponse = mock(CreateResponse.class);
Metacard metacard = mock(Metacard.class);
when(metacard.getId()).thenReturn(METACARD_ID);
when(createResponse.getCreatedMetacards()).thenReturn(Collections.singletonList(metacard));
when(session.getAttribute(SUBJECT)).thenReturn(subject);
when(request.getArgument()).thenReturn(FILE_NAME);
when(session.getFileSystemView()
.getFile(FILE_NAME)).thenReturn(ftpFile);
when(ftpFile.isWritable()).thenReturn(true);
when(ftpFile.getAbsolutePath()).thenReturn(FILE_NAME);
when(subject.execute(any(Callable.class))).thenAnswer(invocationOnMock -> ((Callable) invocationOnMock.getArguments()[0]).call());
when(catalogFramework.create(any(CreateStorageRequest.class))).thenReturn(createResponse);
}
@Test
public void testFileIngestSuccess()
throws FtpException, IOException, SourceUnavailableException, IngestException {
setupIngest();
FtpletResult result = ftplet.onUploadStart(session, request);
assertEquals(FtpletResult.SKIP, result);
}
@Test
public void testFileIngestUniqueSuccess()
throws FtpException, IOException, SourceUnavailableException, IngestException {
setupIngest();
FtpletResult result = ftplet.onUploadUniqueStart(session, request);
ArgumentCaptor<FtpReply> argumentCaptor = ArgumentCaptor.forClass(FtpReply.class);
verify(session, atLeast(1)).write(argumentCaptor.capture());
assertEquals(FtpletResult.SKIP, result);
List<String> strReplies = argumentCaptor.getAllValues()
.stream()
.map(FtpReply::getMessage)
.collect(Collectors.toList());
assertThat(strReplies,
hasItem(containsString("Storing data with unique name: " + METACARD_ID)));
}
private List<Integer> getReplyCodes() throws FtpException {
ArgumentCaptor<FtpReply> argumentCaptor = ArgumentCaptor.forClass(FtpReply.class);
verify(session, atLeast(1)).write(argumentCaptor.capture());
return argumentCaptor.getAllValues()
.stream()
.map(FtpReply::getCode)
.collect(Collectors.toList());
}
@Test
public void testOnDeleteStart() throws FtpException, IOException {
FtpletResult result = ftplet.onDeleteStart(session, request);
assertEquals(FtpletResult.SKIP, result);
assertThat(getReplyCodes(), hasItem(250));
}
@Test
public void testOnDownloadStart() throws FtpException, IOException {
FtpletResult result = ftplet.onDownloadStart(session, request);
assertEquals(FtpletResult.SKIP, result);
assertThat(getReplyCodes(), hasItem(250));
}
@Test
public void testOnRmdirStart() throws FtpException, IOException {
FtpletResult result = ftplet.onRmdirStart(session, request);
assertEquals(FtpletResult.SKIP, result);
assertThat(getReplyCodes(), hasItem(250));
}
@Test
public void testOnMkdirStart() throws FtpException, IOException {
FtpletResult result = ftplet.onMkdirStart(session, request);
assertEquals(FtpletResult.SKIP, result);
assertThat(getReplyCodes(), hasItem(257));
}
@Test
public void testOnAppendStart() throws FtpException, IOException {
FtpletResult result = ftplet.onAppendStart(session, request);
assertEquals(FtpletResult.SKIP, result);
assertThat(getReplyCodes(), hasItem(250));
}
@Test
public void testOnRenameStart() throws FtpException, IOException {
FtpFile ftpFile = mock(FtpFile.class);
when(ftpFile.getName()).thenReturn("test.txt");
when(session.getRenameFrom()).thenReturn(ftpFile);
FtpletResult result = ftplet.onRenameStart(session, request);
assertEquals(FtpletResult.SKIP, result);
assertThat(getReplyCodes(), hasItem(250));
}
@Test
public void testOnSite() throws FtpException, IOException {
FtpletResult result = ftplet.onSite(session, request);
assertEquals(FtpletResult.SKIP, result);
assertThat(getReplyCodes(), hasItem(202));
}
}