StubFtpServerIntegrationTest.java revision a5ff87feb19f5ff748fd4375b108d8be4a18686b
1/* 2 * Copyright 2007 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.stub; 17 18import org.apache.commons.net.ftp.FTP; 19import org.apache.commons.net.ftp.FTPClient; 20import org.apache.commons.net.ftp.FTPFile; 21import org.apache.log4j.Logger; 22import org.mockftpserver.core.command.CommandHandler; 23import org.mockftpserver.core.command.CommandNames; 24import org.mockftpserver.core.command.InvocationRecord; 25import org.mockftpserver.core.command.SimpleCompositeCommandHandler; 26import org.mockftpserver.core.command.StaticReplyCommandHandler; 27import org.mockftpserver.stub.command.*; 28import org.mockftpserver.test.*; 29import org.mockftpserver.test.AbstractTestCase; 30 31import java.io.ByteArrayInputStream; 32import java.io.ByteArrayOutputStream; 33import java.io.IOException; 34 35/** 36 * Tests for StubFtpServer using the Apache Jakarta Commons Net FTP client. 37 * 38 * @author Chris Mair 39 * @version $Revision$ - $Date$ 40 */ 41public final class StubFtpServerIntegrationTest extends AbstractTestCase implements IntegrationTest { 42 43 private static final Logger LOG = Logger.getLogger(StubFtpServerIntegrationTest.class); 44 private static final String SERVER = "localhost"; 45 private static final String USERNAME = "user123"; 46 private static final String PASSWORD = "password"; 47 private static final String FILENAME = "abc.txt"; 48 private static final String ASCII_CONTENTS = "abcdef\tghijklmnopqr"; 49 private static final byte[] BINARY_CONTENTS = new byte[256]; 50 51 private StubFtpServer stubFtpServer; 52 private FTPClient ftpClient; 53 private RetrCommandHandler retrCommandHandler; 54 private StorCommandHandler storCommandHandler; 55 56 //------------------------------------------------------------------------- 57 // Tests 58 //------------------------------------------------------------------------- 59 60 public void testLogin() throws Exception { 61 // Connect 62 LOG.info("Conecting to " + SERVER); 63 ftpClientConnect(); 64 verifyReplyCode("connect", 220); 65 66 // Login 67 String userAndPassword = USERNAME + "/" + PASSWORD; 68 LOG.info("Logging in as " + userAndPassword); 69 boolean success = ftpClient.login(USERNAME, PASSWORD); 70 assertTrue("Unable to login with " + userAndPassword, success); 71 verifyReplyCode("login with " + userAndPassword, 230); 72 73 assertTrue("isStarted", stubFtpServer.isStarted()); 74 assertFalse("isShutdown", stubFtpServer.isShutdown()); 75 76 // Quit 77 LOG.info("Quit"); 78 ftpClient.quit(); 79 verifyReplyCode("quit", 221); 80 } 81 82 public void testAcct() throws Exception { 83 ftpClientConnect(); 84 85 // ACCT 86 int replyCode = ftpClient.acct("123456"); 87 assertEquals("acct", 230, replyCode); 88 } 89 90 /** 91 * Test the stop() method when no session has ever been started 92 */ 93 public void testStop_NoSessionEverStarted() throws Exception { 94 LOG.info("Testing a stop() when no session has ever been started"); 95 } 96 97 public void testHelp() throws Exception { 98 // Modify HELP CommandHandler to return a predefined help message 99 final String HELP = "help message"; 100 HelpCommandHandler helpCommandHandler = (HelpCommandHandler) stubFtpServer.getCommandHandler(CommandNames.HELP); 101 helpCommandHandler.setHelpMessage(HELP); 102 103 ftpClientConnect(); 104 105 // HELP 106 String help = ftpClient.listHelp(); 107 assertTrue("Wrong response", help.indexOf(HELP) != -1); 108 verifyReplyCode("listHelp", 214); 109 } 110 111 /** 112 * Test the LIST and SYST commands. 113 */ 114 public void testList() throws Exception { 115 ftpClientConnect(); 116 117 // Set directory listing 118 ListCommandHandler listCommandHandler = (ListCommandHandler) stubFtpServer.getCommandHandler(CommandNames.LIST); 119 listCommandHandler.setDirectoryListing("11-09-01 12:30PM 406348 File2350.log\n" 120 + "11-01-01 1:30PM <DIR> archive"); 121 122 // LIST 123 FTPFile[] files = ftpClient.listFiles(); 124 assertEquals("number of files", 2, files.length); 125 verifyFTPFile(files[0], FTPFile.FILE_TYPE, "File2350.log", 406348L); 126 verifyFTPFile(files[1], FTPFile.DIRECTORY_TYPE, "archive", 0L); 127 verifyReplyCode("list", 226); 128 } 129 130 /** 131 * Test the LIST, PASV and SYST commands, transferring a directory listing in passive mode 132 */ 133 public void testList_PassiveMode() throws Exception { 134 ftpClientConnect(); 135 136 ftpClient.enterLocalPassiveMode(); 137 138 // Set directory listing 139 ListCommandHandler listCommandHandler = (ListCommandHandler) stubFtpServer.getCommandHandler(CommandNames.LIST); 140 listCommandHandler.setDirectoryListing("11-09-01 12:30PM 406348 File2350.log"); 141 142 // LIST 143 FTPFile[] files = ftpClient.listFiles(); 144 assertEquals("number of files", 1, files.length); 145 verifyReplyCode("list", 226); 146 } 147 148 public void testNlst() throws Exception { 149 ftpClientConnect(); 150 151 // Set directory listing 152 NlstCommandHandler nlstCommandHandler = (NlstCommandHandler) stubFtpServer.getCommandHandler(CommandNames.NLST); 153 nlstCommandHandler.setDirectoryListing("File1.txt\nfile2.data"); 154 155 // NLST 156 String[] filenames = ftpClient.listNames(); 157 assertEquals("number of files", 2, filenames.length); 158 assertEquals(filenames[0], "File1.txt"); 159 assertEquals(filenames[1], "file2.data"); 160 verifyReplyCode("listNames", 226); 161 } 162 163 /** 164 * Test printing the current working directory (PWD) 165 */ 166 public void testPwd() throws Exception { 167 // Modify PWD CommandHandler to return a predefined directory 168 final String DIR = "some/dir"; 169 PwdCommandHandler pwdCommandHandler = (PwdCommandHandler) stubFtpServer.getCommandHandler(CommandNames.PWD); 170 pwdCommandHandler.setDirectory(DIR); 171 172 ftpClientConnect(); 173 174 // PWD 175 String dir = ftpClient.printWorkingDirectory(); 176 assertEquals("Unable to PWD", DIR, dir); 177 verifyReplyCode("printWorkingDirectory", 257); 178 } 179 180 public void testStat() throws Exception { 181 // Modify Stat CommandHandler to return predefined text 182 final String STATUS = "some information 123"; 183 StatCommandHandler statCommandHandler = (StatCommandHandler) stubFtpServer.getCommandHandler(CommandNames.STAT); 184 statCommandHandler.setStatus(STATUS); 185 186 ftpClientConnect(); 187 188 // STAT 189 String status = ftpClient.getStatus(); 190 assertEquals("STAT reply", "211 " + STATUS + ".", status.trim()); 191 verifyReplyCode("getStatus", 211); 192 } 193 194 /** 195 * Test getting the status (STAT), when the reply text contains multiple lines 196 */ 197 public void testStat_MultilineReplyText() throws Exception { 198 // Modify Stat CommandHandler to return predefined text 199 final String STATUS = "System name: abc.def\nVersion 3.5.7\nNumber of failed logins: 2"; 200 final String FORMATTED_REPLY_STATUS = "211-System name: abc.def\r\nVersion 3.5.7\r\n211 Number of failed logins: 2."; 201 StatCommandHandler statCommandHandler = (StatCommandHandler) stubFtpServer.getCommandHandler(CommandNames.STAT); 202 statCommandHandler.setStatus(STATUS); 203 204 ftpClientConnect(); 205 206 // STAT 207 String status = ftpClient.getStatus(); 208 assertEquals("STAT reply", FORMATTED_REPLY_STATUS, status.trim()); 209 verifyReplyCode("getStatus", 211); 210 } 211 212 public void testSyst() throws Exception { 213 ftpClientConnect(); 214 215 // SYST 216 assertEquals("getSystemName()", "\"WINDOWS\" system type.", ftpClient.getSystemName()); 217 verifyReplyCode("syst", 215); 218 } 219 220 public void testCwd() throws Exception { 221 // Connect 222 LOG.info("Conecting to " + SERVER); 223 ftpClientConnect(); 224 verifyReplyCode("connect", 220); 225 226 // CWD 227 boolean success = ftpClient.changeWorkingDirectory("dir1/dir2"); 228 assertTrue("Unable to CWD", success); 229 verifyReplyCode("changeWorkingDirectory", 250); 230 } 231 232 /** 233 * Test changing the current working directory (CWD), when it causes a remote error 234 */ 235 public void testCwd_Error() throws Exception { 236 // Override CWD CommandHandler to return error reply code 237 final int REPLY_CODE = 500; 238 StaticReplyCommandHandler cwdCommandHandler = new StaticReplyCommandHandler(REPLY_CODE); 239 stubFtpServer.setCommandHandler("CWD", cwdCommandHandler); 240 241 ftpClientConnect(); 242 243 // CWD 244 boolean success = ftpClient.changeWorkingDirectory("dir1/dir2"); 245 assertFalse("Expected failure", success); 246 verifyReplyCode("changeWorkingDirectory", REPLY_CODE); 247 } 248 249 public void testCdup() throws Exception { 250 ftpClientConnect(); 251 252 // CDUP 253 boolean success = ftpClient.changeToParentDirectory(); 254 assertTrue("Unable to CDUP", success); 255 verifyReplyCode("changeToParentDirectory", 200); 256 } 257 258 public void testDele() throws Exception { 259 ftpClientConnect(); 260 261 // DELE 262 boolean success = ftpClient.deleteFile(FILENAME); 263 assertTrue("Unable to DELE", success); 264 verifyReplyCode("deleteFile", 250); 265 } 266 267 public void testEprt() throws Exception { 268 ftpClientConnect(); 269 ftpClient.sendCommand("EPRT", "|2|1080::8:800:200C:417A|5282|"); 270 verifyReplyCode("EPRT", 200); 271 } 272 273 public void testEpsv() throws Exception { 274 ftpClientConnect(); 275 ftpClient.sendCommand("EPSV"); 276 verifyReplyCode("EPSV", 229); 277 } 278 279 public void testFeat_UseStaticReplyCommandHandler() throws IOException { 280 // The FEAT command is not supported out of the box 281 final String FEAT_TEXT = "Extensions supported:\n" + 282 "MLST size*;create;modify*;perm;media-type\n" + 283 "SIZE\n" + 284 "COMPRESSION\n" + 285 "END"; 286 StaticReplyCommandHandler featCommandHandler = new StaticReplyCommandHandler(211, FEAT_TEXT); 287 stubFtpServer.setCommandHandler("FEAT", featCommandHandler); 288 289 ftpClientConnect(); 290 assertEquals(ftpClient.sendCommand("FEAT"), 211); 291 LOG.info(ftpClient.getReplyString()); 292 } 293 294 public void testMkd() throws Exception { 295 ftpClientConnect(); 296 297 // MKD 298 boolean success = ftpClient.makeDirectory("dir1/dir2"); 299 assertTrue("Unable to CWD", success); 300 verifyReplyCode("makeDirectory", 257); 301 } 302 303 public void testNoop() throws Exception { 304 ftpClientConnect(); 305 306 // NOOP 307 boolean success = ftpClient.sendNoOp(); 308 assertTrue("Unable to NOOP", success); 309 verifyReplyCode("NOOP", 200); 310 } 311 312 public void testRest() throws Exception { 313 ftpClientConnect(); 314 315 // REST 316 int replyCode = ftpClient.rest("marker"); 317 assertEquals("Unable to REST", 350, replyCode); 318 } 319 320 public void testRmd() throws Exception { 321 ftpClientConnect(); 322 323 // RMD 324 boolean success = ftpClient.removeDirectory("dir1/dir2"); 325 assertTrue("Unable to RMD", success); 326 verifyReplyCode("removeDirectory", 250); 327 } 328 329 public void testRename() throws Exception { 330 ftpClientConnect(); 331 332 // Rename (RNFR, RNTO) 333 boolean success = ftpClient.rename(FILENAME, "new_" + FILENAME); 334 assertTrue("Unable to RENAME", success); 335 verifyReplyCode("rename", 250); 336 } 337 338 public void testAllo() throws Exception { 339 ftpClientConnect(); 340 341 // ALLO 342 assertTrue("ALLO", ftpClient.allocate(1024)); 343 assertTrue("ALLO with recordSize", ftpClient.allocate(1024, 64)); 344 } 345 346 /** 347 * Test GET and PUT of ASCII files 348 */ 349 public void testTransferAsciiFile() throws Exception { 350 retrCommandHandler.setFileContents(ASCII_CONTENTS); 351 352 ftpClientConnect(); 353 354 // Get File 355 LOG.info("Get File for remotePath [" + FILENAME + "]"); 356 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 357 assertTrue(ftpClient.retrieveFile(FILENAME, outputStream)); 358 LOG.info("File contents=[" + outputStream.toString()); 359 assertEquals("File contents", ASCII_CONTENTS, outputStream.toString()); 360 361 // Put File 362 LOG.info("Put File for local path [" + FILENAME + "]"); 363 ByteArrayInputStream inputStream = new ByteArrayInputStream(ASCII_CONTENTS.getBytes()); 364 assertTrue(ftpClient.storeFile(FILENAME, inputStream)); 365 InvocationRecord invocationRecord = storCommandHandler.getInvocation(0); 366 byte[] contents = (byte[]) invocationRecord.getObject(StorCommandHandler.FILE_CONTENTS_KEY); 367 LOG.info("File contents=[" + contents + "]"); 368 assertEquals("File contents", ASCII_CONTENTS.getBytes(), contents); 369 } 370 371 /** 372 * Test GET and PUT of binary files 373 */ 374 public void testTransferBinaryFiles() throws Exception { 375 retrCommandHandler.setFileContents(BINARY_CONTENTS); 376 377 ftpClientConnect(); 378 ftpClient.setFileType(FTP.BINARY_FILE_TYPE); 379 380 // Get File 381 LOG.info("Get File for remotePath [" + FILENAME + "]"); 382 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 383 assertTrue("GET", ftpClient.retrieveFile(FILENAME, outputStream)); 384 LOG.info("GET File length=" + outputStream.size()); 385 assertEquals("File contents", BINARY_CONTENTS, outputStream.toByteArray()); 386 387 // Put File 388 LOG.info("Put File for local path [" + FILENAME + "]"); 389 ByteArrayInputStream inputStream = new ByteArrayInputStream(BINARY_CONTENTS); 390 assertTrue("PUT", ftpClient.storeFile(FILENAME, inputStream)); 391 InvocationRecord invocationRecord = storCommandHandler.getInvocation(0); 392 byte[] contents = (byte[]) invocationRecord.getObject(StorCommandHandler.FILE_CONTENTS_KEY); 393 LOG.info("PUT File length=" + contents.length); 394 assertEquals("File contents", BINARY_CONTENTS, contents); 395 } 396 397 public void testStou() throws Exception { 398 StouCommandHandler stouCommandHandler = (StouCommandHandler) stubFtpServer.getCommandHandler(CommandNames.STOU); 399 stouCommandHandler.setFilename(FILENAME); 400 401 ftpClientConnect(); 402 403 // Stor a File (STOU) 404 ByteArrayInputStream inputStream = new ByteArrayInputStream(ASCII_CONTENTS.getBytes()); 405 assertTrue(ftpClient.storeUniqueFile(FILENAME, inputStream)); 406 InvocationRecord invocationRecord = stouCommandHandler.getInvocation(0); 407 byte[] contents = (byte[]) invocationRecord.getObject(StorCommandHandler.FILE_CONTENTS_KEY); 408 LOG.info("File contents=[" + contents + "]"); 409 assertEquals("File contents", ASCII_CONTENTS.getBytes(), contents); 410 } 411 412 public void testAppe() throws Exception { 413 AppeCommandHandler appeCommandHandler = (AppeCommandHandler) stubFtpServer.getCommandHandler(CommandNames.APPE); 414 415 ftpClientConnect(); 416 417 // Append a File (APPE) 418 ByteArrayInputStream inputStream = new ByteArrayInputStream(ASCII_CONTENTS.getBytes()); 419 assertTrue(ftpClient.appendFile(FILENAME, inputStream)); 420 InvocationRecord invocationRecord = appeCommandHandler.getInvocation(0); 421 byte[] contents = (byte[]) invocationRecord.getObject(AppeCommandHandler.FILE_CONTENTS_KEY); 422 LOG.info("File contents=[" + contents + "]"); 423 assertEquals("File contents", ASCII_CONTENTS.getBytes(), contents); 424 } 425 426 public void testAbor() throws Exception { 427 ftpClientConnect(); 428 429 // ABOR 430 assertTrue("ABOR", ftpClient.abort()); 431 } 432 433 public void testPasv() throws Exception { 434 ftpClientConnect(); 435 436 // PASV 437 ftpClient.enterLocalPassiveMode(); 438 // no reply code; the PASV command is sent only when the data connection is opened 439 } 440 441 public void testMode() throws Exception { 442 ftpClientConnect(); 443 444 // MODE 445 boolean success = ftpClient.setFileTransferMode(FTP.STREAM_TRANSFER_MODE); 446 assertTrue("Unable to MODE", success); 447 verifyReplyCode("setFileTransferMode", 200); 448 } 449 450 public void testStru() throws Exception { 451 ftpClientConnect(); 452 453 // STRU 454 boolean success = ftpClient.setFileStructure(FTP.FILE_STRUCTURE); 455 assertTrue("Unable to STRU", success); 456 verifyReplyCode("setFileStructure", 200); 457 } 458 459 public void testSimpleCompositeCommandHandler() throws Exception { 460 // Replace CWD CommandHandler with a SimpleCompositeCommandHandler 461 CommandHandler commandHandler1 = new StaticReplyCommandHandler(500); 462 CommandHandler commandHandler2 = new CwdCommandHandler(); 463 SimpleCompositeCommandHandler simpleCompositeCommandHandler = new SimpleCompositeCommandHandler(); 464 simpleCompositeCommandHandler.addCommandHandler(commandHandler1); 465 simpleCompositeCommandHandler.addCommandHandler(commandHandler2); 466 stubFtpServer.setCommandHandler("CWD", simpleCompositeCommandHandler); 467 468 // Connect 469 ftpClientConnect(); 470 471 // CWD 472 assertFalse("first", ftpClient.changeWorkingDirectory("dir1/dir2")); 473 assertTrue("first", ftpClient.changeWorkingDirectory("dir1/dir2")); 474 } 475 476 public void testSite() throws Exception { 477 ftpClientConnect(); 478 479 // SITE 480 int replyCode = ftpClient.site("parameters,1,2,3"); 481 assertEquals("SITE", 200, replyCode); 482 } 483 484 public void testSmnt() throws Exception { 485 ftpClientConnect(); 486 487 // SMNT 488 assertTrue("SMNT", ftpClient.structureMount("dir1/dir2")); 489 verifyReplyCode("structureMount", 250); 490 } 491 492 public void testRein() throws Exception { 493 ftpClientConnect(); 494 495 // REIN 496 assertEquals("REIN", 220, ftpClient.rein()); 497 } 498 499 /** 500 * Test that command names in lowercase or mixed upper/lower case are accepted 501 */ 502 public void testCommandNamesInLowerOrMixedCase() throws Exception { 503 ftpClientConnect(); 504 505 assertEquals("rein", 220, ftpClient.sendCommand("rein")); 506 assertEquals("rEIn", 220, ftpClient.sendCommand("rEIn")); 507 assertEquals("reiN", 220, ftpClient.sendCommand("reiN")); 508 assertEquals("Rein", 220, ftpClient.sendCommand("Rein")); 509 } 510 511 public void testUnrecognizedCommand() throws Exception { 512 ftpClientConnect(); 513 514 assertEquals("Unrecognized:XXXX", 502, ftpClient.sendCommand("XXXX")); 515 } 516 517 // ------------------------------------------------------------------------- 518 // Test setup and tear-down 519 // ------------------------------------------------------------------------- 520 521 /** 522 * Perform initialization before each test 523 * 524 * @see org.mockftpserver.test.AbstractTestCase#setUp() 525 */ 526 protected void setUp() throws Exception { 527 super.setUp(); 528 529 for (int i = 0; i < BINARY_CONTENTS.length; i++) { 530 BINARY_CONTENTS[i] = (byte) i; 531 } 532 533 stubFtpServer = new StubFtpServer(); 534 stubFtpServer.setServerControlPort(PortTestUtil.getFtpServerControlPort()); 535 stubFtpServer.start(); 536 ftpClient = new FTPClient(); 537 retrCommandHandler = (RetrCommandHandler) stubFtpServer.getCommandHandler(CommandNames.RETR); 538 storCommandHandler = (StorCommandHandler) stubFtpServer.getCommandHandler(CommandNames.STOR); 539 } 540 541 /** 542 * Perform cleanup after each test 543 * 544 * @see org.mockftpserver.test.AbstractTestCase#tearDown() 545 */ 546 protected void tearDown() throws Exception { 547 super.tearDown(); 548 stubFtpServer.stop(); 549 } 550 551 // ------------------------------------------------------------------------- 552 // Internal Helper Methods 553 // ------------------------------------------------------------------------- 554 555 /** 556 * Connect to the server from the FTPClient 557 */ 558 private void ftpClientConnect() throws IOException { 559 ftpClient.connect(SERVER, PortTestUtil.getFtpServerControlPort()); 560 } 561 562 /** 563 * Assert that the FtpClient reply code is equal to the expected value 564 * 565 * @param operation - the description of the operation performed; used in the error message 566 * @param expectedReplyCode - the expected FtpClient reply code 567 */ 568 private void verifyReplyCode(String operation, int expectedReplyCode) { 569 int replyCode = ftpClient.getReplyCode(); 570 LOG.info("Reply: operation=\"" + operation + "\" replyCode=" + replyCode); 571 assertEquals("Unexpected replyCode for " + operation, expectedReplyCode, replyCode); 572 } 573 574 /** 575 * Verify that the FTPFile has the specified properties 576 * 577 * @param ftpFile - the FTPFile to verify 578 * @param type - the expected file type 579 * @param name - the expected file name 580 * @param size - the expected file size (will be zero for a directory) 581 */ 582 private void verifyFTPFile(FTPFile ftpFile, int type, String name, long size) { 583 LOG.info(ftpFile); 584 assertEquals("type: " + ftpFile, type, ftpFile.getType()); 585 assertEquals("name: " + ftpFile, name, ftpFile.getName()); 586 assertEquals("size: " + ftpFile, size, ftpFile.getSize()); 587 } 588 589} 590