FakeFtpServerIntegrationTest.groovy revision dfa40a06dff44f29d8d5e1d3186055ad325fc7b9
1/*
2 * Copyright 2008 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.fake
17
18import org.apache.commons.net.ftp.FTP
19import org.apache.commons.net.ftp.FTPClient
20import org.apache.commons.net.ftp.FTPFile
21import org.mockftpserver.core.command.CommandNames
22import org.mockftpserver.core.command.StaticReplyCommandHandler
23
24import org.mockftpserver.fake.filesystem.DirectoryEntry
25import org.mockftpserver.fake.filesystem.FileEntry
26import org.mockftpserver.fake.filesystem.FileSystem
27import org.mockftpserver.fake.filesystem.UnixFakeFileSystem
28import org.mockftpserver.fake.filesystem.WindowsFakeFileSystem
29import org.mockftpserver.stub.command.CwdCommandHandler
30import org.mockftpserver.test.AbstractGroovyTestCase
31import org.mockftpserver.test.PortTestUtil
32
33/**
34 * Integration tests for FakeFtpServer.
35 *
36 * @version $Revision$ - $Date$
37 *
38 * @author Chris Mair
39 */
40class FakeFtpServerIntegrationTest extends AbstractGroovyTestCase {
41
42    static final SERVER = "localhost"
43    static final USERNAME = "user123"
44    static final PASSWORD = "password"
45    static final ACCOUNT = "account123"
46    static final ASCII_DATA = "abcdef\tghijklmnopqr"
47    static final BINARY_DATA = new byte[256]
48    static final ROOT_DIR = "c:/"
49    static final HOME_DIR = p(ROOT_DIR, "home")
50    static final SUBDIR_NAME = 'sub'
51    static final SUBDIR_NAME2 = "archive"
52    static final SUBDIR = p(HOME_DIR, SUBDIR_NAME)
53    static final FILENAME1 = "abc.txt"
54    static final FILENAME2 = "SomeOtherFile.xml"
55    static final FILE1 = p(HOME_DIR, FILENAME1)
56    static final SYSTEM_NAME = "WINDOWS"
57
58    private FakeFtpServer ftpServer
59    private FTPClient ftpClient
60    private FileSystem fileSystem
61    private UserAccount userAccount
62
63    //-------------------------------------------------------------------------
64    // Tests
65    //-------------------------------------------------------------------------
66
67    void testAbor() {
68        ftpClientConnectAndLogin()
69        assert ftpClient.abort()
70        verifyReplyCode("ABOR", 226)
71    }
72
73    void testAcct() {
74        ftpClientConnectAndLogin()
75        assert ftpClient.acct(ACCOUNT) == 230
76    }
77
78    void testAllo() {
79        ftpClientConnectAndLogin()
80        assert ftpClient.allocate(99)
81        verifyReplyCode("ALLO", 200)
82    }
83
84    void testAppe() {
85        def ORIGINAL_CONTENTS = '123 456 789'
86        fileSystem.add(new FileEntry(path: FILE1, contents: ORIGINAL_CONTENTS))
87
88        ftpClientConnectAndLogin()
89
90        LOG.info("Put File for local path [$FILE1]")
91        def inputStream = new ByteArrayInputStream(ASCII_DATA.getBytes())
92        assert ftpClient.appendFile(FILE1, inputStream)
93        def contents = fileSystem.getEntry(FILE1).createInputStream().text
94        LOG.info("File contents=[" + contents + "]")
95        assert contents == ORIGINAL_CONTENTS + ASCII_DATA
96    }
97
98    void testCdup() {
99        ftpClientConnectAndLogin()
100        assert ftpClient.changeToParentDirectory()
101        verifyReplyCode("changeToParentDirectory", 200)
102    }
103
104    void testCwd() {
105        ftpClientConnectAndLogin()
106        assert ftpClient.changeWorkingDirectory(SUBDIR_NAME)
107        verifyReplyCode("changeWorkingDirectory", 250)
108    }
109
110    /**
111     * Test that a CWD to ".." properly resolves the current dir (without the "..") so that PWD returns the parent
112     */
113    void testCwd_DotDot_Pwd() {
114        ftpClientConnectAndLogin()
115        assert ftpClient.changeWorkingDirectory("..")
116        verifyReplyCode("changeWorkingDirectory", 250)
117        assert p(ftpClient.printWorkingDirectory()) == p(ROOT_DIR)
118        assert ftpClient.changeWorkingDirectory("home")
119        assert p(ftpClient.printWorkingDirectory()) == p(HOME_DIR)
120    }
121
122    /**
123     * Test that a CWD to "." properly resolves the current dir (without the ".") so that PWD returns the parent
124     */
125    void testCwd_Dot_Pwd() {
126        ftpClientConnectAndLogin()
127        assert ftpClient.changeWorkingDirectory(".")
128        verifyReplyCode("changeWorkingDirectory", 250)
129        assert p(ftpClient.printWorkingDirectory()) == p(HOME_DIR)
130    }
131
132    void testCwd_UseStaticReplyCommandHandler() {
133        final int REPLY_CODE = 500;
134        StaticReplyCommandHandler cwdCommandHandler = new StaticReplyCommandHandler(REPLY_CODE);
135        ftpServer.setCommandHandler(CommandNames.CWD, cwdCommandHandler);
136
137        ftpClientConnectAndLogin()
138        assert !ftpClient.changeWorkingDirectory(SUBDIR_NAME)
139        verifyReplyCode("changeWorkingDirectory", REPLY_CODE)
140    }
141
142    void testCwd_UseStubCommandHandler() {
143        final int REPLY_CODE = 502;
144        CwdCommandHandler cwdCommandHandler = new CwdCommandHandler();     // Stub command handler
145        cwdCommandHandler.setReplyCode(REPLY_CODE);
146        ftpServer.setCommandHandler(CommandNames.CWD, cwdCommandHandler);
147
148        ftpClientConnectAndLogin()
149        assert !ftpClient.changeWorkingDirectory(SUBDIR_NAME)
150        verifyReplyCode("changeWorkingDirectory", REPLY_CODE)
151        assert cwdCommandHandler.getInvocation(0)
152    }
153
154    void testDele() {
155        fileSystem.add(new FileEntry(FILE1))
156
157        ftpClientConnectAndLogin()
158        assert ftpClient.deleteFile(FILENAME1)
159        verifyReplyCode("deleteFile", 250)
160        assert !fileSystem.exists(FILENAME1)
161    }
162
163    void testEprt() {
164        log("Skipping...")
165//        ftpClientConnectAndLogin()
166//        assert ftpClient.sendCommand("EPRT", "|2|1080::8:800:200C:417A|5282|") == 200
167    }
168
169    void testEpsv() {
170        ftpClientConnectAndLogin()
171        assert ftpClient.sendCommand("EPSV") == 229
172    }
173
174    void testFeat_UseStaticReplyCommandHandler() {
175        // The FEAT command is not supported out of the box
176        StaticReplyCommandHandler featCommandHandler = new StaticReplyCommandHandler(211, "No Features");
177        ftpServer.setCommandHandler("FEAT", featCommandHandler);
178
179        ftpClientConnectAndLogin()
180        assert ftpClient.sendCommand("FEAT") == 211
181    }
182
183    void testHelp() {
184        ftpServer.helpText = [a: 'aaa', '': 'default']
185        ftpClientConnect()
186
187        String help = ftpClient.listHelp()
188        assert help.contains('default')
189        verifyReplyCode("listHelp", 214)
190
191        help = ftpClient.listHelp('a')
192        assert help.contains('aaa')
193        verifyReplyCode("listHelp", 214)
194
195        help = ftpClient.listHelp('bad')
196        assert help.contains('bad')
197        verifyReplyCode("listHelp", 214)
198    }
199
200    void testList() {
201        def LAST_MODIFIED = new Date()
202        fileSystem.add(new FileEntry(path: p(SUBDIR, FILENAME1), lastModified: LAST_MODIFIED, contents: ASCII_DATA))
203        fileSystem.add(new DirectoryEntry(path: p(SUBDIR, SUBDIR_NAME2), lastModified: LAST_MODIFIED))
204
205        ftpClientConnectAndLogin()
206
207        FTPFile[] files = ftpClient.listFiles(SUBDIR)
208        assert files.length == 2
209
210        // Can't be sure of order
211        FTPFile fileEntry = (files[0].getType() == FTPFile.FILE_TYPE) ? files[0] : files[1]
212        FTPFile dirEntry = (files[0].getType() == FTPFile.FILE_TYPE) ? files[1] : files[0]
213        verifyFTPFile(fileEntry, FTPFile.FILE_TYPE, FILENAME1, ASCII_DATA.size())
214        verifyFTPFile(dirEntry, FTPFile.DIRECTORY_TYPE, SUBDIR_NAME2, 0)
215
216        verifyReplyCode("list", 226)
217    }
218
219    void testList_Unix() {
220        ftpServer.systemName = 'UNIX'
221        userAccount.homeDirectory = '/'
222
223        def unixFileSystem = new UnixFakeFileSystem()
224        unixFileSystem.createParentDirectoriesAutomatically = true
225        unixFileSystem.add(new DirectoryEntry('/'))
226        ftpServer.fileSystem = unixFileSystem
227
228        def LAST_MODIFIED = new Date()
229        unixFileSystem.add(new FileEntry(path: p('/', FILENAME1), lastModified: LAST_MODIFIED, contents: ASCII_DATA))
230        unixFileSystem.add(new DirectoryEntry(path: p('/', SUBDIR_NAME2), lastModified: LAST_MODIFIED))
231
232        ftpClientConnectAndLogin()
233
234        FTPFile[] files = ftpClient.listFiles('/')
235        assert files.length == 2
236
237        // Can't be sure of order
238        FTPFile fileEntry = (files[0].getType() == FTPFile.FILE_TYPE) ? files[0] : files[1]
239        FTPFile dirEntry = (files[0].getType() == FTPFile.FILE_TYPE) ? files[1] : files[0]
240
241        verifyFTPFile(dirEntry, FTPFile.DIRECTORY_TYPE, SUBDIR_NAME2, 0)
242        verifyFTPFile(fileEntry, FTPFile.FILE_TYPE, FILENAME1, ASCII_DATA.size())
243        verifyReplyCode("list", 226)
244    }
245
246    void testLogin() {
247        ftpClientConnect()
248        LOG.info("Logging in as $USERNAME/$PASSWORD")
249        assert ftpClient.login(USERNAME, PASSWORD)
250        verifyReplyCode("login with $USERNAME/$PASSWORD", 230)
251
252        assertTrue("isStarted", ftpServer.isStarted());
253        assertFalse("isShutdown", ftpServer.isShutdown());
254    }
255
256    void testLogin_WithAccount() {
257        userAccount.accountRequiredForLogin = true
258        ftpClientConnect()
259        LOG.info("Logging in as $USERNAME/$PASSWORD with $ACCOUNT")
260        assert ftpClient.login(USERNAME, PASSWORD, ACCOUNT)
261        verifyReplyCode("login with $USERNAME/$PASSWORD with $ACCOUNT", 230)
262    }
263
264    void testMkd() {
265        ftpClientConnectAndLogin()
266
267        def DIR = p(HOME_DIR, 'NewDir')
268        assert ftpClient.makeDirectory(DIR)
269        verifyReplyCode("makeDirectory", 257)
270        assert fileSystem.isDirectory(DIR)
271    }
272
273    void testMode() {
274        ftpClientConnectAndLogin()
275        assert ftpClient.setFileTransferMode(FTP.STREAM_TRANSFER_MODE);
276        verifyReplyCode("MODE", 200)
277    }
278
279    void testNlst() {
280        fileSystem.add(new FileEntry(path: p(SUBDIR, FILENAME1)))
281        fileSystem.add(new DirectoryEntry(path: p(SUBDIR, SUBDIR_NAME2)))
282
283        ftpClientConnectAndLogin()
284
285        String[] filenames = ftpClient.listNames(SUBDIR)
286        assert filenames as Set == [FILENAME1, SUBDIR_NAME2] as Set
287        verifyReplyCode("listNames", 226)
288    }
289
290    void testNoop() {
291        ftpClientConnectAndLogin()
292        assert ftpClient.sendNoOp()
293        verifyReplyCode("NOOP", 200)
294    }
295
296    void testPasv_Nlst() {
297        fileSystem.add(new FileEntry(path: p(SUBDIR, FILENAME1)))
298        fileSystem.add(new FileEntry(path: p(SUBDIR, FILENAME2)))
299
300        ftpClientConnectAndLogin()
301        ftpClient.enterLocalPassiveMode();
302
303        String[] filenames = ftpClient.listNames(SUBDIR)
304        assert filenames == [FILENAME1, FILENAME2]
305        verifyReplyCode("listNames", 226)
306    }
307
308    void testPwd() {
309        ftpClientConnectAndLogin()
310        assert ftpClient.printWorkingDirectory() == HOME_DIR
311        verifyReplyCode("printWorkingDirectory", 257)
312    }
313
314    void testQuit() {
315        ftpClientConnect()
316        ftpClient.quit()
317        verifyReplyCode("quit", 221)
318    }
319
320    void testRein() {
321        ftpClientConnectAndLogin()
322        assert ftpClient.rein() == 220
323        assert ftpClient.cdup() == 530      // now logged out
324    }
325
326    void testRest() {
327        ftpClientConnectAndLogin()
328        assert ftpClient.rest("marker") == 350
329    }
330
331    void testRetr() {
332        fileSystem.add(new FileEntry(path: FILE1, contents: ASCII_DATA))
333
334        ftpClientConnectAndLogin()
335
336        LOG.info("Get File for remotePath [$FILE1]")
337        def outputStream = new ByteArrayOutputStream()
338        assert ftpClient.retrieveFile(FILE1, outputStream)
339        LOG.info("File contents=[${outputStream.toString()}]")
340        assert outputStream.toString() == ASCII_DATA
341    }
342
343    void testRmd() {
344        ftpClientConnectAndLogin()
345
346        assert ftpClient.removeDirectory(SUBDIR)
347        verifyReplyCode("removeDirectory", 250)
348        assert !fileSystem.exists(SUBDIR)
349    }
350
351    void testRename() {                 // RNFR and RNTO
352        fileSystem.add(new FileEntry(FILE1))
353
354        ftpClientConnectAndLogin()
355
356        assert ftpClient.rename(FILE1, FILE1 + "NEW")
357        verifyReplyCode("rename", 250)
358        assert !fileSystem.exists(FILE1)
359        assert fileSystem.exists(FILE1 + "NEW")
360    }
361
362    void testSite() {
363        ftpClientConnectAndLogin()
364        assert ftpClient.site("parameters,1,2,3") == 200
365    }
366
367    void testSmnt() {
368        ftpClientConnectAndLogin()
369        assert ftpClient.smnt("dir") == 250
370    }
371
372    void testStat() {
373        ftpClientConnectAndLogin()
374        def status = ftpClient.getStatus()
375        assert status.contains('Connected')
376        verifyReplyCode("stat", 211)
377    }
378
379    void testStor() {
380        ftpClientConnectAndLogin()
381
382        LOG.info("Put File for local path [$FILE1]")
383        def inputStream = new ByteArrayInputStream(ASCII_DATA.getBytes())
384        assert ftpClient.storeFile(FILENAME1, inputStream)      // relative to homeDirectory
385        def contents = fileSystem.getEntry(FILE1).createInputStream().text
386        LOG.info("File contents=[" + contents + "]")
387        assert contents == ASCII_DATA
388    }
389
390    void testStou() {
391        ftpClientConnectAndLogin()
392
393        def inputStream = new ByteArrayInputStream(ASCII_DATA.getBytes())
394        assert ftpClient.storeUniqueFile(FILENAME1, inputStream)
395
396        def names = fileSystem.listNames(HOME_DIR)
397        def filename = names.find {name -> name.startsWith(FILENAME1) }
398        assert filename
399
400        def contents = fileSystem.getEntry(p(HOME_DIR, filename)).createInputStream().text
401        LOG.info("File contents=[" + contents + "]")
402        assert contents == ASCII_DATA
403    }
404
405    void testStru() {
406        ftpClientConnectAndLogin()
407        assert ftpClient.setFileStructure(FTP.FILE_STRUCTURE);
408        verifyReplyCode("STRU", 200)
409    }
410
411    void testSyst() {
412        ftpClientConnectAndLogin()
413
414        def systemName = ftpClient.getSystemName()
415        LOG.info("system name = [$systemName]")
416        assert systemName.contains('"' + SYSTEM_NAME + '"')
417        verifyReplyCode("getSystemName", 215)
418    }
419
420    void testType() {
421        ftpClientConnectAndLogin()
422        assert ftpClient.type(FTP.ASCII_FILE_TYPE)
423        verifyReplyCode("TYPE", 200)
424    }
425
426    void testUnrecognizedCommand() {
427        ftpClientConnectAndLogin()
428        assert ftpClient.sendCommand("XXX") == 502
429        verifyReplyCode("XXX", 502)
430    }
431
432    // -------------------------------------------------------------------------
433    // Test setup and tear-down
434    // -------------------------------------------------------------------------
435
436    /**
437     * Perform initialization before each test
438     * @see org.mockftpserver.test.AbstractTestCase#setUp()
439     */
440    void setUp() {
441        super.setUp()
442
443        for (int i = 0; i < BINARY_DATA.length; i++) {
444            BINARY_DATA[i] = (byte) i
445        }
446
447        ftpServer = new FakeFtpServer()
448        ftpServer.serverControlPort = PortTestUtil.getFtpServerControlPort()
449        ftpServer.systemName = SYSTEM_NAME
450
451        fileSystem = new WindowsFakeFileSystem()
452        fileSystem.createParentDirectoriesAutomatically = true
453        fileSystem.add(new DirectoryEntry(SUBDIR))
454        ftpServer.fileSystem = fileSystem
455
456        userAccount = new UserAccount(USERNAME, PASSWORD, HOME_DIR)
457        ftpServer.addUserAccount(userAccount)
458
459        ftpServer.start()
460        ftpClient = new FTPClient()
461    }
462
463    /**
464     * Perform cleanup after each test
465     * @see org.mockftpserver.test.AbstractTestCase#tearDown()
466     */
467    void tearDown() {
468        super.tearDown()
469        ftpServer.stop()
470    }
471
472    // -------------------------------------------------------------------------
473    // Internal Helper Methods
474    // -------------------------------------------------------------------------
475
476    private ftpClientConnectAndLogin() {
477        ftpClientConnect()
478        assert ftpClient.login(USERNAME, PASSWORD)
479    }
480
481    /**
482     * Connect to the server from the FTPClient
483     */
484    private void ftpClientConnect() {
485        def port = PortTestUtil.getFtpServerControlPort()
486        LOG.info("Conecting to $SERVER on port $port")
487        ftpClient.connect(SERVER, port)
488        verifyReplyCode("connect", 220)
489    }
490
491    /**
492     * Assert that the FtpClient reply code is equal to the expected value
493     *
494     * @param operation - the description of the operation performed used in the error message
495     * @param expectedReplyCode - the expected FtpClient reply code
496     */
497    private void verifyReplyCode(String operation, int expectedReplyCode) {
498        int replyCode = ftpClient.getReplyCode()
499        LOG.info("Reply: operation=\"" + operation + "\" replyCode=" + replyCode)
500        assertEquals("Unexpected replyCode for " + operation, expectedReplyCode, replyCode)
501    }
502
503    private void verifyFTPFile(FTPFile ftpFile, int type, String name, long size) {
504        LOG.info(ftpFile.toString())
505        assertEquals("type: " + ftpFile, type, ftpFile.getType())
506        assertEquals("name: " + ftpFile, name, ftpFile.getName())
507        assertEquals("size: " + ftpFile, size, ftpFile.getSize())
508    }
509
510}