FakeFtpServerIntegrationTest.groovy revision 40658190151b7ded3489ff89c301b470155c95f4
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.AbstractGroovyTest
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 AbstractGroovyTest {
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        ftpClientConnectAndLogin()
165        assert ftpClient.sendCommand("EPRT", "|2|1080::8:800:200C:417A|5282|") == 200
166    }
167
168    void testEpsv() {
169        ftpClientConnectAndLogin()
170        assert ftpClient.sendCommand("EPSV") == 229
171    }
172
173    void testFeat_UseStaticReplyCommandHandler() {
174        // The FEAT command is not supported out of the box
175        StaticReplyCommandHandler featCommandHandler = new StaticReplyCommandHandler(211, "No Features");
176        ftpServer.setCommandHandler("FEAT", featCommandHandler);
177
178        ftpClientConnectAndLogin()
179        assert ftpClient.sendCommand("FEAT") == 211
180    }
181
182    void testHelp() {
183        ftpServer.helpText = [a: 'aaa', '': 'default']
184        ftpClientConnect()
185
186        String help = ftpClient.listHelp()
187        assert help.contains('default')
188        verifyReplyCode("listHelp", 214)
189
190        help = ftpClient.listHelp('a')
191        assert help.contains('aaa')
192        verifyReplyCode("listHelp", 214)
193
194        help = ftpClient.listHelp('bad')
195        assert help.contains('bad')
196        verifyReplyCode("listHelp", 214)
197    }
198
199    void testList() {
200        def LAST_MODIFIED = new Date()
201        fileSystem.add(new FileEntry(path: p(SUBDIR, FILENAME1), lastModified: LAST_MODIFIED, contents: ASCII_DATA))
202        fileSystem.add(new DirectoryEntry(path: p(SUBDIR, SUBDIR_NAME2), lastModified: LAST_MODIFIED))
203
204        ftpClientConnectAndLogin()
205
206        FTPFile[] files = ftpClient.listFiles(SUBDIR)
207        assert files.length == 2
208        verifyFTPFile(files[0], FTPFile.FILE_TYPE, FILENAME1, ASCII_DATA.size())
209        verifyFTPFile(files[1], FTPFile.DIRECTORY_TYPE, SUBDIR_NAME2, 0)
210        verifyReplyCode("list", 226)
211    }
212
213    void testList_Unix() {
214        ftpServer.systemName = 'UNIX'
215        userAccount.homeDirectory = '/'
216
217        def unixFileSystem = new UnixFakeFileSystem()
218        unixFileSystem.createParentDirectoriesAutomatically = true
219        unixFileSystem.add(new DirectoryEntry('/'))
220        ftpServer.fileSystem = unixFileSystem
221
222        def LAST_MODIFIED = new Date()
223        unixFileSystem.add(new FileEntry(path: p('/', FILENAME1), lastModified: LAST_MODIFIED, contents: ASCII_DATA))
224        unixFileSystem.add(new DirectoryEntry(path: p('/', SUBDIR_NAME2), lastModified: LAST_MODIFIED))
225
226        ftpClientConnectAndLogin()
227
228        FTPFile[] files = ftpClient.listFiles('/')
229        assert files.length == 2
230        verifyFTPFile(files[0], FTPFile.DIRECTORY_TYPE, SUBDIR_NAME2, 0)
231        verifyFTPFile(files[1], FTPFile.FILE_TYPE, FILENAME1, ASCII_DATA.size())
232        verifyReplyCode("list", 226)
233    }
234
235    void testLogin() {
236        ftpClientConnect()
237        LOG.info("Logging in as $USERNAME/$PASSWORD")
238        assert ftpClient.login(USERNAME, PASSWORD)
239        verifyReplyCode("login with $USERNAME/$PASSWORD", 230)
240
241        assertTrue("isStarted", ftpServer.isStarted());
242        assertFalse("isShutdown", ftpServer.isShutdown());
243    }
244
245    void testLogin_WithAccount() {
246        userAccount.accountRequiredForLogin = true
247        ftpClientConnect()
248        LOG.info("Logging in as $USERNAME/$PASSWORD with $ACCOUNT")
249        assert ftpClient.login(USERNAME, PASSWORD, ACCOUNT)
250        verifyReplyCode("login with $USERNAME/$PASSWORD with $ACCOUNT", 230)
251    }
252
253    void testMkd() {
254        ftpClientConnectAndLogin()
255
256        def DIR = p(HOME_DIR, 'NewDir')
257        assert ftpClient.makeDirectory(DIR)
258        verifyReplyCode("makeDirectory", 257)
259        assert fileSystem.isDirectory(DIR)
260    }
261
262    void testMode() {
263        ftpClientConnectAndLogin()
264        assert ftpClient.setFileTransferMode(FTP.STREAM_TRANSFER_MODE);
265        verifyReplyCode("MODE", 200)
266    }
267
268    void testNlst() {
269        fileSystem.add(new FileEntry(path: p(SUBDIR, FILENAME1)))
270        fileSystem.add(new DirectoryEntry(path: p(SUBDIR, SUBDIR_NAME2)))
271
272        ftpClientConnectAndLogin()
273
274        String[] filenames = ftpClient.listNames(SUBDIR)
275        assert filenames == [FILENAME1, SUBDIR_NAME2]
276        verifyReplyCode("listNames", 226)
277    }
278
279    void testNoop() {
280        ftpClientConnectAndLogin()
281        assert ftpClient.sendNoOp()
282        verifyReplyCode("NOOP", 200)
283    }
284
285    void testPasv_Nlst() {
286        fileSystem.add(new FileEntry(path: p(SUBDIR, FILENAME1)))
287        fileSystem.add(new FileEntry(path: p(SUBDIR, FILENAME2)))
288
289        ftpClientConnectAndLogin()
290        ftpClient.enterLocalPassiveMode();
291
292        String[] filenames = ftpClient.listNames(SUBDIR)
293        assert filenames == [FILENAME1, FILENAME2]
294        verifyReplyCode("listNames", 226)
295    }
296
297    void testPwd() {
298        ftpClientConnectAndLogin()
299        assert ftpClient.printWorkingDirectory() == HOME_DIR
300        verifyReplyCode("printWorkingDirectory", 257)
301    }
302
303    void testQuit() {
304        ftpClientConnect()
305        ftpClient.quit()
306        verifyReplyCode("quit", 221)
307    }
308
309    void testRein() {
310        ftpClientConnectAndLogin()
311        assert ftpClient.rein() == 220
312        assert ftpClient.cdup() == 530      // now logged out
313    }
314
315    void testRest() {
316        ftpClientConnectAndLogin()
317        assert ftpClient.rest("marker") == 350
318    }
319
320    void testRetr() {
321        fileSystem.add(new FileEntry(path: FILE1, contents: ASCII_DATA))
322
323        ftpClientConnectAndLogin()
324
325        LOG.info("Get File for remotePath [$FILE1]")
326        def outputStream = new ByteArrayOutputStream()
327        assert ftpClient.retrieveFile(FILE1, outputStream)
328        LOG.info("File contents=[${outputStream.toString()}]")
329        assert outputStream.toString() == ASCII_DATA
330    }
331
332    void testRmd() {
333        ftpClientConnectAndLogin()
334
335        assert ftpClient.removeDirectory(SUBDIR)
336        verifyReplyCode("removeDirectory", 250)
337        assert !fileSystem.exists(SUBDIR)
338    }
339
340    void testRename() {                 // RNFR and RNTO
341        fileSystem.add(new FileEntry(FILE1))
342
343        ftpClientConnectAndLogin()
344
345        assert ftpClient.rename(FILE1, FILE1 + "NEW")
346        verifyReplyCode("rename", 250)
347        assert !fileSystem.exists(FILE1)
348        assert fileSystem.exists(FILE1 + "NEW")
349    }
350
351    void testSite() {
352        ftpClientConnectAndLogin()
353        assert ftpClient.site("parameters,1,2,3") == 200
354    }
355
356    void testSmnt() {
357        ftpClientConnectAndLogin()
358        assert ftpClient.smnt("dir") == 250
359    }
360
361    void testStat() {
362        ftpClientConnectAndLogin()
363        def status = ftpClient.getStatus()
364        assert status.contains('Connected')
365        verifyReplyCode("stat", 211)
366    }
367
368    void testStor() {
369        ftpClientConnectAndLogin()
370
371        LOG.info("Put File for local path [$FILE1]")
372        def inputStream = new ByteArrayInputStream(ASCII_DATA.getBytes())
373        assert ftpClient.storeFile(FILE1, inputStream)
374        def contents = fileSystem.getEntry(FILE1).createInputStream().text
375        LOG.info("File contents=[" + contents + "]")
376        assert contents == ASCII_DATA
377    }
378
379    void testStou() {
380        ftpClientConnectAndLogin()
381
382        def inputStream = new ByteArrayInputStream(ASCII_DATA.getBytes())
383        assert ftpClient.storeUniqueFile(FILENAME1, inputStream)
384
385        def names = fileSystem.listNames(HOME_DIR)
386        def filename = names.find {name -> name.startsWith(FILENAME1) }
387        assert filename
388
389        def contents = fileSystem.getEntry(p(HOME_DIR, filename)).createInputStream().text
390        LOG.info("File contents=[" + contents + "]")
391        assert contents == ASCII_DATA
392    }
393
394    void testStru() {
395        ftpClientConnectAndLogin()
396        assert ftpClient.setFileStructure(FTP.FILE_STRUCTURE);
397        verifyReplyCode("STRU", 200)
398    }
399
400    void testSyst() {
401        ftpClientConnectAndLogin()
402
403        def systemName = ftpClient.getSystemName()
404        LOG.info("system name = [$systemName]")
405        assert systemName.contains('"' + SYSTEM_NAME + '"')
406        verifyReplyCode("getSystemName", 215)
407    }
408
409    void testType() {
410        ftpClientConnectAndLogin()
411        assert ftpClient.type(FTP.ASCII_FILE_TYPE)
412        verifyReplyCode("TYPE", 200)
413    }
414
415    void testUnrecognizedCommand() {
416        ftpClientConnectAndLogin()
417        assert ftpClient.sendCommand("XXX") == 502
418        verifyReplyCode("XXX", 502)
419    }
420
421    // -------------------------------------------------------------------------
422    // Test setup and tear-down
423    // -------------------------------------------------------------------------
424
425    /**
426     * Perform initialization before each test
427     * @see org.mockftpserver.test.AbstractTestCase#setUp()
428     */
429    void setUp() {
430        super.setUp()
431
432        for (int i = 0; i < BINARY_DATA.length; i++) {
433            BINARY_DATA[i] = (byte) i
434        }
435
436        ftpServer = new FakeFtpServer()
437        ftpServer.serverControlPort = PortTestUtil.getFtpServerControlPort()
438        ftpServer.systemName = SYSTEM_NAME
439
440        fileSystem = new WindowsFakeFileSystem()
441        fileSystem.createParentDirectoriesAutomatically = true
442        fileSystem.add(new DirectoryEntry(SUBDIR))
443        ftpServer.fileSystem = fileSystem
444
445        userAccount = new UserAccount(USERNAME, PASSWORD, HOME_DIR)
446        ftpServer.addUserAccount(userAccount)
447
448        ftpServer.start()
449        ftpClient = new FTPClient()
450    }
451
452    /**
453     * Perform cleanup after each test
454     * @see org.mockftpserver.test.AbstractTestCase#tearDown()
455     */
456    void tearDown() {
457        super.tearDown()
458        ftpServer.stop()
459    }
460
461    // -------------------------------------------------------------------------
462    // Internal Helper Methods
463    // -------------------------------------------------------------------------
464
465    private ftpClientConnectAndLogin() {
466        ftpClientConnect()
467        assert ftpClient.login(USERNAME, PASSWORD)
468    }
469
470    /**
471     * Connect to the server from the FTPClient
472     */
473    private void ftpClientConnect() {
474        def port = PortTestUtil.getFtpServerControlPort()
475        LOG.info("Conecting to $SERVER on port $port")
476        ftpClient.connect(SERVER, port)
477        verifyReplyCode("connect", 220)
478    }
479
480    /**
481     * Assert that the FtpClient reply code is equal to the expected value
482     *
483     * @param operation - the description of the operation performed used in the error message
484     * @param expectedReplyCode - the expected FtpClient reply code
485     */
486    private void verifyReplyCode(String operation, int expectedReplyCode) {
487        int replyCode = ftpClient.getReplyCode()
488        LOG.info("Reply: operation=\"" + operation + "\" replyCode=" + replyCode)
489        assertEquals("Unexpected replyCode for " + operation, expectedReplyCode, replyCode)
490    }
491
492    private void verifyFTPFile(FTPFile ftpFile, int type, String name, long size) {
493        LOG.info(ftpFile)
494        assertEquals("type: " + ftpFile, type, ftpFile.getType())
495        assertEquals("name: " + ftpFile, name, ftpFile.getName())
496        assertEquals("size: " + ftpFile, size, ftpFile.getSize())
497    }
498
499}