FakeFtpServerIntegrationTest.groovy revision 0da0f84ad9831bea470896e79b5ec207c83f258e
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.StaticReplyCommandHandler
22import org.mockftpserver.fake.FakeFtpServer
23import org.mockftpserver.fake.UserAccount
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    void testCwd_UseStaticReplyCommandHandler() {
111        final int REPLY_CODE = 500;
112        StaticReplyCommandHandler cwdCommandHandler = new StaticReplyCommandHandler(REPLY_CODE);
113        ftpServer.setCommandHandler("CWD", cwdCommandHandler);
114
115        ftpClientConnectAndLogin()
116        assert !ftpClient.changeWorkingDirectory(SUBDIR_NAME)
117        verifyReplyCode("changeWorkingDirectory", REPLY_CODE)
118    }
119
120    void testCwd_UseStubCommandHandler() {
121        final int REPLY_CODE = 502;
122        CwdCommandHandler cwdCommandHandler = new CwdCommandHandler();     // Stub command handler
123        cwdCommandHandler.setReplyCode(REPLY_CODE);
124        ftpServer.setCommandHandler("CWD", cwdCommandHandler);
125
126        ftpClientConnectAndLogin()
127        assert !ftpClient.changeWorkingDirectory(SUBDIR_NAME)
128        verifyReplyCode("changeWorkingDirectory", REPLY_CODE)
129        assert cwdCommandHandler.getInvocation(0)
130    }
131
132    void testDele() {
133        fileSystem.add(new FileEntry(FILE1))
134
135        ftpClientConnectAndLogin()
136        assert ftpClient.deleteFile(FILENAME1)
137        verifyReplyCode("deleteFile", 250)
138        assert !fileSystem.exists(FILENAME1)
139    }
140
141    void testHelp() {
142        ftpServer.helpText = [a: 'aaa', '': 'default']
143        ftpClientConnect()
144
145        String help = ftpClient.listHelp()
146        assert help.contains('default')
147        verifyReplyCode("listHelp", 214)
148
149        help = ftpClient.listHelp('a')
150        assert help.contains('aaa')
151        verifyReplyCode("listHelp", 214)
152
153        help = ftpClient.listHelp('bad')
154        assert help.contains('bad')
155        verifyReplyCode("listHelp", 214)
156    }
157
158    void testList() {
159        def LAST_MODIFIED = new Date()
160        fileSystem.add(new FileEntry(path: p(SUBDIR, FILENAME1), lastModified: LAST_MODIFIED, contents: ASCII_DATA))
161        fileSystem.add(new DirectoryEntry(path: p(SUBDIR, SUBDIR_NAME2), lastModified: LAST_MODIFIED))
162
163        ftpClientConnectAndLogin()
164
165        FTPFile[] files = ftpClient.listFiles(SUBDIR)
166        assert files.length == 2
167        verifyFTPFile(files[0], FTPFile.FILE_TYPE, FILENAME1, ASCII_DATA.size())
168        verifyFTPFile(files[1], FTPFile.DIRECTORY_TYPE, SUBDIR_NAME2, 0)
169        verifyReplyCode("list", 226)
170    }
171
172    void testList_Unix() {
173        ftpServer.systemName = 'UNIX'
174        userAccount.homeDirectory = '/'
175
176        def unixFileSystem = new UnixFakeFileSystem()
177        unixFileSystem.createParentDirectoriesAutomatically = true
178        unixFileSystem.add(new DirectoryEntry('/'))
179        ftpServer.fileSystem = unixFileSystem
180
181        def LAST_MODIFIED = new Date()
182        unixFileSystem.add(new FileEntry(path: p('/', FILENAME1), lastModified: LAST_MODIFIED, contents: ASCII_DATA))
183        unixFileSystem.add(new DirectoryEntry(path: p('/', SUBDIR_NAME2), lastModified: LAST_MODIFIED))
184
185        ftpClientConnectAndLogin()
186
187        FTPFile[] files = ftpClient.listFiles('/')
188        assert files.length == 2
189        verifyFTPFile(files[0], FTPFile.DIRECTORY_TYPE, SUBDIR_NAME2, 0)
190        verifyFTPFile(files[1], FTPFile.FILE_TYPE, FILENAME1, ASCII_DATA.size())
191        verifyReplyCode("list", 226)
192    }
193
194    void testLogin() {
195        ftpClientConnect()
196        LOG.info("Logging in as $USERNAME/$PASSWORD")
197        assert ftpClient.login(USERNAME, PASSWORD)
198        verifyReplyCode("login with $USERNAME/$PASSWORD", 230)
199    }
200
201    void testLogin_WithAccount() {
202        userAccount.accountRequiredForLogin = true
203        ftpClientConnect()
204        LOG.info("Logging in as $USERNAME/$PASSWORD with $ACCOUNT")
205        assert ftpClient.login(USERNAME, PASSWORD, ACCOUNT)
206        verifyReplyCode("login with $USERNAME/$PASSWORD with $ACCOUNT", 230)
207    }
208
209    void testMkd() {
210        ftpClientConnectAndLogin()
211
212        def DIR = p(HOME_DIR, 'NewDir')
213        assert ftpClient.makeDirectory(DIR)
214        verifyReplyCode("makeDirectory", 257)
215        assert fileSystem.isDirectory(DIR)
216    }
217
218    void testMode() {
219        ftpClientConnectAndLogin()
220        assert ftpClient.setFileTransferMode(FTP.STREAM_TRANSFER_MODE);
221        verifyReplyCode("MODE", 200)
222    }
223
224    void testNlst() {
225        fileSystem.add(new FileEntry(path: p(SUBDIR, FILENAME1)))
226        fileSystem.add(new DirectoryEntry(path: p(SUBDIR, SUBDIR_NAME2)))
227
228        ftpClientConnectAndLogin()
229
230        String[] filenames = ftpClient.listNames(SUBDIR)
231        assert filenames == [FILENAME1, SUBDIR_NAME2]
232        verifyReplyCode("listNames", 226)
233    }
234
235    void testNoop() {
236        ftpClientConnectAndLogin()
237        assert ftpClient.sendNoOp()
238        verifyReplyCode("NOOP", 200)
239    }
240
241    void testPasv_Nlst() {
242        fileSystem.add(new FileEntry(path: p(SUBDIR, FILENAME1)))
243        fileSystem.add(new FileEntry(path: p(SUBDIR, FILENAME2)))
244
245        ftpClientConnectAndLogin()
246        ftpClient.enterLocalPassiveMode();
247
248        String[] filenames = ftpClient.listNames(SUBDIR)
249        assert filenames == [FILENAME1, FILENAME2]
250        verifyReplyCode("listNames", 226)
251    }
252
253    void testPwd() {
254        ftpClientConnectAndLogin()
255        assert ftpClient.printWorkingDirectory() == HOME_DIR
256        verifyReplyCode("printWorkingDirectory", 257)
257    }
258
259    void testQuit() {
260        ftpClientConnect()
261        ftpClient.quit()
262        verifyReplyCode("quit", 221)
263    }
264
265    void testRein() {
266        ftpClientConnectAndLogin()
267        assert ftpClient.rein() == 220
268        assert ftpClient.cdup() == 530      // now logged out
269    }
270
271    void testRest() {
272        ftpClientConnectAndLogin()
273        assert ftpClient.rest("marker") == 350
274    }
275
276    void testRetr() {
277        fileSystem.add(new FileEntry(path: FILE1, contents: ASCII_DATA))
278
279        ftpClientConnectAndLogin()
280
281        LOG.info("Get File for remotePath [$FILE1]")
282        def outputStream = new ByteArrayOutputStream()
283        assert ftpClient.retrieveFile(FILE1, outputStream)
284        LOG.info("File contents=[${outputStream.toString()}]")
285        assert outputStream.toString() == ASCII_DATA
286    }
287
288    void testRmd() {
289        ftpClientConnectAndLogin()
290
291        assert ftpClient.removeDirectory(SUBDIR)
292        verifyReplyCode("removeDirectory", 250)
293        assert !fileSystem.exists(SUBDIR)
294    }
295
296    void testRename() {                 // RNFR and RNTO
297        fileSystem.add(new FileEntry(FILE1))
298
299        ftpClientConnectAndLogin()
300
301        assert ftpClient.rename(FILE1, FILE1 + "NEW")
302        verifyReplyCode("rename", 250)
303        assert !fileSystem.exists(FILE1)
304        assert fileSystem.exists(FILE1 + "NEW")
305    }
306
307    void testSite() {
308        ftpClientConnectAndLogin()
309        assert ftpClient.site("parameters,1,2,3") == 200
310    }
311
312    void testStor() {
313        ftpClientConnectAndLogin()
314
315        LOG.info("Put File for local path [$FILE1]")
316        def inputStream = new ByteArrayInputStream(ASCII_DATA.getBytes())
317        assert ftpClient.storeFile(FILE1, inputStream)
318        def contents = fileSystem.getEntry(FILE1).createInputStream().text
319        LOG.info("File contents=[" + contents + "]")
320        assert contents == ASCII_DATA
321    }
322
323    void testStou() {
324        ftpClientConnectAndLogin()
325
326        def inputStream = new ByteArrayInputStream(ASCII_DATA.getBytes())
327        assert ftpClient.storeUniqueFile(FILENAME1, inputStream)
328
329        def names = fileSystem.listNames(HOME_DIR)
330        def filename = names.find {name -> name.startsWith(FILENAME1) }
331        assert filename
332
333        def contents = fileSystem.getEntry(p(HOME_DIR, filename)).createInputStream().text
334        LOG.info("File contents=[" + contents + "]")
335        assert contents == ASCII_DATA
336    }
337
338    void testStru() {
339        ftpClientConnectAndLogin()
340        assert ftpClient.setFileStructure(FTP.FILE_STRUCTURE);
341        verifyReplyCode("STRU", 200)
342    }
343
344    void testSyst() {
345        ftpClientConnectAndLogin()
346
347        def systemName = ftpClient.getSystemName()
348        LOG.info("system name = [$systemName]")
349        assert systemName.contains('"' + SYSTEM_NAME + '"')
350        verifyReplyCode("getSystemName", 215)
351    }
352
353    void testType() {
354        ftpClientConnectAndLogin()
355        assert ftpClient.type(FTP.ASCII_FILE_TYPE)
356        verifyReplyCode("TYPE", 200)
357    }
358
359    // -------------------------------------------------------------------------
360    // Test setup and tear-down
361    // -------------------------------------------------------------------------
362
363    /**
364     * Perform initialization before each test
365     * @see org.mockftpserver.test.AbstractTest#setUp()
366     */
367    void setUp() {
368        super.setUp()
369
370        for (int i = 0; i < BINARY_DATA.length; i++) {
371            BINARY_DATA[i] = (byte) i
372        }
373
374        ftpServer = new FakeFtpServer()
375        ftpServer.serverControlPort = PortTestUtil.getFtpServerControlPort()
376        ftpServer.systemName = SYSTEM_NAME
377
378        fileSystem = new WindowsFakeFileSystem()
379        fileSystem.createParentDirectoriesAutomatically = true
380        fileSystem.add(new DirectoryEntry(SUBDIR))
381        ftpServer.fileSystem = fileSystem
382
383        userAccount = new UserAccount(USERNAME, PASSWORD, HOME_DIR)
384        ftpServer.userAccounts[USERNAME] = userAccount
385
386        ftpServer.start()
387        ftpClient = new FTPClient()
388    }
389
390    /**
391     * Perform cleanup after each test
392     * @see org.mockftpserver.test.AbstractTest#tearDown()
393     */
394    void tearDown() {
395        super.tearDown()
396        ftpServer.stop()
397    }
398
399    // -------------------------------------------------------------------------
400    // Internal Helper Methods
401    // -------------------------------------------------------------------------
402
403    private ftpClientConnectAndLogin() {
404        ftpClientConnect()
405        assert ftpClient.login(USERNAME, PASSWORD)
406    }
407
408    /**
409     * Connect to the server from the FTPClient
410     */
411    private void ftpClientConnect() {
412        def port = PortTestUtil.getFtpServerControlPort()
413        LOG.info("Conecting to $SERVER on port $port")
414        ftpClient.connect(SERVER, port)
415        verifyReplyCode("connect", 220)
416    }
417
418    /**
419     * Assert that the FtpClient reply code is equal to the expected value
420     *
421     * @param operation - the description of the operation performed used in the error message
422     * @param expectedReplyCode - the expected FtpClient reply code
423     */
424    private void verifyReplyCode(String operation, int expectedReplyCode) {
425        int replyCode = ftpClient.getReplyCode()
426        LOG.info("Reply: operation=\"" + operation + "\" replyCode=" + replyCode)
427        assertEquals("Unexpected replyCode for " + operation, expectedReplyCode, replyCode)
428    }
429
430    private void verifyFTPFile(FTPFile ftpFile, int type, String name, long size) {
431        LOG.info(ftpFile)
432        assertEquals("type: " + ftpFile, type, ftpFile.getType())
433        assertEquals("name: " + ftpFile, name, ftpFile.getName())
434        assertEquals("size: " + ftpFile, size, ftpFile.getSize())
435    }
436
437}