// Copyright (C) 2003-2009 by Object Mentor, Inc. All rights reserved. // Released under the terms of the CPL Common Public License version 1.0. package fitnesse; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.*; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.util.concurrent.Executors; import fitnesse.authentication.Authenticator; import fitnesse.authentication.UnauthorizedResponder; import fitnesse.http.MockRequest; import fitnesse.http.Request; import fitnesse.http.Response; import fitnesse.http.ResponseParser; import fitnesse.testutil.FitNesseUtil; import fitnesse.util.MockSocket; import fitnesse.wiki.WikiPage; import org.junit.Before; import org.junit.Test; public class FitNesseExpediterTest { public static final int REQUEST_PARSING_TIME_LIMIT = 200; private FitNesseExpediter expediter; private MockSocket socket; private FitNesseContext context; private PipedInputStream clientInput; private PipedOutputStream clientOutput; private ResponseParser response; private java.util.concurrent.ExecutorService executorService; @Before public void setUp() throws Exception { context = FitNesseUtil.makeTestContext(); executorService = Executors.newFixedThreadPool(2); WikiPage root = context.getRootPage(); root.addChildPage("FrontPage"); socket = new MockSocket(); expediter = new FitNesseExpediter(socket, context, executorService); } @Test public void testAuthenticationGetsCalled() throws Exception { context = FitNesseUtil.makeTestContext(new StoneWallAuthenticator()); WikiPage root = context.getRootPage(); root.addChildPage("FrontPage"); expediter = new FitNesseExpediter(socket, context, executorService); MockRequest request = new MockRequest(); Response response = expediter.createGoodResponse(request); assertEquals(401, response.getStatus()); } @Test(expected = IOException.class) public void testClosedSocketMidResponse() throws Exception { MockRequest request = new MockRequest(); Response response = expediter.createGoodResponse(request); socket.close(); response.sendTo(expediter); } @Test public void testIncompleteRequestsTimeOut() throws Exception { final FitNesseExpediter sender = preparePipedFitNesseExpediter(); Thread senderThread = makeSendingThread(sender); senderThread.start(); Thread parseResponseThread = makeParsingThread(); parseResponseThread.start(); Thread.sleep(REQUEST_PARSING_TIME_LIMIT + 100); parseResponseThread.join(); assertEquals(408, response.getStatus()); } private FitNesseExpediter preparePipedFitNesseExpediter() throws Exception { PipedInputStream socketInput = new PipedInputStream(); clientOutput = new PipedOutputStream(socketInput); clientInput = new PipedInputStream(); PipedOutputStream socketOutput = new PipedOutputStream(clientInput); MockSocket socket = new MockSocket(socketInput, socketOutput); return new FitNesseExpediter(socket, context, executorService, REQUEST_PARSING_TIME_LIMIT); } @Test public void testCompleteRequest() throws Exception { final FitNesseExpediter sender = preparePipedFitNesseExpediter(); Thread senderThread = makeSendingThread(sender); senderThread.start(); Thread parseResponseThread = makeParsingThread(); parseResponseThread.start(); clientOutput.write("GET /root HTTP/1.1\r\n\r\n".getBytes()); clientOutput.flush(); parseResponseThread.join(); assertEquals(200, response.getStatus()); } @Test public void slowButCompleteRequestCanTimeOut() throws Exception { final FitNesseExpediter sender = preparePipedFitNesseExpediter(); Thread senderThread = makeSendingThread(sender); senderThread.start(); Thread parseResponseThread = makeParsingThread(); parseResponseThread.start(); boolean pipeHasBeenClosed = false; // 22 bytes * 20ms sleep => 440 ms for the whole request. Time limit is set to 200ms. byte[] bytes = "GET /root HTTP/1.1\r\n\r\n".getBytes(); try { for (byte aByte : bytes) { clientOutput.write(aByte); clientOutput.flush(); Thread.sleep(20); } } catch (IOException pipedClosed) { pipeHasBeenClosed = true; } parseResponseThread.join(); assertTrue(pipeHasBeenClosed); assertEquals(408, response.getStatus()); assertThat(response.getBody(), containsString("The client request has been unproductive for too long. It has timed out and will no longer be processed.")); } private Thread makeSendingThread(final FitNesseExpediter sender) { return new Thread(new Runnable() { @Override public void run() { try { sender.run(); } catch (Exception e) { e.printStackTrace(); } } }); } private Thread makeParsingThread() { return new Thread(new Runnable() { @Override public void run() { try { response = new ResponseParser(clientInput); } catch (Exception e) { e.printStackTrace(); } } }); } class StoneWallAuthenticator extends Authenticator { @Override public Responder authenticate(FitNesseContext context, Request request, Responder privilegedResponder) { return new UnauthorizedResponder(); } @Override public boolean isAuthenticated(String username, String password) { return false; } } }