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