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