Flesh out FakeHttpServlet{Request,Response}

Add more functional methods for manipulating headers and the response
body, as well as making some trivial getters/setters non-useless.

Change-Id: I95d6934d502bc50aaf1bad03001925081874548e
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/GitilesView.java b/gitiles-servlet/src/main/java/com/google/gitiles/GitilesView.java
index f48ec3b..42079ee 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/GitilesView.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/GitilesView.java
@@ -19,6 +19,7 @@
 import static com.google.gitiles.GitilesUrls.NAME_ESCAPER;
 import static com.google.gitiles.RevisionParser.PATH_SPLITTER;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Charsets;
 import com.google.common.base.Objects;
 import com.google.common.base.Strings;
@@ -599,7 +600,8 @@
         || rev2.getName().equals(rev1.getName() + "~1");
   }
 
-  private static String paramsToString(ListMultimap<String, String> params) {
+  @VisibleForTesting
+  static String paramsToString(ListMultimap<String, String> params) {
     try {
     StringBuilder sb = new StringBuilder();
     boolean first = true;
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/FakeHttpServletRequest.java b/gitiles-servlet/src/test/java/com/google/gitiles/FakeHttpServletRequest.java
index c1afa74..04d9ef9 100644
--- a/gitiles-servlet/src/test/java/com/google/gitiles/FakeHttpServletRequest.java
+++ b/gitiles-servlet/src/test/java/com/google/gitiles/FakeHttpServletRequest.java
@@ -328,7 +328,11 @@
 
   @Override
   public String getRequestURI() {
-    return null;
+    String uri = contextPath + servletPath + path;
+    if (!parameters.isEmpty()) {
+      uri += "?" + GitilesView.paramsToString(parameters);
+    }
+    return uri;
   }
 
   @Override
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/FakeHttpServletResponse.java b/gitiles-servlet/src/test/java/com/google/gitiles/FakeHttpServletResponse.java
index 87a0099..5249e41 100644
--- a/gitiles-servlet/src/test/java/com/google/gitiles/FakeHttpServletResponse.java
+++ b/gitiles-servlet/src/test/java/com/google/gitiles/FakeHttpServletResponse.java
@@ -15,8 +15,20 @@
 package com.google.gitiles;
 
 import static com.google.common.base.Charsets.UTF_8;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
 
+import com.google.common.base.Charsets;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.net.HttpHeaders;
+
+import org.eclipse.jgit.util.RawParseUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.PrintWriter;
+import java.nio.charset.Charset;
 import java.util.Locale;
 
 import javax.servlet.ServletOutputStream;
@@ -25,16 +37,25 @@
 
 /** Simple fake implementation of {@link HttpServletResponse}. */
 public class FakeHttpServletResponse implements HttpServletResponse {
+  private final ByteArrayOutputStream actualBody = new ByteArrayOutputStream();
+  private final ListMultimap<String, String> headers = LinkedListMultimap.create();
 
-  private volatile int status;
+  private int status = 200;
+  private boolean committed;
+  private ServletOutputStream outputStream;
+  private PrintWriter writer;
 
   public FakeHttpServletResponse() {
-    status = 200;
   }
 
   @Override
-  public void flushBuffer() {
-    throw new UnsupportedOperationException();
+  public synchronized void flushBuffer() throws IOException {
+    if (outputStream != null) {
+      outputStream.flush();
+    }
+    if (writer != null) {
+      writer.flush();
+    }
   }
 
   @Override
@@ -58,18 +79,33 @@
   }
 
   @Override
-  public ServletOutputStream getOutputStream() {
-    throw new UnsupportedOperationException();
+  public synchronized ServletOutputStream getOutputStream() {
+    checkState(writer == null, "getWriter() already called");
+    if (outputStream == null) {
+      final PrintWriter osWriter = new PrintWriter(actualBody);
+      outputStream = new ServletOutputStream() {
+        @Override
+        public void write(int c) throws IOException {
+          osWriter.write(c);
+          osWriter.flush();
+        }
+      };
+    }
+    return outputStream;
   }
 
   @Override
-  public PrintWriter getWriter() {
-    throw new UnsupportedOperationException();
+  public synchronized PrintWriter getWriter() {
+    checkState(outputStream == null, "getOutputStream() already called");
+    if (writer == null) {
+      writer = new PrintWriter(actualBody);
+    }
+    return writer;
   }
 
   @Override
-  public boolean isCommitted() {
-    return false;
+  public synchronized boolean isCommitted() {
+    return committed;
   }
 
   @Override
@@ -89,17 +125,20 @@
 
   @Override
   public void setCharacterEncoding(String name) {
-    throw new UnsupportedOperationException();
+    checkArgument(Charsets.UTF_8.equals(Charset.forName(name)),
+        "unsupported charset: %s", name);
   }
 
   @Override
   public void setContentLength(int length) {
-    throw new UnsupportedOperationException();
+    headers.removeAll(HttpHeaders.CONTENT_LENGTH);
+    headers.put(HttpHeaders.CONTENT_LENGTH, Integer.toString(length));
   }
 
   @Override
   public void setContentType(String type) {
-    throw new UnsupportedOperationException();
+    headers.removeAll(HttpHeaders.CONTENT_TYPE);
+    headers.put(HttpHeaders.CONTENT_TYPE, type);
   }
 
   @Override
@@ -119,17 +158,17 @@
 
   @Override
   public void addHeader(String name, String value) {
-    throw new UnsupportedOperationException();
+    headers.put(name, value);
   }
 
   @Override
   public void addIntHeader(String name, int value) {
-    throw new UnsupportedOperationException();
+    headers.put(name, Integer.toString(value));
   }
 
   @Override
   public boolean containsHeader(String name) {
-    return false;
+    return !headers.get(name).isEmpty();
   }
 
   @Override
@@ -155,47 +194,62 @@
   }
 
   @Override
-  public void sendError(int sc) {
+  public synchronized void sendError(int sc) {
     status = sc;
+    committed = true;
   }
 
   @Override
-  public void sendError(int sc, String msg) {
+  public synchronized void sendError(int sc, String msg) {
     status = sc;
+    committed = true;
   }
 
   @Override
-  public void sendRedirect(String msg) {
+  public synchronized void sendRedirect(String msg) {
     status = SC_FOUND;
+    committed = true;
   }
 
   @Override
   public void setDateHeader(String name, long value) {
-    throw new UnsupportedOperationException();
+    setHeader(name, Long.toString(value));
   }
 
   @Override
   public void setHeader(String name, String value) {
-    throw new UnsupportedOperationException();
+    headers.removeAll(name);
+    addHeader(name, value);
   }
 
   @Override
   public void setIntHeader(String name, int value) {
-    throw new UnsupportedOperationException();
+    headers.removeAll(name);
+    addIntHeader(name, value);
   }
 
   @Override
-  public void setStatus(int sc) {
+  public synchronized void setStatus(int sc) {
     status = sc;
+    committed = true;
   }
 
   @Override
   @Deprecated
-  public void setStatus(int sc, String msg) {
+  public synchronized void setStatus(int sc, String msg) {
     status = sc;
+    committed = true;
   }
 
-  public int getStatus() {
+  public synchronized int getStatus() {
     return status;
   }
+
+  public byte[] getActualBody() {
+    return actualBody.toByteArray();
+  }
+
+  public String getActualBodyString() {
+    return RawParseUtils.decode(getActualBody());
+  }
 }