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 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(FILENAME1, inputStream) // relative to homeDirectory 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}