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.core.session;
17
18import org.apache.log4j.Logger;
19import org.mockftpserver.core.command.Command;
20import org.mockftpserver.core.command.CommandHandler;
21import org.mockftpserver.core.command.CommandNames;
22import org.mockftpserver.core.command.ConnectCommandHandler;
23import org.mockftpserver.core.command.InvocationRecord;
24import org.mockftpserver.core.socket.StubSocket;
25import org.mockftpserver.stub.command.AbstractStubCommandHandler;
26import org.mockftpserver.test.AbstractTest;
27
28import java.io.ByteArrayInputStream;
29import java.io.ByteArrayOutputStream;
30import java.io.InputStream;
31import java.util.HashMap;
32import java.util.ListResourceBundle;
33import java.util.Map;
34import java.util.ResourceBundle;
35
36/**
37 * Tests for the DefaultSession class that require the session (thread) to be running/active.
38 *
39 * @author Chris Mair
40 * @version $Revision$ - $Date$
41 */
42public final class DefaultSession_RunTest extends AbstractTest {
43
44    private static final Logger LOG = Logger.getLogger(DefaultSession_RunTest.class);
45    private static final Command COMMAND = new Command("USER", EMPTY);
46    private static final int REPLY_CODE = 100;
47    private static final String REPLY_TEXT = "sample text description";
48
49    private DefaultSession session;
50    private ByteArrayOutputStream outputStream;
51    private Map commandHandlerMap;
52    private StubSocket stubSocket;
53    private boolean commandHandled = false;
54    private String commandToRegister = COMMAND.getName();
55
56    protected void setUp() throws Exception {
57        super.setUp();
58        commandHandlerMap = new HashMap();
59        outputStream = new ByteArrayOutputStream();
60    }
61
62    public void testInvocationOfCommandHandler() throws Exception {
63        AbstractStubCommandHandler commandHandler = new AbstractStubCommandHandler() {
64            public void handleCommand(Command command, Session cmdSession, InvocationRecord invocationRecord) {
65                assertEquals("command", COMMAND, command);
66                assertSame("session", session, cmdSession);
67                assertEquals("InvocationRecord: command", COMMAND, invocationRecord.getCommand());
68                assertEquals("InvocationRecord: clientHost", DEFAULT_HOST, invocationRecord.getClientHost());
69                commandHandled = true;
70            }
71        };
72        runCommandAndVerifyOutput(commandHandler, "");
73    }
74
75    public void testClose() throws Exception {
76        CommandHandler commandHandler = new AbstractStubCommandHandler() {
77            public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
78                session.close();
79                commandHandled = true;
80            }
81        };
82        runCommandAndVerifyOutput(commandHandler, "");
83        assertFalse("socket should not be closed", stubSocket.isClosed());
84    }
85
86    public void testGetClientHost() throws Exception {
87        CommandHandler commandHandler = new AbstractStubCommandHandler() {
88            public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
89                commandHandled = true;
90            }
91        };
92        runCommandAndVerifyOutput(commandHandler, "");
93        LOG.info("clientHost=" + session.getClientHost());
94        assertEquals("clientHost", DEFAULT_HOST, session.getClientHost());
95    }
96
97    public void testSendReply_NullReplyText() throws Exception {
98        CommandHandler commandHandler = new AbstractStubCommandHandler() {
99            public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
100                session.sendReply(REPLY_CODE, null);
101                commandHandled = true;
102            }
103        };
104        runCommandAndVerifyOutput(commandHandler, Integer.toString(REPLY_CODE));
105    }
106
107    public void testSendReply_TrimReplyText() throws Exception {
108        CommandHandler commandHandler = new AbstractStubCommandHandler() {
109            public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
110                session.sendReply(REPLY_CODE, " " + REPLY_TEXT + " ");
111                commandHandled = true;
112            }
113        };
114        runCommandAndVerifyOutput(commandHandler, REPLY_CODE + " " + REPLY_TEXT);
115    }
116
117    public void testSendReply_MultiLineText() throws Exception {
118        final String MULTILINE_REPLY_TEXT = "abc\ndef\nghi\njkl";
119        final String FORMATTED_MULTILINE_REPLY_TEXT = "123-abc\ndef\nghi\n123 jkl";
120
121        CommandHandler commandHandler = new AbstractStubCommandHandler() {
122            public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
123                session.sendReply(123, MULTILINE_REPLY_TEXT);
124                commandHandled = true;
125            }
126        };
127        runCommandAndVerifyOutput(commandHandler, FORMATTED_MULTILINE_REPLY_TEXT);
128    }
129
130    public void testSendReply_ReplyText() throws Exception {
131        CommandHandler commandHandler = new AbstractStubCommandHandler() {
132            public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
133                session.sendReply(REPLY_CODE, REPLY_TEXT);
134                commandHandled = true;
135            }
136        };
137        runCommandAndVerifyOutput(commandHandler, REPLY_CODE + " " + REPLY_TEXT);
138    }
139
140    public void testUnrecognizedCommand() throws Exception {
141        // Register a handler for unsupported commands
142        CommandHandler commandHandler = new AbstractStubCommandHandler() {
143            public void handleCommand(Command command, Session session, InvocationRecord invocationRecord) {
144                session.sendReply(502, "Unsupported");
145                commandHandled = true;
146            }
147        };
148        // Register the UNSUPPORTED command handler instead of the command that will be sent. So when we
149        // send the regular command, it will trigger the handling for unsupported/unrecognized commands.
150        commandToRegister = CommandNames.UNSUPPORTED;
151        runCommandAndVerifyOutput(commandHandler, "502 Unsupported");
152    }
153
154    // -------------------------------------------------------------------------
155    // Internal Helper Methods
156    // -------------------------------------------------------------------------
157
158    /**
159     * Create and return a DefaultSession and define the specified CommandHandler. Also, save the
160     * StubSocket being used in the stubSocket attribute.
161     *
162     * @param commandHandler - define this CommandHandler within the commandHandlerMap
163     * @return the DefaultSession
164     */
165    private DefaultSession createDefaultSession(CommandHandler commandHandler) {
166        stubSocket = createTestSocket(COMMAND.getName());
167        commandHandlerMap.put(commandToRegister, commandHandler);
168
169        ConnectCommandHandler connectCommandHandler = new ConnectCommandHandler();
170
171        ResourceBundle replyTextBundle = new ListResourceBundle() {
172            protected Object[][] getContents() {
173                return new Object[][]{
174                        {"220", "Reply for 220"},
175                };
176            }
177        };
178        connectCommandHandler.setReplyTextBundle(replyTextBundle);
179        commandHandlerMap.put(CommandNames.CONNECT, connectCommandHandler);
180
181        return new DefaultSession(stubSocket, commandHandlerMap);
182    }
183
184    /**
185     * Create and return a StubSocket that reads from an InputStream with the specified contents and
186     * writes to the predefined outputStrean ByteArrayOutputStream.
187     *
188     * @param inputStreamContents - the contents of the input stream
189     * @return the StubSocket
190     */
191    private StubSocket createTestSocket(String inputStreamContents) {
192        InputStream inputStream = new ByteArrayInputStream(inputStreamContents.getBytes());
193        return new StubSocket(DEFAULT_HOST, inputStream, outputStream);
194    }
195
196    /**
197     * Run the command represented by the CommandHandler and verify that the session output from the
198     * control socket contains the expected output text.
199     *
200     * @param commandHandler - the CommandHandler to invoke
201     * @param expectedOutput - the text expected within the session output
202     * @throws InterruptedException - if the thread sleep is interrupted
203     */
204    private void runCommandAndVerifyOutput(CommandHandler commandHandler, String expectedOutput)
205            throws InterruptedException {
206        session = createDefaultSession(commandHandler);
207
208        Thread thread = new Thread(session);
209        thread.start();
210
211        for (int i = 0; !commandHandled && i < 10; i++) {
212            Thread.sleep(50L);
213        }
214
215        session.close();
216        thread.join();
217
218        assertEquals("commandHandled", true, commandHandled);
219
220        String output = outputStream.toString();
221        LOG.info("output=[" + output.trim() + "]");
222        assertTrue("line ends with \\r\\n",
223                output.charAt(output.length() - 2) == '\r' && output.charAt(output.length() - 1) == '\n');
224        assertTrue("output: expected [" + expectedOutput + "]", output.indexOf(expectedOutput) != -1);
225    }
226
227}
228