ListingFunctionalTest.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.net.ftp;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.io.IOException;
import java.net.SocketException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.net.PrintCommandListener;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

/**
 * A functional test suite for checking that site listings work.
 */
public class ListingFunctionalTest {
    // Offsets within testData below
    static final int HOSTNAME = 0;
    static final int VALID_PARSERKEY = 1;
    static final int INVALID_PARSERKEY = 2;
    static final int INVALID_PATH = 3;
    static final int VALID_FILENAME = 4;
    static final int VALID_PATH = 5;
    static final int PATH_PWD = 6; // response to PWD

    public static final class TestCase {
        private final String hostName;
        private final String invalidParserKey;
        private final String invalidPath;
        private final String validFilename;
        private final String validParserKey;
        private final String validPath;
        private final String pwdPath;

        private TestCase(final String[] settings) {
            invalidParserKey = settings[INVALID_PARSERKEY];
            validParserKey = settings[VALID_PARSERKEY];
            invalidPath = settings[INVALID_PATH];
            validFilename = settings[VALID_FILENAME];
            validPath = settings[VALID_PATH];
            pwdPath = settings[PATH_PWD];
            hostName = settings[HOSTNAME];
        }

        @Override
        public String toString() {
            return validParserKey + " @ " + hostName;
        }
    }

    private static Stream<TestCase> testCases() {
        final String[][] testData = { { "ftp.ibiblio.org", "unix", "vms", "HA!", "javaio.jar", "pub/languages/java/javafaq", "/pub/languages/java/javafaq", },
                { "apache.cs.utah.edu", "unix", "vms", "HA!", "HEADER.html", "apache.org", "/apache.org", },
//                { // not available
//                    "ftp.wacom.com", "windows", "VMS", "HA!",
//                    "wacom97.zip", "pub\\drivers"
//                },
                { "ftp.decuslib.com", "vms", "windows", // VMS OpenVMS V8.3
                        "[.HA!]", "FREEWARE_SUBMISSION_INSTRUCTIONS.TXT;1", "[.FREEWAREV80.FREEWARE]", "DECUSLIB:[DECUS.FREEWAREV80.FREEWARE]" },
//                {  // VMS TCPware V5.7-2 does not return (RWED) permissions
//                    "ftp.process.com", "vms", "windows",
//                    "[.HA!]", "MESSAGE.;1",
//                    "[.VMS-FREEWARE.FREE-VMS]" //
//                },
        };
        return Arrays.stream(testData).map(TestCase::new);
    }

    private FTPClient client;

    private FTPClient createFTPClient(final String hostName) {
        try {
            final FTPClient ftpClient = new FTPClient();
            ftpClient.addProtocolCommandListener(new PrintCommandListener(System.out));
            ftpClient.connect(hostName);
            ftpClient.login("anonymous", "anonymous");
            ftpClient.enterLocalPassiveMode();
            ftpClient.setAutodetectUTF8(true);
            ftpClient.opts("UTF-8", "NLST");
            return ftpClient;
        } catch (final SocketException e) {
            return fail("Could not connect to FTP", e);
        } catch (final IOException e) {
            return fail(e);
        }
    }

    private boolean findByName(final List<?> fileList, final String string) {
        boolean found = false;
        final Iterator<?> iter = fileList.iterator();
        while (iter.hasNext() && !found) {
            final Object element = iter.next();
            if (element instanceof FTPFile) {
                final FTPFile file = (FTPFile) element;
                found = file.getName().equals(string);
            } else {
                final String fileName = (String) element;
                found = fileName.endsWith(string);
            }
        }
        return found;
    }

    @AfterEach
    protected void tearDown() throws Exception {
        if (client == null) {
            return;
        }
        try {
            client.logout();
        } catch (final IOException e) {
            e.printStackTrace();
        }
        if (client.isConnected()) {
            client.disconnect();
        }
        client = null;
    }

    /*
     * Test for FTPListParseEngine initiateListParsing()
     */
    @ParameterizedTest(name = "hostname={0}")
    @MethodSource("testCases")
    public void testInitiateListParsing(final TestCase testCase) throws IOException {
        client = createFTPClient(testCase.hostName);
        client.changeWorkingDirectory(testCase.validPath);
        final FTPListParseEngine engine = client.initiateListParsing();
        final List<FTPFile> files = Arrays.asList(engine.getNext(25));
        assertTrue(findByName(files, testCase.validFilename), files.toString());
    }

    /*
     * Test for FTPListParseEngine initiateListParsing(String, String)
     */
    @ParameterizedTest(name = "hostname={0}")
    @MethodSource("testCases")
    public void testInitiateListParsingWithPath(final TestCase testCase) throws IOException {
        client = createFTPClient(testCase.hostName);
        final FTPListParseEngine engine = client.initiateListParsing(testCase.validParserKey, testCase.validPath);
        final List<FTPFile> files = Arrays.asList(engine.getNext(25));
        assertTrue(findByName(files, testCase.validFilename), files.toString());
    }

    /*
     * Test for FTPListParseEngine initiateListParsing(String)
     */
    @ParameterizedTest(name = "hostname={0}")
    @MethodSource("testCases")
    public void testInitiateListParsingWithPathAndAutodetection(final TestCase testCase) throws IOException {
        client = createFTPClient(testCase.hostName);
        final FTPListParseEngine engine = client.initiateListParsing(testCase.validPath);
        final List<FTPFile> files = Arrays.asList(engine.getNext(25));
        assertTrue(findByName(files, testCase.validFilename), files.toString());
    }

    /*
     * Test for FTPListParseEngine initiateListParsing(String)
     */
    @ParameterizedTest(name = "hostname={0}")
    @MethodSource("testCases")
    public void testInitiateListParsingWithPathAndAutodetectionButEmpty(final TestCase testCase) throws IOException {
        client = createFTPClient(testCase.hostName);
        final FTPListParseEngine engine = client.initiateListParsing(testCase.invalidPath);
        assertFalse(engine.hasNext());
    }

    /*
     * Test for FTPListParseEngine initiateListParsing(String, String)
     */
    @ParameterizedTest(name = "hostname={0}")
    @MethodSource("testCases")
    public void testInitiateListParsingWithPathAndIncorrectParser(final TestCase testCase) throws IOException {
        client = createFTPClient(testCase.hostName);
        final FTPListParseEngine engine = client.initiateListParsing(testCase.invalidParserKey, testCase.invalidPath);
        assertFalse(engine.hasNext());
    }

    /*
     * Test for FTPFile[] listFiles(String, String)
     */
    @ParameterizedTest(name = "hostname={0}")
    @MethodSource("testCases")
    public void testListFiles(final TestCase testCase) throws IOException {
        client = createFTPClient(testCase.hostName);
        final FTPClientConfig config = new FTPClientConfig(testCase.validParserKey);
        client.configure(config);
        final List<FTPFile> files = Arrays.asList(client.listFiles(testCase.validPath));
        assertTrue(findByName(files, testCase.validFilename), files.toString());
    }

    @ParameterizedTest(name = "hostname={0}")
    @MethodSource("testCases")
    public void testListFilesWithAutodection(final TestCase testCase) throws IOException {
        client = createFTPClient(testCase.hostName);
        client.changeWorkingDirectory(testCase.validPath);
        final List<FTPFile> files = Arrays.asList(client.listFiles());
        assertTrue(findByName(files, testCase.validFilename), files.toString());
    }

    /*
     * Test for FTPFile[] listFiles(String, String)
     */
    @ParameterizedTest(name = "hostname={0}")
    @MethodSource("testCases")
    public void testListFilesWithIncorrectParser(final TestCase testCase) throws IOException {
        client = createFTPClient(testCase.hostName);
        final FTPClientConfig config = new FTPClientConfig(testCase.invalidParserKey);
        client.configure(config);
        final FTPFile[] files = client.listFiles(testCase.validPath);
        assertNotNull(files);
        // This may well fail, e.g. window parser for VMS listing
        assertArrayEquals(new FTPFile[] {}, files, "Expected empty array: " + Arrays.toString(files));
    }

    /*
     * Test for FTPFile[] listFiles(String)
     */
    @ParameterizedTest(name = "hostname={0}")
    @MethodSource("testCases")
    public void testListFilesWithPathAndAutodectionButEmpty(final TestCase testCase) throws IOException {
        client = createFTPClient(testCase.hostName);
        final FTPFile[] files = client.listFiles(testCase.invalidPath);
        assertEquals(0, files.length);
    }

    /*
     * Test for FTPFile[] listFiles(String)
     */
    @ParameterizedTest(name = "hostname={0}")
    @MethodSource("testCases")
    public void testListFilesWithPathAndAutodetection(final TestCase testCase) throws IOException {
        client = createFTPClient(testCase.hostName);
        final List<FTPFile> files = Arrays.asList(client.listFiles(testCase.validPath));
        assertTrue(findByName(files, testCase.validFilename), files.toString());
    }

    /*
     * Test for String[] listNames()
     */
    @ParameterizedTest(name = "hostname={0}")
    @MethodSource("testCases")
    public void testListNames(final TestCase testCase) throws IOException {
        client = createFTPClient(testCase.hostName);
        client.changeWorkingDirectory(testCase.validPath);
        final String[] names = client.listNames();
        assertNotNull(names);
        final List<String> lnames = Arrays.asList(names);
        assertTrue(lnames.contains(testCase.validFilename), lnames.toString());
    }

    /*
     * Test for String[] listNames(String)
     */
    @ParameterizedTest(name = "hostname={0}")
    @MethodSource("testCases")
    public void testListNamesWithPath(final TestCase testCase) throws IOException {
        client = createFTPClient(testCase.hostName);
        final String[] listNames = client.listNames(testCase.validPath);
        assertNotNull(listNames, "listNames not null");
        final List<String> names = Arrays.asList(listNames);
        assertTrue(findByName(names, testCase.validFilename), names.toString());
    }

    @ParameterizedTest(name = "hostname={0}")
    @MethodSource("testCases")
    public void testListNamesWithPathButEmpty(final TestCase testCase) throws IOException {
        client = createFTPClient(testCase.hostName);
        final String[] names = client.listNames(testCase.invalidPath);
        assertTrue(ArrayUtils.isEmpty(names));
    }

    @ParameterizedTest(name = "hostname={0}")
    @MethodSource("testCases")
    public void testPrintWorkingDirectory(final TestCase testCase) throws IOException {
        client = createFTPClient(testCase.hostName);
        client.changeWorkingDirectory(testCase.validPath);
        final String pwd = client.printWorkingDirectory();
        assertEquals(testCase.pwdPath, pwd);
    }
}