StubFtpServerIntegrationTest.java revision dfa40a06dff44f29d8d5e1d3186055ad325fc7b9
1/*
2 * Copyright 2007 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.mockftpserver.stub;
17
18import org.apache.commons.net.ftp.FTP;
19import org.apache.commons.net.ftp.FTPClient;
20import org.apache.commons.net.ftp.FTPFile;
21import org.slf4j.Logger;
22import org.slf4j.LoggerFactory;
23import org.mockftpserver.core.command.CommandHandler;
24import org.mockftpserver.core.command.CommandNames;
25import org.mockftpserver.core.command.InvocationRecord;
26import org.mockftpserver.core.command.SimpleCompositeCommandHandler;
27import org.mockftpserver.core.command.StaticReplyCommandHandler;
28import org.mockftpserver.stub.command.*;
29import org.mockftpserver.test.*;
30import org.mockftpserver.test.AbstractTestCase;
31
32import java.io.ByteArrayInputStream;
33import java.io.ByteArrayOutputStream;
34import java.io.IOException;
35
36/**
37 * Tests for StubFtpServer using the Apache Jakarta Commons Net FTP client.
38 *
39 * @author Chris Mair
40 * @version $Revision$ - $Date$
41 */
42public final class StubFtpServerIntegrationTest extends AbstractTestCase implements IntegrationTest {
43
44    private static final Logger LOG = LoggerFactory.getLogger(StubFtpServerIntegrationTest.class);
45    private static final String SERVER = "localhost";
46    private static final String USERNAME = "user123";
47    private static final String PASSWORD = "password";
48    private static final String FILENAME = "abc.txt";
49    private static final String ASCII_CONTENTS = "abcdef\tghijklmnopqr";
50    private static final byte[] BINARY_CONTENTS = new byte[256];
51
52    private StubFtpServer stubFtpServer;
53    private FTPClient ftpClient;
54    private RetrCommandHandler retrCommandHandler;
55    private StorCommandHandler storCommandHandler;
56
57    //-------------------------------------------------------------------------
58    // Tests
59    //-------------------------------------------------------------------------
60
61    public void testLogin() throws Exception {
62        // Connect
63        LOG.info("Conecting to " + SERVER);
64        ftpClientConnect();
65        verifyReplyCode("connect", 220);
66
67        // Login
68        String userAndPassword = USERNAME + "/" + PASSWORD;
69        LOG.info("Logging in as " + userAndPassword);
70        boolean success = ftpClient.login(USERNAME, PASSWORD);
71        assertTrue("Unable to login with " + userAndPassword, success);
72        verifyReplyCode("login with " + userAndPassword, 230);
73
74        assertTrue("isStarted", stubFtpServer.isStarted());
75        assertFalse("isShutdown", stubFtpServer.isShutdown());
76
77        // Quit
78        LOG.info("Quit");
79        ftpClient.quit();
80        verifyReplyCode("quit", 221);
81    }
82
83    public void testAcct() throws Exception {
84        ftpClientConnect();
85
86        // ACCT
87        int replyCode = ftpClient.acct("123456");
88        assertEquals("acct", 230, replyCode);
89    }
90
91    /**
92     * Test the stop() method when no session has ever been started
93     */
94    public void testStop_NoSessionEverStarted() throws Exception {
95        LOG.info("Testing a stop() when no session has ever been started");
96    }
97
98    public void testHelp() throws Exception {
99        // Modify HELP CommandHandler to return a predefined help message
100        final String HELP = "help message";
101        HelpCommandHandler helpCommandHandler = (HelpCommandHandler) stubFtpServer.getCommandHandler(CommandNames.HELP);
102        helpCommandHandler.setHelpMessage(HELP);
103
104        ftpClientConnect();
105
106        // HELP
107        String help = ftpClient.listHelp();
108        assertTrue("Wrong response", help.indexOf(HELP) != -1);
109        verifyReplyCode("listHelp", 214);
110    }
111
112    /**
113     * Test the LIST and SYST commands.
114     */
115    public void testList() throws Exception {
116        ftpClientConnect();
117
118        // Set directory listing
119        ListCommandHandler listCommandHandler = (ListCommandHandler) stubFtpServer.getCommandHandler(CommandNames.LIST);
120        listCommandHandler.setDirectoryListing("11-09-01 12:30PM  406348 File2350.log\n"
121                + "11-01-01 1:30PM <DIR>  archive");
122
123        // LIST
124        FTPFile[] files = ftpClient.listFiles();
125        assertEquals("number of files", 2, files.length);
126        verifyFTPFile(files[0], FTPFile.FILE_TYPE, "File2350.log", 406348L);
127        verifyFTPFile(files[1], FTPFile.DIRECTORY_TYPE, "archive", 0L);
128        verifyReplyCode("list", 226);
129    }
130
131    /**
132     * Test the LIST, PASV and SYST commands, transferring a directory listing in passive mode
133     */
134    public void testList_PassiveMode() throws Exception {
135        ftpClientConnect();
136
137        ftpClient.enterLocalPassiveMode();
138
139        // Set directory listing
140        ListCommandHandler listCommandHandler = (ListCommandHandler) stubFtpServer.getCommandHandler(CommandNames.LIST);
141        listCommandHandler.setDirectoryListing("11-09-01 12:30PM  406348 File2350.log");
142
143        // LIST
144        FTPFile[] files = ftpClient.listFiles();
145        assertEquals("number of files", 1, files.length);
146        verifyReplyCode("list", 226);
147    }
148
149    public void testNlst() throws Exception {
150        ftpClientConnect();
151
152        // Set directory listing
153        NlstCommandHandler nlstCommandHandler = (NlstCommandHandler) stubFtpServer.getCommandHandler(CommandNames.NLST);
154        nlstCommandHandler.setDirectoryListing("File1.txt\nfile2.data");
155
156        // NLST
157        String[] filenames = ftpClient.listNames();
158        assertEquals("number of files", 2, filenames.length);
159        assertEquals(filenames[0], "File1.txt");
160        assertEquals(filenames[1], "file2.data");
161        verifyReplyCode("listNames", 226);
162    }
163
164    /**
165     * Test printing the current working directory (PWD)
166     */
167    public void testPwd() throws Exception {
168        // Modify PWD CommandHandler to return a predefined directory
169        final String DIR = "some/dir";
170        PwdCommandHandler pwdCommandHandler = (PwdCommandHandler) stubFtpServer.getCommandHandler(CommandNames.PWD);
171        pwdCommandHandler.setDirectory(DIR);
172
173        ftpClientConnect();
174
175        // PWD
176        String dir = ftpClient.printWorkingDirectory();
177        assertEquals("Unable to PWD", DIR, dir);
178        verifyReplyCode("printWorkingDirectory", 257);
179    }
180
181    public void testStat() throws Exception {
182        // Modify Stat CommandHandler to return predefined text
183        final String STATUS = "some information 123";
184        StatCommandHandler statCommandHandler = (StatCommandHandler) stubFtpServer.getCommandHandler(CommandNames.STAT);
185        statCommandHandler.setStatus(STATUS);
186
187        ftpClientConnect();
188
189        // STAT
190        String status = ftpClient.getStatus();
191        assertEquals("STAT reply", "211 " + STATUS + ".", status.trim());
192        verifyReplyCode("getStatus", 211);
193    }
194
195    /**
196     * Test getting the status (STAT), when the reply text contains multiple lines
197     */
198    public void testStat_MultilineReplyText() throws Exception {
199        // Modify Stat CommandHandler to return predefined text
200        final String STATUS = "System name: abc.def\nVersion 3.5.7\nNumber of failed logins: 2";
201        final String FORMATTED_REPLY_STATUS = "211-System name: abc.def\r\nVersion 3.5.7\r\n211 Number of failed logins: 2.";
202        StatCommandHandler statCommandHandler = (StatCommandHandler) stubFtpServer.getCommandHandler(CommandNames.STAT);
203        statCommandHandler.setStatus(STATUS);
204
205        ftpClientConnect();
206
207        // STAT
208        String status = ftpClient.getStatus();
209        assertEquals("STAT reply", FORMATTED_REPLY_STATUS, status.trim());
210        verifyReplyCode("getStatus", 211);
211    }
212
213    public void testSyst() throws Exception {
214        ftpClientConnect();
215
216        // SYST
217        assertEquals("getSystemName()", "\"WINDOWS\" system type.", ftpClient.getSystemName());
218        verifyReplyCode("syst", 215);
219    }
220
221    public void testCwd() throws Exception {
222        // Connect
223        LOG.info("Conecting to " + SERVER);
224        ftpClientConnect();
225        verifyReplyCode("connect", 220);
226
227        // CWD
228        boolean success = ftpClient.changeWorkingDirectory("dir1/dir2");
229        assertTrue("Unable to CWD", success);
230        verifyReplyCode("changeWorkingDirectory", 250);
231    }
232
233    /**
234     * Test changing the current working directory (CWD), when it causes a remote error
235     */
236    public void testCwd_Error() throws Exception {
237        // Override CWD CommandHandler to return error reply code
238        final int REPLY_CODE = 500;
239        StaticReplyCommandHandler cwdCommandHandler = new StaticReplyCommandHandler(REPLY_CODE);
240        stubFtpServer.setCommandHandler("CWD", cwdCommandHandler);
241
242        ftpClientConnect();
243
244        // CWD
245        boolean success = ftpClient.changeWorkingDirectory("dir1/dir2");
246        assertFalse("Expected failure", success);
247        verifyReplyCode("changeWorkingDirectory", REPLY_CODE);
248    }
249
250    public void testCdup() throws Exception {
251        ftpClientConnect();
252
253        // CDUP
254        boolean success = ftpClient.changeToParentDirectory();
255        assertTrue("Unable to CDUP", success);
256        verifyReplyCode("changeToParentDirectory", 200);
257    }
258
259    public void testDele() throws Exception {
260        ftpClientConnect();
261
262        // DELE
263        boolean success = ftpClient.deleteFile(FILENAME);
264        assertTrue("Unable to DELE", success);
265        verifyReplyCode("deleteFile", 250);
266    }
267
268    public void testEprt() throws Exception {
269        LOG.info("Skipping...");
270//        ftpClientConnect();
271//        ftpClient.sendCommand("EPRT", "|2|1080::8:800:200C:417A|5282|");
272//        verifyReplyCode("EPRT", 200);
273    }
274
275    public void testEpsv() throws Exception {
276        ftpClientConnect();
277        ftpClient.sendCommand("EPSV");
278        verifyReplyCode("EPSV", 229);
279    }
280
281    public void testFeat_UseStaticReplyCommandHandler() throws IOException {
282        // The FEAT command is not supported out of the box
283        final String FEAT_TEXT = "Extensions supported:\n" +
284                "MLST size*;create;modify*;perm;media-type\n" +
285                "SIZE\n" +
286                "COMPRESSION\n" +
287                "END";
288        StaticReplyCommandHandler featCommandHandler = new StaticReplyCommandHandler(211, FEAT_TEXT);
289        stubFtpServer.setCommandHandler("FEAT", featCommandHandler);
290
291        ftpClientConnect();
292        assertEquals(ftpClient.sendCommand("FEAT"), 211);
293        LOG.info(ftpClient.getReplyString());
294    }
295
296    public void testMkd() throws Exception {
297        ftpClientConnect();
298
299        // MKD
300        boolean success = ftpClient.makeDirectory("dir1/dir2");
301        assertTrue("Unable to CWD", success);
302        verifyReplyCode("makeDirectory", 257);
303    }
304
305    public void testNoop() throws Exception {
306        ftpClientConnect();
307
308        // NOOP
309        boolean success = ftpClient.sendNoOp();
310        assertTrue("Unable to NOOP", success);
311        verifyReplyCode("NOOP", 200);
312    }
313
314    public void testRest() throws Exception {
315        ftpClientConnect();
316
317        // REST
318        int replyCode = ftpClient.rest("marker");
319        assertEquals("Unable to REST", 350, replyCode);
320    }
321
322    public void testRmd() throws Exception {
323        ftpClientConnect();
324
325        // RMD
326        boolean success = ftpClient.removeDirectory("dir1/dir2");
327        assertTrue("Unable to RMD", success);
328        verifyReplyCode("removeDirectory", 250);
329    }
330
331    public void testRename() throws Exception {
332        ftpClientConnect();
333
334        // Rename (RNFR, RNTO)
335        boolean success = ftpClient.rename(FILENAME, "new_" + FILENAME);
336        assertTrue("Unable to RENAME", success);
337        verifyReplyCode("rename", 250);
338    }
339
340    public void testAllo() throws Exception {
341        ftpClientConnect();
342
343        // ALLO
344        assertTrue("ALLO", ftpClient.allocate(1024));
345        assertTrue("ALLO with recordSize", ftpClient.allocate(1024, 64));
346    }
347
348    /**
349     * Test GET and PUT of ASCII files
350     */
351    public void testTransferAsciiFile() throws Exception {
352        retrCommandHandler.setFileContents(ASCII_CONTENTS);
353
354        ftpClientConnect();
355
356        // Get File
357        LOG.info("Get File for remotePath [" + FILENAME + "]");
358        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
359        assertTrue(ftpClient.retrieveFile(FILENAME, outputStream));
360        LOG.info("File contents=[" + outputStream.toString());
361        assertEquals("File contents", ASCII_CONTENTS, outputStream.toString());
362
363        // Put File
364        LOG.info("Put File for local path [" + FILENAME + "]");
365        ByteArrayInputStream inputStream = new ByteArrayInputStream(ASCII_CONTENTS.getBytes());
366        assertTrue(ftpClient.storeFile(FILENAME, inputStream));
367        InvocationRecord invocationRecord = storCommandHandler.getInvocation(0);
368        byte[] contents = (byte[]) invocationRecord.getObject(StorCommandHandler.FILE_CONTENTS_KEY);
369        LOG.info("File contents=[" + contents + "]");
370        assertEquals("File contents", ASCII_CONTENTS.getBytes(), contents);
371    }
372
373    /**
374     * Test GET and PUT of binary files
375     */
376    public void testTransferBinaryFiles() throws Exception {
377        retrCommandHandler.setFileContents(BINARY_CONTENTS);
378
379        ftpClientConnect();
380        ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
381
382        // Get File
383        LOG.info("Get File for remotePath [" + FILENAME + "]");
384        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
385        assertTrue("GET", ftpClient.retrieveFile(FILENAME, outputStream));
386        LOG.info("GET File length=" + outputStream.size());
387        assertEquals("File contents", BINARY_CONTENTS, outputStream.toByteArray());
388
389        // Put File
390        LOG.info("Put File for local path [" + FILENAME + "]");
391        ByteArrayInputStream inputStream = new ByteArrayInputStream(BINARY_CONTENTS);
392        assertTrue("PUT", ftpClient.storeFile(FILENAME, inputStream));
393        InvocationRecord invocationRecord = storCommandHandler.getInvocation(0);
394        byte[] contents = (byte[]) invocationRecord.getObject(StorCommandHandler.FILE_CONTENTS_KEY);
395        LOG.info("PUT File length=" + contents.length);
396        assertEquals("File contents", BINARY_CONTENTS, contents);
397    }
398
399    public void testStou() throws Exception {
400        StouCommandHandler stouCommandHandler = (StouCommandHandler) stubFtpServer.getCommandHandler(CommandNames.STOU);
401        stouCommandHandler.setFilename(FILENAME);
402
403        ftpClientConnect();
404
405        // Stor a File (STOU)
406        ByteArrayInputStream inputStream = new ByteArrayInputStream(ASCII_CONTENTS.getBytes());
407        assertTrue(ftpClient.storeUniqueFile(FILENAME, inputStream));
408        InvocationRecord invocationRecord = stouCommandHandler.getInvocation(0);
409        byte[] contents = (byte[]) invocationRecord.getObject(StorCommandHandler.FILE_CONTENTS_KEY);
410        LOG.info("File contents=[" + contents + "]");
411        assertEquals("File contents", ASCII_CONTENTS.getBytes(), contents);
412    }
413
414    public void testAppe() throws Exception {
415        AppeCommandHandler appeCommandHandler = (AppeCommandHandler) stubFtpServer.getCommandHandler(CommandNames.APPE);
416
417        ftpClientConnect();
418
419        // Append a File (APPE)
420        ByteArrayInputStream inputStream = new ByteArrayInputStream(ASCII_CONTENTS.getBytes());
421        assertTrue(ftpClient.appendFile(FILENAME, inputStream));
422        InvocationRecord invocationRecord = appeCommandHandler.getInvocation(0);
423        byte[] contents = (byte[]) invocationRecord.getObject(AppeCommandHandler.FILE_CONTENTS_KEY);
424        LOG.info("File contents=[" + contents + "]");
425        assertEquals("File contents", ASCII_CONTENTS.getBytes(), contents);
426    }
427
428    public void testAbor() throws Exception {
429        ftpClientConnect();
430
431        // ABOR
432        assertTrue("ABOR", ftpClient.abort());
433    }
434
435    public void testPasv() throws Exception {
436        ftpClientConnect();
437
438        // PASV
439        ftpClient.enterLocalPassiveMode();
440        // no reply code; the PASV command is sent only when the data connection is opened
441    }
442
443    public void testMode() throws Exception {
444        ftpClientConnect();
445
446        // MODE
447        boolean success = ftpClient.setFileTransferMode(FTP.STREAM_TRANSFER_MODE);
448        assertTrue("Unable to MODE", success);
449        verifyReplyCode("setFileTransferMode", 200);
450    }
451
452    public void testStru() throws Exception {
453        ftpClientConnect();
454
455        // STRU
456        boolean success = ftpClient.setFileStructure(FTP.FILE_STRUCTURE);
457        assertTrue("Unable to STRU", success);
458        verifyReplyCode("setFileStructure", 200);
459    }
460
461    public void testSimpleCompositeCommandHandler() throws Exception {
462        // Replace CWD CommandHandler with a SimpleCompositeCommandHandler
463        CommandHandler commandHandler1 = new StaticReplyCommandHandler(500);
464        CommandHandler commandHandler2 = new CwdCommandHandler();
465        SimpleCompositeCommandHandler simpleCompositeCommandHandler = new SimpleCompositeCommandHandler();
466        simpleCompositeCommandHandler.addCommandHandler(commandHandler1);
467        simpleCompositeCommandHandler.addCommandHandler(commandHandler2);
468        stubFtpServer.setCommandHandler("CWD", simpleCompositeCommandHandler);
469
470        // Connect
471        ftpClientConnect();
472
473        // CWD
474        assertFalse("first", ftpClient.changeWorkingDirectory("dir1/dir2"));
475        assertTrue("first", ftpClient.changeWorkingDirectory("dir1/dir2"));
476    }
477
478    public void testSite() throws Exception {
479        ftpClientConnect();
480
481        // SITE
482        int replyCode = ftpClient.site("parameters,1,2,3");
483        assertEquals("SITE", 200, replyCode);
484    }
485
486    public void testSmnt() throws Exception {
487        ftpClientConnect();
488
489        // SMNT
490        assertTrue("SMNT", ftpClient.structureMount("dir1/dir2"));
491        verifyReplyCode("structureMount", 250);
492    }
493
494    public void testRein() throws Exception {
495        ftpClientConnect();
496
497        // REIN
498        assertEquals("REIN", 220, ftpClient.rein());
499    }
500
501    /**
502     * Test that command names in lowercase or mixed upper/lower case are accepted
503     */
504    public void testCommandNamesInLowerOrMixedCase() throws Exception {
505        ftpClientConnect();
506
507        assertEquals("rein", 220, ftpClient.sendCommand("rein"));
508        assertEquals("rEIn", 220, ftpClient.sendCommand("rEIn"));
509        assertEquals("reiN", 220, ftpClient.sendCommand("reiN"));
510        assertEquals("Rein", 220, ftpClient.sendCommand("Rein"));
511    }
512
513    public void testUnrecognizedCommand() throws Exception {
514        ftpClientConnect();
515
516        assertEquals("Unrecognized:XXXX", 502, ftpClient.sendCommand("XXXX"));
517    }
518
519    // -------------------------------------------------------------------------
520    // Test setup and tear-down
521    // -------------------------------------------------------------------------
522
523    /**
524     * Perform initialization before each test
525     *
526     * @see org.mockftpserver.test.AbstractTestCase#setUp()
527     */
528    protected void setUp() throws Exception {
529        super.setUp();
530
531        for (int i = 0; i < BINARY_CONTENTS.length; i++) {
532            BINARY_CONTENTS[i] = (byte) i;
533        }
534
535        stubFtpServer = new StubFtpServer();
536        stubFtpServer.setServerControlPort(PortTestUtil.getFtpServerControlPort());
537        stubFtpServer.start();
538        ftpClient = new FTPClient();
539        retrCommandHandler = (RetrCommandHandler) stubFtpServer.getCommandHandler(CommandNames.RETR);
540        storCommandHandler = (StorCommandHandler) stubFtpServer.getCommandHandler(CommandNames.STOR);
541    }
542
543    /**
544     * Perform cleanup after each test
545     *
546     * @see org.mockftpserver.test.AbstractTestCase#tearDown()
547     */
548    protected void tearDown() throws Exception {
549        super.tearDown();
550        stubFtpServer.stop();
551    }
552
553    // -------------------------------------------------------------------------
554    // Internal Helper Methods
555    // -------------------------------------------------------------------------
556
557    /**
558     * Connect to the server from the FTPClient
559     */
560    private void ftpClientConnect() throws IOException {
561        ftpClient.connect(SERVER, PortTestUtil.getFtpServerControlPort());
562    }
563
564    /**
565     * Assert that the FtpClient reply code is equal to the expected value
566     *
567     * @param operation         - the description of the operation performed; used in the error message
568     * @param expectedReplyCode - the expected FtpClient reply code
569     */
570    private void verifyReplyCode(String operation, int expectedReplyCode) {
571        int replyCode = ftpClient.getReplyCode();
572        LOG.info("Reply: operation=\"" + operation + "\" replyCode=" + replyCode);
573        assertEquals("Unexpected replyCode for " + operation, expectedReplyCode, replyCode);
574    }
575
576    /**
577     * Verify that the FTPFile has the specified properties
578     *
579     * @param ftpFile - the FTPFile to verify
580     * @param type    - the expected file type
581     * @param name    - the expected file name
582     * @param size    - the expected file size (will be zero for a directory)
583     */
584    private void verifyFTPFile(FTPFile ftpFile, int type, String name, long size) {
585        LOG.info(ftpFile.toString());
586        assertEquals("type: " + ftpFile, type, ftpFile.getType());
587        assertEquals("name: " + ftpFile, name, ftpFile.getName());
588        assertEquals("size: " + ftpFile, size, ftpFile.getSize());
589    }
590
591}
592