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 */ 16 package org.mockftpserver.fake.command 17 18 import org.mockftpserver.core.command.Command 19 import org.mockftpserver.core.command.CommandHandler 20 import org.mockftpserver.core.command.ReplyCodes 21 import org.mockftpserver.core.session.SessionKeys 22 import org.mockftpserver.core.session.StubSession 23 import org.mockftpserver.fake.StubServerConfiguration 24 import org.mockftpserver.fake.UserAccount 25 import org.mockftpserver.fake.filesystem.DirectoryEntry 26 import org.mockftpserver.fake.filesystem.FileEntry 27 import org.mockftpserver.fake.filesystem.FileSystemException 28 import org.mockftpserver.fake.filesystem.TestUnixFakeFileSystem 29 import org.mockftpserver.test.AbstractGroovyTestCase 30 import org.mockftpserver.test.StubResourceBundle 31 32 /** 33 * Abstract superclass for CommandHandler tests 34 * 35 * @version $Revision$ - $Date$ 36 * 37 * @author Chris Mair 38 */ 39 abstract class AbstractFakeCommandHandlerTestCase extends AbstractGroovyTestCase { 40 41 protected static final ERROR_MESSAGE_KEY = 'msgkey' 42 43 protected session 44 protected serverConfiguration 45 protected replyTextBundle 46 protected commandHandler 47 protected fileSystem 48 protected userAccount 49 50 /** Set this to false to skip the test that verifies that the CommandHandler requires a logged in user */ 51 boolean testNotLoggedIn = true 52 53 //------------------------------------------------------------------------- 54 // Tests (common to all subclasses) 55 //------------------------------------------------------------------------- 56 57 void testHandleCommand_ServerConfigurationIsNull() { 58 commandHandler.serverConfiguration = null 59 def command = createValidCommand() 60 shouldFailWithMessageContaining("serverConfiguration") { commandHandler.handleCommand(command, session) } 61 } 62 63 void testHandleCommand_CommandIsNull() { 64 shouldFailWithMessageContaining("command") { commandHandler.handleCommand(null, session) } 65 } 66 67 void testHandleCommand_SessionIsNull() { 68 def command = createValidCommand() 69 shouldFailWithMessageContaining("session") { commandHandler.handleCommand(command, null) } 70 } 71 72 void testHandleCommand_NotLoggedIn() { 73 if (getProperty('testNotLoggedIn')) { 74 def command = createValidCommand() 75 session.removeAttribute(SessionKeys.USER_ACCOUNT) 76 commandHandler.handleCommand(command, session) 77 assertSessionReply(ReplyCodes.NOT_LOGGED_IN) 78 } 79 } 80 81 //------------------------------------------------------------------------- 82 // Abstract Method Declarations (must be implemented by all subclasses) 83 //------------------------------------------------------------------------- 84 85 /** 86 * Create and return a new instance of the CommandHandler class under test. Concrete subclasses must implement. 87 */ 88 abstract CommandHandler createCommandHandler() 89 90 /** 91 * Create and return a valid instance of the Command for the CommandHandler class 92 * under test. Concrete subclasses must implement. 93 */ 94 abstract Command createValidCommand() 95 96 //------------------------------------------------------------------------- 97 // Test Setup 98 //------------------------------------------------------------------------- 99 100 void setUp() { 101 super.setUp() 102 session = new StubSession() 103 serverConfiguration = new StubServerConfiguration() 104 replyTextBundle = new StubResourceBundle() 105 fileSystem = new TestUnixFakeFileSystem() 106 fileSystem.createParentDirectoriesAutomatically = true 107 serverConfiguration.setFileSystem(fileSystem) 108 109 userAccount = new UserAccount() 110 session.setAttribute(SessionKeys.USER_ACCOUNT, userAccount) 111 112 commandHandler = createCommandHandler() 113 commandHandler.serverConfiguration = serverConfiguration 114 commandHandler.replyTextBundle = replyTextBundle 115 } 116 117 //------------------------------------------------------------------------- 118 // Helper Methods 119 //------------------------------------------------------------------------- 120 121 /** 122 * Perform a test of the handleCommand() method on the specified command 123 * parameters, which are missing a required parameter for this CommandHandler. 124 */ 125 protected void testHandleCommand_MissingRequiredParameter(List commandParameters) { 126 commandHandler.handleCommand(createCommand(commandParameters), session) 127 assertSessionReply(ReplyCodes.COMMAND_SYNTAX_ERROR) 128 } 129 130 /** 131 * Perform a test of the handleCommand() method on the specified command 132 * parameters, which are missing a required parameter for this CommandHandler. 133 */ 134 protected testHandleCommand_MissingRequiredSessionAttribute() { 135 def command = createValidCommand() 136 commandHandler.handleCommand(command, session) 137 assertSessionReply(ReplyCodes.ILLEGAL_STATE) 138 } 139 140 /** 141 * @return a new Command with the specified parameters for this CommandHandler 142 */ 143 protected Command createCommand(List commandParameters) { 144 new Command(createValidCommand().name, commandParameters) 145 } 146 147 /** 148 * Invoke the handleCommand() method for the current CommandHandler, passing in 149 * the specified parameters 150 * @param parameters - the List of command parameters; may be empty, but not null 151 */ 152 protected void handleCommand(List parameters) { 153 commandHandler.handleCommand(createCommand(parameters), session) 154 } 155 156 /** 157 * Assert that the specified reply code and message containing text was sent through the session. 158 * @param expectedReplyCode - the expected reply code 159 * @param text - the text expected within the reply message; defaults to the reply code as a String 160 */ 161 protected assertSessionReply(int expectedReplyCode, text = expectedReplyCode as String) { 162 assertSessionReply(0, expectedReplyCode, text) 163 } 164 165 /** 166 * Assert that the specified reply code and message containing text was sent through the session. 167 * @param replyIndex - the index of the reply to compare 168 * @param expectedReplyCode - the expected reply code 169 * @param text - the text expected within the reply message; defaults to the reply code as a String 170 */ 171 protected assertSessionReply(int replyIndex, int expectedReplyCode, text = expectedReplyCode as String) { 172 LOG.info(session.toString()) 173 String actualMessage = session.getReplyMessage(replyIndex) 174 def actualReplyCode = session.getReplyCode(replyIndex) 175 assert actualReplyCode == expectedReplyCode 176 if (text instanceof List) { 177 text.each { assert actualMessage.contains(it), "[$actualMessage] does not contain [$it]" } 178 } 179 else { 180 assert actualMessage.contains(text), "[$actualMessage] does not contain [$text]" 181 } 182 } 183 184 /** 185 * Assert that the specified reply codes were sent through the session. 186 * @param replyCodes - the List of expected sent reply codes 187 */ 188 protected assertSessionReplies(List replyCodes) { 189 LOG.info(session.toString()) 190 replyCodes.eachWithIndex {replyCode, replyIndex -> 191 assertSessionReply(replyIndex, replyCode) 192 } 193 } 194 195 /** 196 * Assert that the specified data was sent through the session. 197 * @param expectedData - the expected data 198 */ 199 protected assertSessionData(String expectedData) { 200 def actual = session.sentData[0] 201 assert actual != null, "No data for index [0] sent for $session" 202 assert actual == expectedData 203 } 204 205 /** 206 * Assert that the specified data was sent through the session, terminated by an end-of-line. 207 * @param expectedData - the expected data 208 */ 209 protected assertSessionDataWithEndOfLine(String expectedData) { 210 assertSessionData(expectedData + endOfLine()) 211 } 212 213 /** 214 * Assert that the data sent through the session terminated with an end-of-line. 215 */ 216 protected assertSessionDataEndsWithEndOfLine() { 217 assert session.sentData[0].endsWith(endOfLine()) 218 } 219 220 /** 221 * Execute the handleCommand() method with the specified parameters and 222 * assert that the standard SEND DATA replies were sent through the session. 223 * @param parameters - the command parameters to use; defaults to [] 224 * @param finalReplyCode - the expected final reply code; defaults to ReplyCodes.TRANSFER_DATA_FINAL_OK 225 */ 226 protected handleCommandAndVerifySendDataReplies(parameters = [], int finalReplyCode = ReplyCodes.TRANSFER_DATA_FINAL_OK) { 227 handleCommand(parameters) 228 assertSessionReplies([ReplyCodes.TRANSFER_DATA_INITIAL_OK, finalReplyCode]) 229 } 230 231 /** 232 * Execute the handleCommand() method with the specified parameters and 233 * assert that the standard SEND DATA replies were sent through the session. 234 * @param parameters - the command parameters to use 235 * @param initialReplyMessageKey - the expected reply message key for the initial reply 236 * @param finalReplyMessageKey - the expected reply message key for the final reply 237 * @param finalReplyCode - the expected final reply code; defaults to ReplyCodes.TRANSFER_DATA_FINAL_OK 238 */ 239 protected handleCommandAndVerifySendDataReplies(parameters, String initialReplyMessageKey, String finalReplyMessageKey, int finalReplyCode = ReplyCodes.TRANSFER_DATA_FINAL_OK) { 240 handleCommand(parameters) 241 assertSessionReply(0, ReplyCodes.TRANSFER_DATA_INITIAL_OK, initialReplyMessageKey) 242 assertSessionReply(1, finalReplyCode, finalReplyMessageKey) 243 } 244 245 /** 246 * Override the named method for the specified object instance 247 * @param object - the object instance 248 * @param methodName - the name of the method to override 249 * @param newMethod - the Closure representing the new method for this single instance 250 */ 251 protected void overrideMethod(object, String methodName, Closure newMethod) { 252 LOG.info("Overriding method [$methodName] for class [${object.class}]") 253 def emc = new ExpandoMetaClass(object.class, false) 254 emc."$methodName" = newMethod 255 emc.initialize() 256 object.metaClass = emc 257 } 258 259 /** 260 * Override the named method (that takes a single String arg) of the fileSystem object to throw a (generic) FileSystemException 261 * @param methodName - the name of the fileSystem method to override 262 */ 263 protected void overrideMethodToThrowFileSystemException(String methodName) { 264 def newMethod = {String path -> throw new FileSystemException("Error thrown by method [$methodName]", ERROR_MESSAGE_KEY) } 265 overrideMethod(fileSystem, methodName, newMethod) 266 } 267 268 /** 269 * Set the current directory within the session 270 * @param path - the new path value for the current directory 271 */ 272 protected void setCurrentDirectory(String path) { 273 session.setAttribute(SessionKeys.CURRENT_DIRECTORY, path) 274 } 275 276 /** 277 * Convenience method to return the end-of-line character(s) for the current CommandHandler. 278 */ 279 protected endOfLine() { 280 commandHandler.endOfLine() 281 } 282 283 /** 284 * Create a new directory entry with the specified path in the file system 285 * @param path - the path of the new directory entry 286 * @return the newly created DirectoryEntry 287 */ 288 protected DirectoryEntry createDirectory(String path) { 289 DirectoryEntry entry = new DirectoryEntry(path) 290 fileSystem.add(entry) 291 return entry 292 } 293 294 /** 295 * Create a new file entry with the specified path in the file system 296 * @param path - the path of the new file entry 297 * @param contents - the contents for the file; defaults to null 298 * @return the newly created FileEntry 299 */ 300 protected FileEntry createFile(String path, contents = null) { 301 FileEntry entry = new FileEntry(path: path, contents: contents) 302 fileSystem.add(entry) 303 return entry 304 } 305 306 }