Merge "ServletTest: base class for servlet tests"
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/DiffServletTest.java b/gitiles-servlet/src/test/java/com/google/gitiles/DiffServletTest.java
index e9da3a7..8e1ba51 100644
--- a/gitiles-servlet/src/test/java/com/google/gitiles/DiffServletTest.java
+++ b/gitiles-servlet/src/test/java/com/google/gitiles/DiffServletTest.java
@@ -28,30 +28,15 @@
 import org.eclipse.jgit.diff.Edit;
 import org.eclipse.jgit.diff.Edit.Type;
 import org.eclipse.jgit.diff.RawText;
-import org.eclipse.jgit.internal.storage.dfs.DfsRepository;
-import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
-import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
-import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.patch.FileHeader;
 import org.eclipse.jgit.patch.Patch;
 import org.eclipse.jgit.revwalk.RevCommit;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
 @RunWith(JUnit4.class)
-public class DiffServletTest {
-  private TestRepository<DfsRepository> repo;
-  private GitilesServlet servlet;
-
-  @Before
-  public void setUp() throws Exception {
-    DfsRepository r = new InMemoryRepository(new DfsRepositoryDescription("test"));
-    repo = new TestRepository<>(r);
-    servlet = TestGitilesServlet.create(repo);
-  }
-
+public class DiffServletTest extends ServletTest {
   @Test
   public void diffFileOneParentHtml() throws Exception {
     String contents1 = "foo\n";
@@ -59,15 +44,11 @@
     RevCommit c1 = repo.update("master", repo.commit().add("foo", contents1));
     RevCommit c2 = repo.update("master", repo.commit().parent(c1).add("foo", contents2));
 
-    FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
-    req.setPathInfo("/test/+diff/" + c2.name() + "^!/foo");
-    FakeHttpServletResponse res = new FakeHttpServletResponse();
-    servlet.service(req, res);
+    String actual = buildHtml("/repo/+diff/" + c2.name() + "^!/foo", false);
 
     String diffHeader = String.format(
-        "diff --git <a href=\"/b/test/+/%s/foo\">a/foo</a> <a href=\"/b/test/+/%s/foo\">b/foo</a>",
+        "diff --git <a href=\"/b/repo/+/%s/foo\">a/foo</a> <a href=\"/b/repo/+/%s/foo\">b/foo</a>",
         c1.name(), c2.name());
-    String actual = res.getActualBodyString();
     assertTrue(String.format("Expected diff body to contain [%s]:\n%s", diffHeader, actual),
         actual.contains(diffHeader));
   }
@@ -77,11 +58,7 @@
     String contents = "foo\ncontents\n";
     RevCommit c = repo.update("master", repo.commit().add("foo", contents));
 
-    FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
-    req.setPathInfo("/test/+diff/" + c.name() + "^!/foo");
-    req.setQueryString("format=TEXT");
-    FakeHttpServletResponse res = new FakeHttpServletResponse();
-    servlet.service(req, res);
+    FakeHttpServletResponse res = buildText("/repo/+diff/" + c.name() + "^!/foo");
 
     Patch p = parsePatch(res.getActualBody());
     FileHeader f = getOnlyElement(p.getFiles());
@@ -102,11 +79,7 @@
     RevCommit c1 = repo.update("master", repo.commit().add("foo", contents1));
     RevCommit c2 = repo.update("master", repo.commit().parent(c1).add("foo", contents2));
 
-    FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
-    req.setPathInfo("/test/+diff/" + c2.name() + "^!/foo");
-    req.setQueryString("format=TEXT");
-    FakeHttpServletResponse res = new FakeHttpServletResponse();
-    servlet.service(req, res);
+    FakeHttpServletResponse res = buildText("/repo/+diff/" + c2.name() + "^!/foo");
 
     Patch p = parsePatch(res.getActualBody());
     FileHeader f = getOnlyElement(p.getFiles());
@@ -128,11 +101,7 @@
         .add("dir/bar", contents)
         .add("baz", contents));
 
-    FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
-    req.setPathInfo("/test/+diff/" + c.name() + "^!/dir");
-    req.setQueryString("format=TEXT");
-    FakeHttpServletResponse res = new FakeHttpServletResponse();
-    servlet.service(req, res);
+    FakeHttpServletResponse res = buildText("/repo/+diff/" + c.name() + "^!/dir");
 
     Patch p = parsePatch(res.getActualBody());
     assertEquals(2, p.getFiles().size());
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/PathServletTest.java b/gitiles-servlet/src/test/java/com/google/gitiles/PathServletTest.java
index 7797157..c1ee384 100644
--- a/gitiles-servlet/src/test/java/com/google/gitiles/PathServletTest.java
+++ b/gitiles-servlet/src/test/java/com/google/gitiles/PathServletTest.java
@@ -17,52 +17,29 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 
-import com.google.common.collect.ImmutableList;
 import com.google.common.io.BaseEncoding;
-import com.google.common.net.HttpHeaders;
 import com.google.gitiles.TreeJsonData.Tree;
-import com.google.gson.Gson;
 import com.google.template.soy.data.SoyListData;
 import com.google.template.soy.data.restricted.StringData;
 
 import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
 import org.eclipse.jgit.dircache.DirCacheEntry;
-import org.eclipse.jgit.internal.storage.dfs.DfsRepository;
-import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
-import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
-import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.revwalk.RevBlob;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevTree;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.net.URL;
 import java.util.List;
 import java.util.Map;
 
 /** Tests for {@PathServlet}. */
 @SuppressWarnings("unchecked")
 @RunWith(JUnit4.class)
-public class PathServletTest {
-  private static final Renderer RENDERER =
-      new DefaultRenderer("/+static", ImmutableList.<URL> of(), "Test");
-
-  private TestRepository<DfsRepository> repo;
-  private PathServlet servlet;
-
-  @Before
-  public void setUp() throws Exception {
-    DfsRepository r = new InMemoryRepository(new DfsRepositoryDescription("repo"));
-    repo = new TestRepository<>(r);
-    servlet = new PathServlet(
-        new TestGitilesAccess(repo.getRepository()), RENDERER, TestGitilesUrls.URLS);
-  }
-
+public class PathServletTest extends ServletTest {
   @Test
   public void rootTreeHtml() throws Exception {
     repo.branch("master").commit().add("foo", "contents").create();
@@ -166,8 +143,8 @@
   @Test
   public void blobText() throws Exception {
     repo.branch("master").commit().add("foo", "contents").create();
-    String text = buildText("/repo/+/master/foo?format=TEXT", "100644");
-    assertEquals("contents", decodeBase64(text));
+    String text = buildBlob("/repo/+/master/foo", "100644");
+    assertEquals("contents", text);
   }
 
   @Test
@@ -181,8 +158,8 @@
             ent.setObjectId(link);
           }
         }).create();
-    String text = buildText("/repo/+/master/baz?format=TEXT", "120000");
-    assertEquals("foo", decodeBase64(text));
+    String text = buildBlob("/repo/+/master/baz", "120000");
+    assertEquals("foo", text);
   }
 
   @Test
@@ -192,11 +169,11 @@
     repo.branch("master").commit().setTopLevelTree(tree).create();
 
     String expected = "040000 tree " + repo.get(tree, "foo").name() + "\tfoo\n";
-    assertEquals(expected, decodeBase64(buildText("/repo/+/master/?format=TEXT", "040000")));
+    assertEquals(expected, buildBlob("/repo/+/master/", "040000"));
 
     expected = "100644 blob " + blob.name() + "\tbar\n";
-    assertEquals(expected, decodeBase64(buildText("/repo/+/master/foo?format=TEXT", "040000")));
-    assertEquals(expected, decodeBase64(buildText("/repo/+/master/foo/?format=TEXT", "040000")));
+    assertEquals(expected, buildBlob("/repo/+/master/foo", "040000"));
+    assertEquals(expected, buildBlob("/repo/+/master/foo/", "040000"));
   }
 
   @Test
@@ -205,7 +182,7 @@
     repo.branch("master").commit().add("foo\nbar\rbaz", blob).create();
 
     assertEquals("100644 blob " + blob.name() + "\t\"foo\\nbar\\rbaz\"\n",
-        decodeBase64(buildText("/repo/+/master/?format=TEXT", "040000")));
+        buildBlob("/repo/+/master/", "040000"));
   }
 
   @Test
@@ -225,8 +202,8 @@
           }
         }).create();
 
-    assertNotFound("/repo/+/master/nonexistent?format=TEXT");
-    assertNotFound("/repo/+/master/gitiles?format=TEXT");
+    assertNotFound("/repo/+/master/nonexistent", "format=text");
+    assertNotFound("/repo/+/master/gitiles", "format=text");
   }
 
   @Test
@@ -236,7 +213,7 @@
         .add("baz", "baz contents")
         .create());
 
-    Tree tree = buildJson("/repo/+/master/?format=JSON", Tree.class);
+    Tree tree = buildJson("/repo/+/master/", Tree.class);
     assertEquals(c.getTree().name(), tree.id);
     assertEquals(2, tree.entries.size());
     assertEquals(0100644, tree.entries.get(0).mode);
@@ -248,7 +225,7 @@
     assertEquals(repo.get(c.getTree(), "foo").name(), tree.entries.get(1).id);
     assertEquals("foo", tree.entries.get(1).name);
 
-    tree = buildJson("/repo/+/master/foo?format=JSON", Tree.class);
+    tree = buildJson("/repo/+/master/foo", Tree.class);
     assertEquals(repo.get(c.getTree(), "foo").name(), tree.id);
     assertEquals(1, tree.entries.size());
     assertEquals(0100644, tree.entries.get(0).mode);
@@ -256,7 +233,7 @@
     assertEquals(repo.get(c.getTree(), "foo/bar").name(), tree.entries.get(0).id);
     assertEquals("bar", tree.entries.get(0).name);
 
-    tree = buildJson("/repo/+/master/foo/?format=JSON", Tree.class);
+    tree = buildJson("/repo/+/master/foo/", Tree.class);
     assertEquals(repo.get(c.getTree(), "foo").name(), tree.id);
     assertEquals(1, tree.entries.size());
     assertEquals(0100644, tree.entries.get(0).mode);
@@ -273,41 +250,10 @@
     return ((Map<String, List<Map<String, ?>>>) data.get("data")).get("entries");
   }
 
-  private TestViewFilter.Result service(String pathAndQuery) throws Exception {
-    TestViewFilter.Result res = TestViewFilter.service(repo, pathAndQuery);
-    assertEquals(200, res.getResponse().getStatus());
-    assertEquals(GitilesView.Type.PATH, res.getView().getType());
-    servlet.service(res.getRequest(), res.getResponse());
-    return res;
-  }
-
-  private void assertNotFound(String pathAndQuery) throws Exception {
-    assertEquals(404, service(pathAndQuery).getResponse().getStatus());
-  }
-
-  private String buildText(String pathAndQuery, String expectedMode) throws Exception {
-    TestViewFilter.Result res = service(pathAndQuery);
-    assertEquals("text/plain", res.getResponse().getHeader(HttpHeaders.CONTENT_TYPE));
-    assertEquals(expectedMode, res.getResponse().getHeader(PathServlet.MODE_HEADER));
-    return res.getResponse().getActualBodyString();
-  }
-
-  private <T> T buildJson(String pathAndQuery, Class<T> clazz) throws Exception {
-    TestViewFilter.Result res = service(pathAndQuery);
-    assertEquals("application/json", res.getResponse().getHeader(HttpHeaders.CONTENT_TYPE));
-    String body = res.getResponse().getActualBodyString();
-    String magic = ")]}'\n";
-    assertEquals(magic, body.substring(0, magic.length()));
-    return new Gson().fromJson(body.substring(magic.length()), clazz);
-  }
-
-  private Map<String, ?> buildData(String pathAndQuery) throws Exception {
-    // Render the page through Soy to ensure templates are valid, then return
-    // the Soy data for introspection.
-    return BaseServlet.getData(service(pathAndQuery).getRequest());
-  }
-
-  private static String decodeBase64(String in) {
-    return new String(BaseEncoding.base64().decode(in), UTF_8);
+  private String buildBlob(String path, String expectedMode) throws Exception {
+    FakeHttpServletResponse res = buildText(path);
+    assertEquals(expectedMode, res.getHeader(PathServlet.MODE_HEADER));
+    String base64 = res.getActualBodyString();
+    return new String(BaseEncoding.base64().decode(base64), UTF_8);
   }
 }
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/RefServletTest.java b/gitiles-servlet/src/test/java/com/google/gitiles/RefServletTest.java
index 467a65e..04f82fd 100644
--- a/gitiles-servlet/src/test/java/com/google/gitiles/RefServletTest.java
+++ b/gitiles-servlet/src/test/java/com/google/gitiles/RefServletTest.java
@@ -21,21 +21,14 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.net.HttpHeaders;
 import com.google.gitiles.RefServlet.RefJsonData;
-import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 
-import org.eclipse.jgit.internal.storage.dfs.DfsRepository;
-import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
-import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
-import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevTag;
 import org.eclipse.jgit.revwalk.RevWalk;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -48,17 +41,7 @@
 
 /** Tests for {@link Linkifier}. */
 @RunWith(JUnit4.class)
-public class RefServletTest {
-  private TestRepository<DfsRepository> repo;
-  private GitilesServlet servlet;
-
-  @Before
-  public void setUp() throws Exception {
-    DfsRepository r = new InMemoryRepository(new DfsRepositoryDescription("test"));
-    repo = new TestRepository<>(r);
-    servlet = TestGitilesServlet.create(repo);
-  }
-
+public class RefServletTest extends ServletTest {
   private void setUpSimpleRefs() throws Exception {
     RevCommit commit = repo.branch("refs/heads/master").commit().create();
     repo.update("refs/heads/branch", commit);
@@ -75,12 +58,7 @@
     assertTrue(Repository.isValidRefName(evilRefName));
     repo.branch(evilRefName).commit().create();
 
-    FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
-    req.setPathInfo("/test/+refs/evil");
-    req.setQueryString("format=TEXT");
-    FakeHttpServletResponse res = new FakeHttpServletResponse();
-    servlet.service(req, res);
-
+    FakeHttpServletResponse res = buildText("/repo/+refs/evil");
     assertEquals(
         id(evilRefName) + " refs/evil/&lt;script&gt;window.close();&lt;/script&gt;/&amp;foo\n",
         res.getActualBodyString());
@@ -89,13 +67,8 @@
   @Test
   public void getRefsTextAll() throws Exception {
     setUpSimpleRefs();
-    FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
-    req.setPathInfo("/test/+refs");
-    req.setQueryString("format=TEXT");
-    FakeHttpServletResponse res = new FakeHttpServletResponse();
-    servlet.service(req, res);
+    FakeHttpServletResponse res = buildText("/repo/+refs");
 
-    assertEquals(200, res.getStatus());
     assertEquals(
         id("HEAD") + " HEAD\n"
         + id("refs/heads/branch") + " refs/heads/branch\n"
@@ -109,13 +82,8 @@
   @Test
   public void getRefsTextAllTrailingSlash() throws Exception {
     setUpSimpleRefs();
-    FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
-    req.setPathInfo("/test/+refs");
-    req.setQueryString("format=TEXT");
-    FakeHttpServletResponse res = new FakeHttpServletResponse();
-    servlet.service(req, res);
+    FakeHttpServletResponse res = buildText("/repo/+refs/");
 
-    assertEquals(200, res.getStatus());
     assertEquals(
         id("HEAD") + " HEAD\n"
         + id("refs/heads/branch") + " refs/heads/branch\n"
@@ -129,13 +97,8 @@
   @Test
   public void getRefsHeadsText() throws Exception {
     setUpSimpleRefs();
-    FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
-    req.setPathInfo("/test/+refs/heads");
-    req.setQueryString("format=TEXT");
-    FakeHttpServletResponse res = new FakeHttpServletResponse();
-    servlet.service(req, res);
+    FakeHttpServletResponse res = buildText("/repo/+refs/heads");
 
-    assertEquals(200, res.getStatus());
     assertEquals(
         id("refs/heads/branch") + " refs/heads/branch\n"
         + id("refs/heads/master") + " refs/heads/master\n",
@@ -145,13 +108,8 @@
   @Test
   public void getRefsHeadsTextTrailingSlash() throws Exception {
     setUpSimpleRefs();
-    FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
-    req.setPathInfo("/test/+refs/heads/");
-    req.setQueryString("format=TEXT");
-    FakeHttpServletResponse res = new FakeHttpServletResponse();
-    servlet.service(req, res);
+    FakeHttpServletResponse res = buildText("/repo/+refs/heads/");
 
-    assertEquals(200, res.getStatus());
     assertEquals(
         id("refs/heads/branch") + " refs/heads/branch\n"
         + id("refs/heads/master") + " refs/heads/master\n",
@@ -161,13 +119,8 @@
   @Test
   public void noHeadText() throws Exception {
     setUpSimpleRefs();
-    FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
-    req.setPathInfo("/test/+refs/HEAD");
-    req.setQueryString("format=TEXT");
-    FakeHttpServletResponse res = new FakeHttpServletResponse();
-    servlet.service(req, res);
+    FakeHttpServletResponse res = buildText("/repo/+refs/HEAD");
 
-    assertEquals(200, res.getStatus());
     // /+refs/foo means refs/foo(/*), so this is empty.
     assertEquals("", res.getActualBodyString());
   }
@@ -175,13 +128,8 @@
   @Test
   public void singleHeadText() throws Exception {
     setUpSimpleRefs();
-    FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
-    req.setPathInfo("/test/+refs/heads/master");
-    req.setQueryString("format=TEXT");
-    FakeHttpServletResponse res = new FakeHttpServletResponse();
-    servlet.service(req, res);
+    FakeHttpServletResponse res = buildText("/repo/+refs/heads/master");
 
-    assertEquals(200, res.getStatus());
     assertEquals(
         id("refs/heads/master") + " refs/heads/master\n",
         res.getActualBodyString());
@@ -190,13 +138,8 @@
   @Test
   public void singlePeeledTagText() throws Exception {
     setUpSimpleRefs();
-    FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
-    req.setPathInfo("/test/+refs/tags/atag");
-    req.setQueryString("format=TEXT");
-    FakeHttpServletResponse res = new FakeHttpServletResponse();
-    servlet.service(req, res);
+    FakeHttpServletResponse res = buildText("/repo/+refs/tags/atag");
 
-    assertEquals(200, res.getStatus());
     assertEquals(
         id("refs/tags/atag") + " refs/tags/atag\n"
         + peeled("refs/tags/atag") + " refs/tags/atag^{}\n",
@@ -206,7 +149,7 @@
   @Test
   public void getRefsJsonAll() throws Exception {
     setUpSimpleRefs();
-    Map<String, RefJsonData> result = buildJson("/test/+refs");
+    Map<String, RefJsonData> result = buildRefJson("/repo/+refs");
     List<String> keys = ImmutableList.copyOf(result.keySet());
     assertEquals(ImmutableList.of(
           "HEAD",
@@ -245,7 +188,7 @@
   @Test
   public void getRefsHeadsJson() throws Exception {
     setUpSimpleRefs();
-    Map<String, RefJsonData> result = buildJson("/test/+refs/heads");
+    Map<String, RefJsonData> result = buildRefJson("/repo/+refs/heads");
     List<String> keys = ImmutableList.copyOf(result.keySet());
     assertEquals(ImmutableList.of(
           "branch",
@@ -263,19 +206,10 @@
     assertNull(master.target);
   }
 
-  private Map<String, RefJsonData> buildJson(String path) throws Exception {
-    FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
-    req.setPathInfo(path);
-    req.setQueryString("format=JSON");
-    FakeHttpServletResponse res = new FakeHttpServletResponse();
-    servlet.service(req, res);
-
-    assertEquals(200, res.getStatus());
-    assertEquals("application/json", res.getHeader(HttpHeaders.CONTENT_TYPE));
-    String body = res.getActualBodyString();
-    String magic = ")]}'\n";
-    assertEquals(magic, body.substring(0, magic.length()));
-    return new Gson().fromJson(body.substring(magic.length()), new TypeToken<Map<String, RefJsonData>>() {}.getType());
+  private Map<String, RefJsonData> buildRefJson(String path) throws Exception {
+    return buildJson(
+        path,
+        new TypeToken<Map<String, RefJsonData>>() {}.getType());
   }
 
   @Test
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/ServletTest.java b/gitiles-servlet/src/test/java/com/google/gitiles/ServletTest.java
new file mode 100644
index 0000000..993f1d2
--- /dev/null
+++ b/gitiles-servlet/src/test/java/com/google/gitiles/ServletTest.java
@@ -0,0 +1,113 @@
+// Copyright (C) 2015 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gitiles;
+
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.google.common.net.HttpHeaders;
+import com.google.gson.Gson;
+
+import org.eclipse.jgit.internal.storage.dfs.DfsRepository;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.junit.Before;
+
+import java.lang.reflect.Type;
+import java.util.Map;
+
+/** Base class for servlet tests. */
+public class ServletTest {
+  protected TestRepository<DfsRepository> repo;
+  protected GitilesServlet servlet;
+
+  @Before
+  public void setUp() throws Exception {
+    repo = new TestRepository<DfsRepository>(
+        new InMemoryRepository(new DfsRepositoryDescription("repo")));
+    servlet = TestGitilesServlet.create(repo);
+  }
+
+  protected FakeHttpServletResponse buildResponse(
+      String path, String queryString, int expectedStatus)
+      throws Exception {
+    FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
+    req.setPathInfo(path);
+    if (queryString != null) {
+      req.setQueryString(queryString);
+    }
+    FakeHttpServletResponse res = new FakeHttpServletResponse();
+    servlet.service(req, res);
+    assertEquals(expectedStatus, res.getStatus());
+    return res;
+  }
+
+  protected FakeHttpServletResponse build(String path) throws Exception {
+    return buildResponse(path, null, SC_OK);
+  }
+
+  protected String buildHtml(String path, boolean assertHasETag) throws Exception {
+    FakeHttpServletResponse res = build(path);
+    assertEquals("text/html", res.getHeader(HttpHeaders.CONTENT_TYPE));
+    if (assertHasETag) {
+      assertNotNull("has ETag", res.getHeader(HttpHeaders.ETAG));
+    }
+    return res.getActualBodyString();
+  }
+
+  protected String buildHtml(String path) throws Exception {
+    return buildHtml(path, true);
+  }
+
+  protected Map<String, ?> buildData(String path) throws Exception {
+    // Render the page through Soy to ensure templates are valid, then return
+    // the Soy data for introspection.
+    FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
+    req.setPathInfo(path);
+    FakeHttpServletResponse res = new FakeHttpServletResponse();
+    servlet.service(req, res);
+    return BaseServlet.getData(req);
+  }
+
+  protected FakeHttpServletResponse buildText(String path) throws Exception {
+    FakeHttpServletResponse res = buildResponse(path, "format=text", SC_OK);
+    assertEquals("text/plain", res.getHeader(HttpHeaders.CONTENT_TYPE));
+    return res;
+  }
+
+  private String buildJsonRaw(String path) throws Exception {
+    FakeHttpServletResponse res = buildResponse(path, "format=json", SC_OK);
+    assertEquals("application/json", res.getHeader(HttpHeaders.CONTENT_TYPE));
+    String body = res.getActualBodyString();
+    String magic = ")]}'\n";
+    assertEquals(magic, body.substring(0, magic.length()));
+    return body.substring(magic.length());
+  }
+
+  protected <T> T buildJson(String path, Class<T> classOfT) throws Exception {
+    return new Gson().fromJson(buildJsonRaw(path), classOfT);
+  }
+
+  protected <T> T buildJson(String path, Type typeOfT) throws Exception {
+    return new Gson().<T>fromJson(buildJsonRaw(path), typeOfT);
+  }
+
+  protected void assertNotFound(String path, String queryString) throws Exception {
+    buildResponse(path, queryString, SC_NOT_FOUND);
+  }
+}
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/TestViewFilter.java b/gitiles-servlet/src/test/java/com/google/gitiles/TestViewFilter.java
index 18539c1..e6a4768 100644
--- a/gitiles-servlet/src/test/java/com/google/gitiles/TestViewFilter.java
+++ b/gitiles-servlet/src/test/java/com/google/gitiles/TestViewFilter.java
@@ -22,7 +22,6 @@
 
 import com.google.common.collect.ImmutableList;
 
-import org.eclipse.jgit.http.server.ServletUtils;
 import org.eclipse.jgit.http.server.glue.MetaFilter;
 import org.eclipse.jgit.http.server.glue.MetaServlet;
 import org.eclipse.jgit.internal.storage.dfs.DfsRepository;
@@ -78,7 +77,6 @@
     }
 
     FakeHttpServletRequest req = newRequest(repo, pathAndQuery);
-    req.setAttribute(ServletUtils.ATTRIBUTE_REPOSITORY, repo.getRepository());
     FakeHttpServletResponse res = new FakeHttpServletResponse();
     dummyServlet(mf).service(req, res);
     if (servlet.view != null) {
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/blame/BlameServletTest.java b/gitiles-servlet/src/test/java/com/google/gitiles/blame/BlameServletTest.java
index 2fca6d7..064b822 100644
--- a/gitiles-servlet/src/test/java/com/google/gitiles/blame/BlameServletTest.java
+++ b/gitiles-servlet/src/test/java/com/google/gitiles/blame/BlameServletTest.java
@@ -18,19 +18,10 @@
 
 import com.google.common.collect.Iterables;
 import com.google.gitiles.CommitJsonData.Ident;
-import com.google.gitiles.FakeHttpServletRequest;
-import com.google.gitiles.FakeHttpServletResponse;
-import com.google.gitiles.GitilesServlet;
-import com.google.gitiles.TestGitilesServlet;
-import com.google.gson.Gson;
+import com.google.gitiles.ServletTest;
 import com.google.gson.reflect.TypeToken;
 
-import org.eclipse.jgit.internal.storage.dfs.DfsRepository;
-import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
-import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
-import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.revwalk.RevCommit;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -39,7 +30,7 @@
 import java.util.Map;
 
 @RunWith(JUnit4.class)
-public class BlameServletTest {
+public class BlameServletTest extends ServletTest {
   private static class RegionJsonData {
     int start;
     int count;
@@ -48,16 +39,6 @@
     Ident author;
   }
 
-  private TestRepository<DfsRepository> repo;
-  private GitilesServlet servlet;
-
-  @Before
-  public void setUp() throws Exception {
-    DfsRepository r = new InMemoryRepository(new DfsRepositoryDescription("test"));
-    repo = new TestRepository<>(r);
-    servlet = TestGitilesServlet.create(repo);
-  }
-
   @Test
   public void blameJson() throws Exception {
     String contents1 = "foo\n";
@@ -65,7 +46,7 @@
     RevCommit c1 = repo.update("master", repo.commit().add("foo", contents1));
     RevCommit c2 = repo.update("master", repo.commit().tick(10).parent(c1).add("foo", contents2));
 
-    Map<String, List<RegionJsonData>> result = getBlameJson("/test/+blame/" + c2.name() + "/foo");
+    Map<String, List<RegionJsonData>> result = getBlameJson("/repo/+blame/" + c2.name() + "/foo");
     assertEquals("regions", Iterables.getOnlyElement(result.keySet()));
     List<RegionJsonData> regions = result.get("regions");
     assertEquals(2, regions.size());
@@ -90,17 +71,6 @@
   }
 
   private Map<String, List<RegionJsonData>> getBlameJson(String path) throws Exception {
-    FakeHttpServletRequest req = FakeHttpServletRequest.newRequest();
-    req.setPathInfo(path);
-    req.setQueryString("format=JSON");
-
-    FakeHttpServletResponse res = new FakeHttpServletResponse();
-    servlet.service(req, res);
-    assertEquals(200, res.getStatus());
-    String body = res.getActualBodyString();
-    String magic = ")]}'\n";
-    assertEquals(magic, body.substring(0, magic.length()));
-    return new Gson().fromJson(body.substring(magic.length()),
-        new TypeToken<Map<String, List<RegionJsonData>>>() {}.getType());
+    return buildJson(path, new TypeToken<Map<String, List<RegionJsonData>>>() {}.getType());
   }
 }
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/doc/DocServletTest.java b/gitiles-servlet/src/test/java/com/google/gitiles/doc/DocServletTest.java
index 56b7227..a53949c 100644
--- a/gitiles-servlet/src/test/java/com/google/gitiles/doc/DocServletTest.java
+++ b/gitiles-servlet/src/test/java/com/google/gitiles/doc/DocServletTest.java
@@ -14,47 +14,18 @@
 
 package com.google.gitiles.doc;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.net.HttpHeaders;
-import com.google.gitiles.DefaultRenderer;
-import com.google.gitiles.FakeHttpServletResponse;
-import com.google.gitiles.GitilesView;
-import com.google.gitiles.Renderer;
-import com.google.gitiles.TestGitilesAccess;
-import com.google.gitiles.TestViewFilter;
+import com.google.gitiles.ServletTest;
 
-import org.eclipse.jgit.internal.storage.dfs.DfsRepository;
-import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
-import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
-import org.eclipse.jgit.junit.TestRepository;
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.net.URL;
-
 /** Tests for {DocServlet}. */
 @RunWith(JUnit4.class)
-public class DocServletTest {
-  private static final Renderer RENDERER =
-      new DefaultRenderer("/+static", ImmutableList.<URL> of(), "Test");
-
-  private TestRepository<DfsRepository> repo;
-  private DocServlet servlet;
-
-  @Before
-  public void setUp() throws Exception {
-    DfsRepository r = new InMemoryRepository(new DfsRepositoryDescription("repo"));
-    repo = new TestRepository<>(r);
-    servlet = new DocServlet(new TestGitilesAccess(repo.getRepository()), RENDERER);
-  }
-
+public class DocServletTest extends ServletTest {
   @Test
   public void simpleReadmeDoc() throws Exception {
     String title = "DocServletTest simpleDoc";
@@ -162,19 +133,4 @@
     assertTrue(html.contains("<a href=\"/b/repo/+show/master/x\">c</a>"));
   }
 
-  private String buildHtml(String pathAndQuery) throws Exception {
-    TestViewFilter.Result res = service(pathAndQuery);
-    FakeHttpServletResponse http = res.getResponse();
-    assertEquals("text/html", http.getHeader(HttpHeaders.CONTENT_TYPE));
-    assertNotNull("has ETag", http.getHeader(HttpHeaders.ETAG));
-    return http.getActualBodyString();
-  }
-
-  private TestViewFilter.Result service(String pathAndQuery) throws Exception {
-    TestViewFilter.Result res = TestViewFilter.service(repo, pathAndQuery);
-    assertEquals(200, res.getResponse().getStatus());
-    assertEquals(GitilesView.Type.DOC, res.getView().getType());
-    servlet.service(res.getRequest(), res.getResponse());
-    return res;
-  }
 }