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