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