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