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.net.InetAddress;
23import java.net.SocketTimeoutException;
24import java.util.Collections;
25import java.util.HashMap;
26import java.util.Map;
27
28import org.apache.log4j.Logger;
29import org.mockftpserver.core.MockFtpServerException;
30import org.mockftpserver.core.command.Command;
31import org.mockftpserver.core.socket.StubServerSocket;
32import org.mockftpserver.core.socket.StubServerSocketFactory;
33import org.mockftpserver.core.socket.StubSocket;
34import org.mockftpserver.core.socket.StubSocketFactory;
35import org.mockftpserver.core.util.AssertFailedException;
36import org.mockftpserver.test.AbstractTest;
37
38/**
39 * Tests for the DefaultSession class
40 *
41 * @version $Revision$ - $Date$
42 *
43 * @author Chris Mair
44 */
45public final class DefaultSessionTest extends AbstractTest {
46
47    private static final Logger LOG = Logger.getLogger(DefaultSessionTest.class);
48    private static final String DATA = "sample data 123";
49    private static final int PORT = 197;
50    private static final String NAME1 = "name1";
51    private static final String NAME2 = "name2";
52    private static final Object VALUE = "value";
53
54    private DefaultSession session;
55    private ByteArrayOutputStream outputStream;
56    private Map commandHandlerMap;
57    private StubSocket stubSocket;
58    private InetAddress clientHost;
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
68        commandHandlerMap = new HashMap();
69        outputStream = new ByteArrayOutputStream();
70        session = createDefaultSession("");
71        clientHost = InetAddress.getLocalHost();
72    }
73
74    /**
75     * @see org.mockftpserver.test.AbstractTest#tearDown()
76     */
77    protected void tearDown() throws Exception {
78        super.tearDown();
79    }
80
81    /**
82     * Test the Constructor when the control socket is null
83     */
84    public void testConstructor_NullControlSocket() {
85        try {
86            new DefaultSession(null, commandHandlerMap);
87            fail("Expected AssertFailedException");
88        }
89        catch (AssertFailedException expected) {
90            LOG.info("Expected: " + expected);
91        }
92    }
93
94    /**
95     * Test the Constructor when the command handler Map is null
96     */
97    public void testConstructor_NullCommandHandlerMap() {
98        try {
99            new DefaultSession(stubSocket, null);
100            fail("Expected AssertFailedException");
101        }
102        catch (AssertFailedException expected) {
103            LOG.info("Expected: " + expected);
104        }
105    }
106
107    /**
108     * Test the setClientDataPort() method
109     */
110    public void testSetClientDataPort() {
111        StubSocket stubSocket = createTestSocket("");
112        StubSocketFactory stubSocketFactory = new StubSocketFactory(stubSocket);
113        session.socketFactory = stubSocketFactory;
114        session.setClientDataPort(PORT);
115        session.setClientDataHost(clientHost);
116        session.openDataConnection();
117        assertEquals("data port", PORT, stubSocketFactory.requestedDataPort);
118    }
119
120    /**
121     * Test the setClientDataPort() method after the session was in passive data mode
122     */
123    public void testSetClientDataPort_AfterPassiveConnectionMode() throws IOException {
124        StubServerSocket stubServerSocket = new StubServerSocket(PORT);
125        StubServerSocketFactory stubServerSocketFactory = new StubServerSocketFactory(stubServerSocket);
126        session.serverSocketFactory = stubServerSocketFactory;
127
128        session.switchToPassiveMode();
129        assertFalse("server socket closed", stubServerSocket.isClosed());
130        assertNotNull("passiveModeDataSocket", session.passiveModeDataSocket);
131        session.setClientDataPort(PORT);
132
133        // Make sure that any passive mode connection info is cleared out
134        assertTrue("server socket closed", stubServerSocket.isClosed());
135        assertNull("passiveModeDataSocket should be null", session.passiveModeDataSocket);
136    }
137
138    /**
139     * Test the setClientHost() method
140     */
141    public void testSetClientHost() throws Exception {
142        StubSocket stubSocket = createTestSocket("");
143        StubSocketFactory stubSocketFactory = new StubSocketFactory(stubSocket);
144        session.socketFactory = stubSocketFactory;
145        session.setClientDataHost(clientHost);
146        session.openDataConnection();
147        assertEquals("client host", clientHost, stubSocketFactory.requestedHost);
148    }
149
150    /**
151     * Test the openDataConnection(), setClientDataPort() and setClientDataHost() methods
152     */
153    public void testOpenDataConnection() {
154        StubSocket stubSocket = createTestSocket("");
155        StubSocketFactory stubSocketFactory = new StubSocketFactory(stubSocket);
156        session.socketFactory = stubSocketFactory;
157
158        // Use default client data port
159        session.setClientDataHost(clientHost);
160        session.openDataConnection();
161        assertEquals("data port", DefaultSession.DEFAULT_CLIENT_DATA_PORT, stubSocketFactory.requestedDataPort);
162        assertEquals("client host", clientHost, stubSocketFactory.requestedHost);
163
164        // Set client data port explicitly
165        session.setClientDataPort(PORT);
166        session.setClientDataHost(clientHost);
167        session.openDataConnection();
168        assertEquals("data port", PORT, stubSocketFactory.requestedDataPort);
169        assertEquals("client host", clientHost, stubSocketFactory.requestedHost);
170    }
171
172    /**
173     * Test the OpenDataConnection method, when in passive mode and no incoming connection is
174     * initiated
175     */
176    public void testOpenDataConnection_PassiveMode_NoConnection() throws IOException {
177
178        StubServerSocket stubServerSocket = new StubServerSocket(PORT);
179        StubServerSocketFactory stubServerSocketFactory = new StubServerSocketFactory(stubServerSocket);
180        session.serverSocketFactory = stubServerSocketFactory;
181
182        session.switchToPassiveMode();
183
184        try {
185            session.openDataConnection();
186            fail("Expected MockFtpServerException");
187        }
188        catch (MockFtpServerException expected) {
189            LOG.info("Expected: " + expected);
190            assertSame("cause", SocketTimeoutException.class, expected.getCause().getClass());
191        }
192    }
193
194    /**
195     * Test the OpenDataConnection method, when the clientHost has not been set
196     */
197    public void testOpenDataConnection_NullClientHost() {
198        try {
199            session.openDataConnection();
200            fail("Expected AssertFailedException");
201        }
202        catch (AssertFailedException expected) {
203            LOG.info("Expected: " + expected);
204        }
205    }
206
207    /**
208     * Test the readData() method
209     */
210    public void testReadData() {
211        StubSocket stubSocket = createTestSocket(DATA);
212        session.socketFactory = new StubSocketFactory(stubSocket);
213        session.setClientDataHost(clientHost);
214
215        session.openDataConnection();
216        byte[] data = session.readData();
217        LOG.info("data=[" + new String(data) + "]");
218        assertEquals("data", DATA.getBytes(), data);
219    }
220
221    /**
222     * Test the readData() method after switching to passive mode
223     */
224    public void testReadData_PassiveMode() throws IOException {
225        StubSocket stubSocket = createTestSocket(DATA);
226        StubServerSocket stubServerSocket = new StubServerSocket(PORT, stubSocket);
227        StubServerSocketFactory stubServerSocketFactory = new StubServerSocketFactory(stubServerSocket);
228        session.serverSocketFactory = stubServerSocketFactory;
229
230        session.switchToPassiveMode();
231        session.openDataConnection();
232        byte[] data = session.readData();
233        LOG.info("data=[" + new String(data) + "]");
234        assertEquals("data", DATA.getBytes(), data);
235    }
236
237    /**
238     * Test the closeDataConnection() method
239     */
240    public void testCloseDataConnection() {
241        StubSocket stubSocket = createTestSocket(DATA);
242        session.socketFactory = new StubSocketFactory(stubSocket);
243
244        session.setClientDataHost(clientHost);
245        session.openDataConnection();
246        session.closeDataConnection();
247        assertTrue("client data socket should be closed", stubSocket.isClosed());
248    }
249
250    /**
251     * Test the switchToPassiveMode() method
252     */
253    public void testSwitchToPassiveMode() throws IOException {
254        StubServerSocket stubServerSocket = new StubServerSocket(PORT);
255        StubServerSocketFactory stubServerSocketFactory = new StubServerSocketFactory(stubServerSocket);
256        session.serverSocketFactory = stubServerSocketFactory;
257
258        assertNull("passiveModeDataSocket starts out null", session.passiveModeDataSocket);
259        int port = session.switchToPassiveMode();
260        assertSame("passiveModeDataSocket", stubServerSocket, session.passiveModeDataSocket);
261        assertEquals("port", PORT, port);
262    }
263
264    /**
265     * Test the getServerHost() method
266     */
267    public void testGetServerHost() {
268        assertEquals("host", DEFAULT_HOST, session.getServerHost());
269    }
270
271    /**
272     * Test the getClientHost() method when the session is not yet started
273     */
274    public void testGetClientHost_NotRunning() {
275        assertNull("null", session.getClientHost());
276    }
277
278    /**
279     * Test the parseCommand() method
280     */
281    public void testParseCommand() {
282        Command command = session.parseCommand("LIST");
283        assertEquals("command name", "LIST", command.getName());
284        assertEquals("command parameters", EMPTY, command.getParameters());
285
286        command = session.parseCommand("USER user123");
287        assertEquals("command name", "USER", command.getName());
288        assertEquals("command parameters", array("user123"), command.getParameters());
289
290        command = session.parseCommand("PORT 127,0,0,1,17,37");
291        assertEquals("command name", "PORT", command.getName());
292        assertEquals("command parameters", new String[] { "127", "0", "0", "1", "17", "37" }, command
293                .getParameters());
294    }
295
296    /**
297     * Test the parseCommand() method, passing in an empty command String
298     */
299    public void testParseCommand_EmptyCommandString() {
300        try {
301            session.parseCommand("");
302            fail("Expected AssertFailedException");
303        }
304        catch (AssertFailedException expected) {
305            LOG.info("Expected: " + expected);
306        }
307    }
308
309    /**
310     * Test the sendData() method, as well as the openDataConnection() and closeDataConnection()
311     */
312    public void testSendData() {
313        StubSocket stubSocket = createTestSocket("1234567890 abcdef");
314        session.socketFactory = new StubSocketFactory(stubSocket);
315
316        session.setClientDataHost(clientHost);
317        session.openDataConnection();
318        session.sendData(DATA.getBytes(), DATA.length());
319        LOG.info("output=[" + outputStream.toString() + "]");
320        assertEquals("output", DATA, outputStream.toString());
321    }
322
323    /**
324     * Test the SendData() method, passing in a null byte[]
325     */
326    public void testSendData_Null() {
327
328        try {
329            session.sendData(null, 1);
330            fail("Expected AssertFailedException");
331        }
332        catch (AssertFailedException expected) {
333            LOG.info("Expected: " + expected);
334        }
335    }
336
337    /**
338     * Test the SendReply(int,String) method, passing in an invalid reply code
339     */
340    public void testSendReply_InvalidReplyCode() {
341
342        try {
343            session.sendReply(-66, "text");
344            fail("Expected AssertFailedException");
345        }
346        catch (AssertFailedException expected) {
347            LOG.info("Expected: " + expected);
348        }
349    }
350
351    /**
352     * Test the getAttribute() and setAttribute() methods
353     */
354    public void testGetAndSetAttribute() {
355        assertNull("name does not exist yet", session.getAttribute(NAME1));
356        session.setAttribute(NAME1, VALUE);
357        session.setAttribute(NAME2, null);
358        assertEquals("NAME1", VALUE, session.getAttribute(NAME1));
359        assertNull("NAME2", session.getAttribute(NAME2));
360        assertNull("no such name", session.getAttribute("noSuchName"));
361    }
362
363    /**
364     * Test the getAttribute() method, passing in a null name
365     */
366    public void testGetAttribute_Null() {
367        try {
368            session.getAttribute(null);
369            fail("Expected AssertFailedException");
370        }
371        catch (AssertFailedException expected) {
372            LOG.info("Expected: " + expected);
373        }
374    }
375
376    /**
377     * Test the setAttribute() method, passing in a null name
378     */
379    public void testSetAttribute_NullName() {
380        try {
381            session.setAttribute(null, VALUE);
382            fail("Expected AssertFailedException");
383        }
384        catch (AssertFailedException expected) {
385            LOG.info("Expected: " + expected);
386        }
387    }
388
389    /**
390     * Test the removeAttribute()
391     */
392    public void testRemoveAttribute() {
393        session.removeAttribute("noSuchName");      // do nothing
394        session.setAttribute(NAME1, VALUE);
395        session.removeAttribute(NAME1);
396        assertNull("NAME1", session.getAttribute(NAME1));
397    }
398
399    /**
400     * Test the removeAttribute() method, passing in a null name
401     */
402    public void testRemoveAttribute_Null() {
403        try {
404            session.removeAttribute(null);
405            fail("Expected AssertFailedException");
406        }
407        catch (AssertFailedException expected) {
408            LOG.info("Expected: " + expected);
409        }
410    }
411
412    /**
413     * Test the getAttributeNames()
414     */
415    public void testGetAttributeNames() {
416        assertEquals("No names yet", Collections.EMPTY_SET, session.getAttributeNames());
417        session.setAttribute(NAME1, VALUE);
418        assertEquals("1", Collections.singleton(NAME1), session.getAttributeNames());
419        session.setAttribute(NAME2, VALUE);
420        assertEquals("2", set(NAME1, NAME2), session.getAttributeNames());
421    }
422
423    // -------------------------------------------------------------------------
424    // Internal Helper Methods
425    // -------------------------------------------------------------------------
426
427    /**
428     * Create and return a DefaultSession object that reads from an InputStream with the specified
429     * contents and writes to the predefined outputStrean ByteArrayOutputStream. Also, save the
430     * StubSocket being used in the stubSocket attribute.
431     *
432     * @param inputStreamContents - the contents of the input stream
433     * @return the DefaultSession
434     */
435    private DefaultSession createDefaultSession(String inputStreamContents) {
436        stubSocket = createTestSocket(inputStreamContents);
437        return new DefaultSession(stubSocket, commandHandlerMap);
438    }
439
440    /**
441     * Create and return a StubSocket that reads from an InputStream with the specified contents and
442     * writes to the predefined outputStrean ByteArrayOutputStream.
443     *
444     * @param inputStreamContents - the contents of the input stream
445     * @return the StubSocket
446     */
447    private StubSocket createTestSocket(String inputStreamContents) {
448        InputStream inputStream = new ByteArrayInputStream(inputStreamContents.getBytes());
449        StubSocket stubSocket = new StubSocket(inputStream, outputStream);
450        stubSocket._setLocalAddress(DEFAULT_HOST);
451        return stubSocket;
452    }
453
454}
455