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