211 lines
6.4 KiB
Go
211 lines
6.4 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"log"
|
||
|
"net/http"
|
||
|
"net/http/httptest"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/PuerkitoBio/goquery"
|
||
|
)
|
||
|
|
||
|
func TestHeader(t *testing.T) {
|
||
|
// Pipe the rendered template into goquery.
|
||
|
r, w := io.Pipe()
|
||
|
go func() {
|
||
|
_ = headerTemplate("Posts").Render(context.Background(), w)
|
||
|
_ = w.Close()
|
||
|
}()
|
||
|
doc, err := goquery.NewDocumentFromReader(r)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to read template: %v", err)
|
||
|
}
|
||
|
// Expect the component to include a testid.
|
||
|
if doc.Find(`[data-testid="headerTemplate"]`).Length() == 0 {
|
||
|
t.Error("expected data-testid attribute to be rendered, but it wasn't")
|
||
|
}
|
||
|
// Expect the page name to be set correctly.
|
||
|
expectedPageName := "Posts"
|
||
|
if actualPageName := doc.Find("h1").Text(); actualPageName != expectedPageName {
|
||
|
t.Errorf("expected page name %q, got %q", expectedPageName, actualPageName)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestFooter(t *testing.T) {
|
||
|
// Pipe the rendered template into goquery.
|
||
|
r, w := io.Pipe()
|
||
|
go func() {
|
||
|
_ = footerTemplate().Render(context.Background(), w)
|
||
|
_ = w.Close()
|
||
|
}()
|
||
|
doc, err := goquery.NewDocumentFromReader(r)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to read template: %v", err)
|
||
|
}
|
||
|
// Expect the component to include a testid.
|
||
|
if doc.Find(`[data-testid="footerTemplate"]`).Length() == 0 {
|
||
|
t.Error("expected data-testid attribute to be rendered, but it wasn't")
|
||
|
}
|
||
|
// Expect the copyright notice to include the current year.
|
||
|
expectedCopyrightNotice := fmt.Sprintf("© %d", time.Now().Year())
|
||
|
if actualCopyrightNotice := doc.Find("div").Text(); actualCopyrightNotice != expectedCopyrightNotice {
|
||
|
t.Errorf("expected copyright notice %q, got %q", expectedCopyrightNotice, actualCopyrightNotice)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestNav(t *testing.T) {
|
||
|
r, w := io.Pipe()
|
||
|
go func() {
|
||
|
_ = navTemplate().Render(context.Background(), w)
|
||
|
_ = w.Close()
|
||
|
}()
|
||
|
doc, err := goquery.NewDocumentFromReader(r)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to read template: %v", err)
|
||
|
}
|
||
|
// Expect the component to include a testid.
|
||
|
if doc.Find(`[data-testid="navTemplate"]`).Length() == 0 {
|
||
|
t.Error("expected data-testid attribute to be rendered, but it wasn't")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestHome(t *testing.T) {
|
||
|
r, w := io.Pipe()
|
||
|
go func() {
|
||
|
_ = home().Render(context.Background(), w)
|
||
|
_ = w.Close()
|
||
|
}()
|
||
|
doc, err := goquery.NewDocumentFromReader(r)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to read template: %v", err)
|
||
|
}
|
||
|
// Expect the page title to be set correctly.
|
||
|
expectedTitle := "Home"
|
||
|
if actualTitle := doc.Find("title").Text(); actualTitle != expectedTitle {
|
||
|
t.Errorf("expected title name %q, got %q", expectedTitle, actualTitle)
|
||
|
}
|
||
|
// Expect the header to be rendered.
|
||
|
if doc.Find(`[data-testid="headerTemplate"]`).Length() == 0 {
|
||
|
t.Error("expected data-testid attribute to be rendered, but it wasn't")
|
||
|
}
|
||
|
// Expect the navigation to be rendered.
|
||
|
if doc.Find(`[data-testid="navTemplate"]`).Length() == 0 {
|
||
|
t.Error("expected nav to be rendered, but it wasn't")
|
||
|
}
|
||
|
// Expect the home template be rendered.
|
||
|
if doc.Find(`[data-testid="homeTemplate"]`).Length() == 0 {
|
||
|
t.Error("expected homeTemplate to be rendered, but it wasn't")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestPosts(t *testing.T) {
|
||
|
testPosts := []Post{
|
||
|
{Name: "Name1", Author: "Author1"},
|
||
|
{Name: "Name2", Author: "Author2"},
|
||
|
}
|
||
|
r, w := io.Pipe()
|
||
|
go func() {
|
||
|
_ = posts(testPosts).Render(context.Background(), w)
|
||
|
_ = w.Close()
|
||
|
}()
|
||
|
doc, err := goquery.NewDocumentFromReader(r)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to read template: %v", err)
|
||
|
}
|
||
|
// Assert.
|
||
|
// Expect the page title to be set correctly.
|
||
|
expectedTitle := "Posts"
|
||
|
if actualTitle := doc.Find("title").Text(); actualTitle != expectedTitle {
|
||
|
t.Errorf("expected title name %q, got %q", expectedTitle, actualTitle)
|
||
|
}
|
||
|
// Expect the header to be rendered.
|
||
|
if doc.Find(`[data-testid="headerTemplate"]`).Length() == 0 {
|
||
|
t.Error("expected data-testid attribute to be rendered, but it wasn't")
|
||
|
}
|
||
|
// Expect the navigation to be rendered.
|
||
|
if doc.Find(`[data-testid="navTemplate"]`).Length() == 0 {
|
||
|
t.Error("expected nav to be rendered, but it wasn't")
|
||
|
}
|
||
|
// Expect the posts to be rendered.
|
||
|
if doc.Find(`[data-testid="postsTemplate"]`).Length() == 0 {
|
||
|
t.Error("expected posts to be rendered, but it wasn't")
|
||
|
}
|
||
|
// Expect both posts to be rendered.
|
||
|
if actualPostCount := doc.Find(`[data-testid="postsTemplatePost"]`).Length(); actualPostCount != len(testPosts) {
|
||
|
t.Fatalf("expected %d posts to be rendered, found %d", len(testPosts), actualPostCount)
|
||
|
}
|
||
|
// Expect the posts to contain the author name.
|
||
|
doc.Find(`[data-testid="postsTemplatePost"]`).Each(func(index int, sel *goquery.Selection) {
|
||
|
expectedName := testPosts[index].Name
|
||
|
if actualName := sel.Find(`[data-testid="postsTemplatePostName"]`).Text(); actualName != expectedName {
|
||
|
t.Errorf("expected name %q, got %q", actualName, expectedName)
|
||
|
}
|
||
|
expectedAuthor := testPosts[index].Author
|
||
|
if actualAuthor := sel.Find(`[data-testid="postsTemplatePostAuthor"]`).Text(); actualAuthor != expectedAuthor {
|
||
|
t.Errorf("expected author %q, got %q", actualAuthor, expectedAuthor)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestPostsHandler(t *testing.T) {
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
postGetter func() (posts []Post, err error)
|
||
|
expectedStatus int
|
||
|
assert func(doc *goquery.Document)
|
||
|
}{
|
||
|
{
|
||
|
name: "database errors result in a 500 error",
|
||
|
postGetter: func() (posts []Post, err error) {
|
||
|
return nil, errors.New("database error")
|
||
|
},
|
||
|
expectedStatus: http.StatusInternalServerError,
|
||
|
assert: func(doc *goquery.Document) {
|
||
|
expected := "failed to retrieve posts\n"
|
||
|
if actual := doc.Text(); actual != expected {
|
||
|
t.Errorf("expected error message %q, got %q", expected, actual)
|
||
|
}
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "database success renders the posts",
|
||
|
postGetter: func() (posts []Post, err error) {
|
||
|
return []Post{
|
||
|
{Name: "Name1", Author: "Author1"},
|
||
|
{Name: "Name2", Author: "Author2"},
|
||
|
}, nil
|
||
|
},
|
||
|
expectedStatus: http.StatusInternalServerError,
|
||
|
assert: func(doc *goquery.Document) {
|
||
|
if doc.Find(`[data-testid="postsTemplate"]`).Length() == 0 {
|
||
|
t.Error("expected posts to be rendered, but it wasn't")
|
||
|
}
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
for _, test := range tests {
|
||
|
// Arrange.
|
||
|
w := httptest.NewRecorder()
|
||
|
r := httptest.NewRequest(http.MethodGet, "/posts", nil)
|
||
|
|
||
|
ph := NewPostsHandler()
|
||
|
ph.Log = log.New(io.Discard, "", 0) // Suppress logging.
|
||
|
ph.GetPosts = test.postGetter
|
||
|
|
||
|
// Act.
|
||
|
ph.ServeHTTP(w, r)
|
||
|
doc, err := goquery.NewDocumentFromReader(w.Result().Body)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to read template: %v", err)
|
||
|
}
|
||
|
|
||
|
// Assert.
|
||
|
test.assert(doc)
|
||
|
}
|
||
|
}
|