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.command 17 18import org.mockftpserver.core.command.Command 19import org.mockftpserver.core.command.CommandHandler 20import org.mockftpserver.core.command.ReplyCodes 21import org.mockftpserver.core.session.SessionKeys 22import org.mockftpserver.core.session.StubSession 23import org.mockftpserver.fake.StubServerConfiguration 24import org.mockftpserver.fake.UserAccount 25import org.mockftpserver.fake.filesystem.DirectoryEntry 26import org.mockftpserver.fake.filesystem.FileEntry 27import org.mockftpserver.fake.filesystem.FileSystemException 28import org.mockftpserver.fake.filesystem.TestUnixFakeFileSystem 29import org.mockftpserver.test.AbstractGroovyTest 30import org.mockftpserver.test.StubResourceBundle 31 32/** 33 * Abstract superclass for CommandHandler tests 34 * 35 * @version $Revision$ - $Date$ 36 * 37 * @author Chris Mair 38 */ 39abstract class AbstractFakeCommandHandlerTest extends AbstractGroovyTest { 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 * @deprecated 161 */ 162 protected assertSessionReply(int expectedReplyCode, text = expectedReplyCode as String) { 163 assertSessionReply(0, expectedReplyCode, text) 164 } 165 166 /** 167 * Assert that the specified reply code and message containing text was sent through the session. 168 * @param replyIndex - the index of the reply to compare 169 * @param expectedReplyCode - the expected reply code 170 * @param text - the text expected within the reply message; defaults to the reply code as a String 171 */ 172 protected assertSessionReply(int replyIndex, int expectedReplyCode, text = expectedReplyCode as String) { 173 LOG.info(session) 174 String actualMessage = session.getReplyMessage(replyIndex) 175 def actualReplyCode = session.getReplyCode(replyIndex) 176 assert actualReplyCode == expectedReplyCode 177 if (text instanceof List) { 178 text.each { assert actualMessage.contains(it), "[$actualMessage] does not contain [$it]" } 179 } 180 else { 181 assert actualMessage.contains(text), "[$actualMessage] does not contain [$text]" 182 } 183 } 184 185 /** 186 * Assert that the specified reply codes were sent through the session. 187 * @param replyCodes - the List of expected sent reply codes 188 */ 189 protected assertSessionReplies(List replyCodes) { 190 LOG.info(session) 191 replyCodes.eachWithIndex {replyCode, replyIndex -> 192 assertSessionReply(replyIndex, replyCode) 193 } 194 } 195 196 /** 197 * Assert that the specified data was sent through the session. 198 * @param expectedData - the expected data 199 */ 200 protected assertSessionData(String expectedData) { 201 def actual = session.sentData[0] 202 assert actual != null, "No data for index [0] sent for $session" 203 assert actual == expectedData 204 } 205 206 /** 207 * Execute the handleCommand() method with the specified parameters and 208 * assert that the standard SEND DATA replies were sent through the session. 209 * @param parameters - the command parameters to use; defaults to [] 210 * @param finalReplyCode - the expected final reply code; defaults to ReplyCodes.TRANSFER_DATA_FINAL_OK 211 */ 212 protected handleCommandAndVerifySendDataReplies(parameters = [], int finalReplyCode = ReplyCodes.TRANSFER_DATA_FINAL_OK) { 213 handleCommand(parameters) 214 assertSessionReplies([ReplyCodes.TRANSFER_DATA_INITIAL_OK, finalReplyCode]) 215 } 216 217 /** 218 * Execute the handleCommand() method with the specified parameters and 219 * assert that the standard SEND DATA replies were sent through the session. 220 * @param parameters - the command parameters to use 221 * @param initialReplyMessageKey - the expected reply message key for the initial reply 222 * @param finalReplyMessageKey - the expected reply message key for the final reply 223 * @param finalReplyCode - the expected final reply code; defaults to ReplyCodes.TRANSFER_DATA_FINAL_OK 224 */ 225 protected handleCommandAndVerifySendDataReplies(parameters, String initialReplyMessageKey, String finalReplyMessageKey, int finalReplyCode = ReplyCodes.TRANSFER_DATA_FINAL_OK) { 226 handleCommand(parameters) 227 assertSessionReply(0, ReplyCodes.TRANSFER_DATA_INITIAL_OK, initialReplyMessageKey) 228 assertSessionReply(1, finalReplyCode, finalReplyMessageKey) 229 } 230 231 /** 232 * Override the named method for the specified object instance 233 * @param object - the object instance 234 * @param methodName - the name of the method to override 235 * @param newMethod - the Closure representing the new method for this single instance 236 */ 237 protected void overrideMethod(object, String methodName, Closure newMethod) { 238 LOG.info("Overriding method [$methodName] for class [${object.class}]") 239 def emc = new ExpandoMetaClass(object.class, false) 240 emc."$methodName" = newMethod 241 emc.initialize() 242 object.metaClass = emc 243 } 244 245 /** 246 * Override the named method (that takes a single String arg) of the fileSystem object to throw a (generic) FileSystemException 247 * @param methodName - the name of the fileSystem method to override 248 */ 249 protected void overrideMethodToThrowFileSystemException(String methodName) { 250 def newMethod = {String path -> throw new FileSystemException("Error thrown by method [$methodName]", ERROR_MESSAGE_KEY) } 251 overrideMethod(fileSystem, methodName, newMethod) 252 } 253 254 /** 255 * Set the current directory within the session 256 * @param path - the new path value for the current directory 257 */ 258 protected void setCurrentDirectory(String path) { 259 session.setAttribute(SessionKeys.CURRENT_DIRECTORY, path) 260 } 261 262 /** 263 * Convenience method to return the end-of-line character(s) for the current CommandHandler. 264 */ 265 protected endOfLine() { 266 commandHandler.endOfLine() 267 } 268 269 /** 270 * Create a new directory entry with the specified path in the file system 271 * @param path - the path of the new directory entry 272 * @return the newly created DirectoryEntry 273 */ 274 protected DirectoryEntry createDirectory(String path) { 275 DirectoryEntry entry = new DirectoryEntry(path) 276 fileSystem.add(entry) 277 return entry 278 } 279 280 /** 281 * Create a new file entry with the specified path in the file system 282 * @param path - the path of the new file entry 283 * @param contents - the contents for the file; defaults to null 284 * @return the newly created FileEntry 285 */ 286 protected FileEntry createFile(String path, contents = null) { 287 FileEntry entry = new FileEntry(path: path, contents: contents) 288 fileSystem.add(entry) 289 return entry 290 } 291 292}