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}