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