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