Add integration tests for talks app
diff --git a/talksapp/README.md b/talksapp/README.md
index 69227d5..2c47a2f 100644
--- a/talksapp/README.md
+++ b/talksapp/README.md
@@ -8,5 +8,6 @@
 
 - Copy `app.yaml` to `prod.yaml` and put in the authentication data.
 - Install Go App Engine SDK.
-- $ sh setup.sh 
+- `$ sh setup.sh`
 - Run the server using the `goapp serve prod.yaml` command.
+- Run the tests using the `goapp test` command.
diff --git a/talksapp/main.go b/talksapp/main.go
index 4a6c400..71b8943 100644
--- a/talksapp/main.go
+++ b/talksapp/main.go
@@ -36,6 +36,10 @@
 	homeArticle  = loadHomeArticle()
 	contactEmail = "golang-dev@googlegroups.com"
 	github       = httputil.NewAuthTransportFromEnvironment(nil)
+
+	// used for mocking in tests
+	getPresentation = gosrc.GetPresentation
+	playCompileUrl  = "http://play.golang.org/compile"
 )
 
 func init() {
@@ -180,7 +184,7 @@
 	}
 
 	c.Infof("Fetching presentation %s.", importPath)
-	pres, err := gosrc.GetPresentation(httpClient(r), importPath)
+	pres, err := getPresentation(httpClient(r), importPath)
 	if err != nil {
 		return err
 	}
@@ -222,7 +226,7 @@
 	if err := r.ParseForm(); err != nil {
 		return err
 	}
-	resp, err := client.PostForm("http://play.golang.org/compile", r.Form)
+	resp, err := client.PostForm(playCompileUrl, r.Form)
 	if err != nil {
 		return err
 	}
diff --git a/talksapp/main_test.go b/talksapp/main_test.go
new file mode 100644
index 0000000..b9a36a2
--- /dev/null
+++ b/talksapp/main_test.go
@@ -0,0 +1,293 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd.
+
+package talksapp
+
+import (
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/golang/gddo/gosrc"
+
+	"appengine"
+	"appengine/aetest"
+	"appengine/memcache"
+)
+
+const importPath = "github.com/user/repo/path/to/presentation.slide"
+
+func TestHome(t *testing.T) {
+	i, err := aetest.NewInstance(nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer i.Close()
+
+	r, err := i.NewRequest("GET", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handlerFunc(serveRoot).ServeHTTP(w, r)
+
+	if w.Code != http.StatusOK {
+		t.Fatalf("expected status: %d, got: %d", http.StatusOK, w.Code)
+	}
+
+	if !strings.Contains(w.Body.String(), "go-talks.appspot.org") {
+		t.Fatal("expected response to contain: go-talks.appspot.org")
+	}
+}
+
+func TestPresentation(t *testing.T) {
+	presentationTitle := "My awesome presentation!"
+	presentationSrc := []byte(presentationTitle + `
+
+Subtitle
+
+* Slide 1
+
+- Foo
+- Bar
+- Baz
+`)
+
+	originalGetPresentation := getPresentation
+	getPresentation = func(client *http.Client, importPath string) (*gosrc.Presentation, error) {
+		return &gosrc.Presentation{
+			Filename: "presentation.slide",
+			Files: map[string][]byte{
+				"presentation.slide": presentationSrc,
+			},
+		}, nil
+	}
+	defer func() {
+		getPresentation = originalGetPresentation
+	}()
+
+	i, err := aetest.NewInstance(nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer i.Close()
+
+	r, err := i.NewRequest("GET", "/"+importPath, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	w := httptest.NewRecorder()
+
+	handlerFunc(serveRoot).ServeHTTP(w, r)
+
+	if w.Code != http.StatusOK {
+		t.Fatalf("expected status: %d, got: %d", http.StatusOK, w.Code)
+	}
+
+	if !strings.Contains(w.Body.String(), presentationTitle) {
+		t.Fatalf("unexpected response body: %s", w.Body)
+	}
+
+	c := appengine.NewContext(r)
+	_, err = memcache.Get(c, importPath)
+
+	if err == memcache.ErrCacheMiss {
+		t.Fatal("expected result to be cached")
+	}
+
+	if err != nil {
+		t.Fatalf("expected no error, got: %s", err)
+	}
+}
+
+func TestPresentationCacheHit(t *testing.T) {
+	cachedPresentation := "<div>My Presentation</div>"
+
+	i, err := aetest.NewInstance(nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer i.Close()
+
+	r, err := i.NewRequest("GET", "/"+importPath, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	c := appengine.NewContext(r)
+	memcache.Add(c, &memcache.Item{
+		Key:        importPath,
+		Value:      []byte(cachedPresentation),
+		Expiration: time.Hour,
+	})
+
+	handlerFunc(serveRoot).ServeHTTP(w, r)
+
+	if w.Code != http.StatusOK {
+		t.Fatalf("expected status: %d, got: %d", http.StatusOK, w.Code)
+	}
+
+	if w.Body.String() != cachedPresentation {
+		t.Fatal("response does not matched cached presentation")
+	}
+}
+
+func TestPresentationNotFound(t *testing.T) {
+	originalGetPresentation := getPresentation
+	getPresentation = func(client *http.Client, importPath string) (*gosrc.Presentation, error) {
+		return nil, gosrc.NotFoundError{}
+	}
+	defer func() {
+		getPresentation = originalGetPresentation
+	}()
+
+	i, err := aetest.NewInstance(nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer i.Close()
+
+	r, err := i.NewRequest("GET", "/"+importPath, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handlerFunc(serveRoot).ServeHTTP(w, r)
+
+	if w.Code != http.StatusBadRequest {
+		t.Fatalf("expected status: %d, got: %d", http.StatusBadRequest, w.Code)
+	}
+}
+
+func TestWrongMethod(t *testing.T) {
+	i, err := aetest.NewInstance(nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer i.Close()
+
+	r, err := i.NewRequest("POST", "/", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handlerFunc(serveRoot).ServeHTTP(w, r)
+
+	if w.Code != http.StatusMethodNotAllowed {
+		t.Fatalf("expected status %d", http.StatusMethodNotAllowed)
+	}
+}
+
+func TestCompile(t *testing.T) {
+	version := "2"
+	body := `
+	package main
+
+	import "fmt"
+
+	func main() {
+		fmt.fmtPrintln("Hello, playground")
+	}
+	`
+	responseJSON := `{"Errors":"","Events":[{"Message":"Hello, playground\n","Kind":"stdout","Delay":0}]}`
+
+	server := httptest.NewServer(
+		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			formVersion := r.FormValue("version")
+			formBody := r.FormValue("body")
+
+			if formVersion != version {
+				t.Fatal("expected version sent to play.golang.org to be: %s, was: %s", version, formVersion)
+			}
+
+			if formBody != body {
+				t.Fatal("expected body sent to play.golang.org to be: %s, was: %s", body, formBody)
+			}
+
+			w.Header().Set("Content-Type", "application/json")
+			w.WriteHeader(200)
+
+			fmt.Fprintln(w, responseJSON)
+		}),
+	)
+	defer server.Close()
+
+	originalPlayCompileUrl := playCompileUrl
+	playCompileUrl = server.URL
+	defer func() {
+		playCompileUrl = originalPlayCompileUrl
+	}()
+
+	i, err := aetest.NewInstance(nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer i.Close()
+
+	r, err := i.NewRequest("POST", "/compile", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	r.PostForm = url.Values{
+		"version": []string{version},
+		"body":    []string{body},
+	}
+
+	w := httptest.NewRecorder()
+
+	handlerFunc(serveCompile).ServeHTTP(w, r)
+
+	if w.Code != http.StatusOK {
+		t.Fatalf("expected status: %d, got: %d", http.StatusOK, w.Code)
+	}
+
+	contentType := w.Header().Get("Content-Type")
+	if w.Header().Get("Content-Type") != "application/json" {
+		t.Fatalf("unexpected Content-Type: %s", contentType)
+	}
+
+	if strings.TrimSpace(w.Body.String()) != responseJSON {
+		t.Fatalf("unexpected response body: %s", w.Body)
+	}
+}
+
+func TestBot(t *testing.T) {
+	i, err := aetest.NewInstance(nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer i.Close()
+
+	r, err := i.NewRequest("GET", "/bot.html", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	w := httptest.NewRecorder()
+
+	handlerFunc(serveBot).ServeHTTP(w, r)
+
+	if w.Code != http.StatusOK {
+		t.Fatalf("expected status: %d, got: %d", http.StatusOK, w.Code)
+	}
+
+	if !strings.Contains(w.Body.String(), contactEmail) {
+		t.Fatalf("expected body to contain %s", contactEmail)
+	}
+}