CI / test (pull_request) Successful in 6m17s
Reviewed-on: #8 Co-authored-by: codegirl-007 <s.raide@gmail.com>
541 lines
17 KiB
Go
541 lines
17 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"plumber/internal/mail"
|
|
"plumber/internal/pacific"
|
|
"plumber/internal/store"
|
|
)
|
|
|
|
func TestCreatePostRoutePermissions(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
srv, mem := newTestServer(t, Config{})
|
|
handler := srv.Handler()
|
|
homeowner := seedUser(t, mem, uniq("homeowner"), "hunter22", store.RoleUser)
|
|
other := seedUser(t, mem, uniq("other"), "hunter22", store.RoleUser)
|
|
admin := seedUser(t, mem, uniq("admin"), "hunter22", store.RoleAdmin)
|
|
homeownerCookies := loginUser(t, handler, homeowner.Username, "hunter22")
|
|
otherCookies := loginUser(t, handler, other.Username, "hunter22")
|
|
adminCookies := loginUser(t, handler, admin.Username, "hunter22")
|
|
homeownerCSRF := csrfForCookies(t, handler, homeownerCookies)
|
|
otherCSRF := csrfForCookies(t, handler, otherCookies)
|
|
adminCSRF := csrfForCookies(t, handler, adminCookies)
|
|
|
|
rec := postForm(handler, "/posts", url.Values{
|
|
"title": {"No CSRF"},
|
|
"body": {"Body"},
|
|
}, homeownerCookies)
|
|
if rec.Code != http.StatusForbidden {
|
|
t.Fatalf("missing CSRF status = %d, want 403", rec.Code)
|
|
}
|
|
|
|
anonRec := httptest.NewRecorder()
|
|
handler.ServeHTTP(anonRec, httptest.NewRequest(http.MethodGet, "/login", nil))
|
|
anonCookies := anonRec.Result().Cookies()
|
|
anonCSRF := csrfFrom(anonRec.Body.String())
|
|
rec = postForm(handler, "/posts", url.Values{
|
|
"_csrf": {anonCSRF},
|
|
"title": {"Anonymous"},
|
|
"body": {"Body"},
|
|
}, anonCookies)
|
|
if rec.Code != http.StatusUnauthorized {
|
|
t.Fatalf("anonymous create status = %d, want 401", rec.Code)
|
|
}
|
|
|
|
rec = postForm(handler, "/posts", url.Values{
|
|
"_csrf": {homeownerCSRF},
|
|
"title": {"Leaky sink"},
|
|
"body": {"It drips."},
|
|
"city": {"Oakland"},
|
|
}, homeownerCookies)
|
|
if rec.Code != http.StatusSeeOther {
|
|
t.Fatalf("root create status = %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
roots, err := mem.ListRootPosts(context.Background(), pacific.Today(), homeowner.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(roots) != 1 ||
|
|
roots[0].AuthorID != homeowner.ID ||
|
|
roots[0].Title != "Leaky sink" ||
|
|
roots[0].PostState != store.PostStateVisible {
|
|
t.Fatalf("created root = %+v", roots)
|
|
}
|
|
root := roots[0]
|
|
if got := rec.Header().Get("Location"); got != "/questions/"+root.ID+"#post-"+root.ID {
|
|
t.Fatalf("root redirect = %q", got)
|
|
}
|
|
|
|
rec = postForm(handler, "/posts", url.Values{
|
|
"_csrf": {homeownerCSRF},
|
|
"parent_id": {root.ID},
|
|
"body": {"The model is 123."},
|
|
}, homeownerCookies)
|
|
if rec.Code != http.StatusSeeOther {
|
|
t.Fatalf("homeowner reply status = %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
thread, err := mem.GetPostThread(context.Background(), root.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(thread.Replies) != 1 || thread.Replies[0].AuthorID != homeowner.ID {
|
|
t.Fatalf("homeowner reply missing: %+v", thread)
|
|
}
|
|
homeownerReply := thread.Replies[0]
|
|
|
|
rec = postForm(handler, "/posts", url.Values{
|
|
"_csrf": {adminCSRF},
|
|
"parent_id": {homeownerReply.ID},
|
|
"body": {"Replace the cartridge."},
|
|
}, adminCookies)
|
|
if rec.Code != http.StatusSeeOther {
|
|
t.Fatalf("admin nested reply status = %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
thread, err = mem.GetPostThread(context.Background(), root.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(thread.Replies[0].Replies) != 1 ||
|
|
thread.Replies[0].Replies[0].AuthorID != admin.ID {
|
|
t.Fatalf("admin nested reply missing: %+v", thread)
|
|
}
|
|
|
|
rec = postForm(handler, "/posts", url.Values{
|
|
"_csrf": {otherCSRF},
|
|
"parent_id": {homeownerReply.ID},
|
|
"body": {"I should not be here."},
|
|
}, otherCookies)
|
|
if rec.Code != http.StatusForbidden {
|
|
t.Fatalf("unrelated reply status = %d, want 403", rec.Code)
|
|
}
|
|
|
|
rec = postForm(handler, "/posts", url.Values{
|
|
"_csrf": {homeownerCSRF},
|
|
"parent_id": {"missing"},
|
|
"body": {"Missing parent"},
|
|
}, homeownerCookies)
|
|
if rec.Code != http.StatusNotFound {
|
|
t.Fatalf("missing-parent reply status = %d, want 404", rec.Code)
|
|
}
|
|
|
|
hidden := &store.Post{
|
|
AuthorID: homeowner.ID,
|
|
Title: "Hidden thread",
|
|
Body: "Body",
|
|
PostDate: pacific.Today(),
|
|
PostState: store.PostStateHidden,
|
|
}
|
|
if err := mem.CreatePost(context.Background(), hidden); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rec = postForm(handler, "/posts", url.Values{
|
|
"_csrf": {homeownerCSRF},
|
|
"parent_id": {hidden.ID},
|
|
"body": {"Hidden reply"},
|
|
}, homeownerCookies)
|
|
if rec.Code != http.StatusNotFound {
|
|
t.Fatalf("hidden-thread reply status = %d, want 404", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestEditPostRoutePermissions(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
srv, mem := newTestServer(t, Config{})
|
|
handler := srv.Handler()
|
|
homeowner := seedUser(t, mem, uniq("homeowner"), "hunter22", store.RoleUser)
|
|
other := seedUser(t, mem, uniq("other"), "hunter22", store.RoleUser)
|
|
admin := seedUser(t, mem, uniq("admin"), "hunter22", store.RoleAdmin)
|
|
secondAdmin := seedUser(t, mem, uniq("admin"), "hunter22", store.RoleAdmin)
|
|
if err := mem.SetUserRole(context.Background(), secondAdmin.ID, store.RoleAdmin); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
homeownerCookies := loginUser(t, handler, homeowner.Username, "hunter22")
|
|
otherCookies := loginUser(t, handler, other.Username, "hunter22")
|
|
adminCookies := loginUser(t, handler, admin.Username, "hunter22")
|
|
secondAdminCookies := loginUser(t, handler, secondAdmin.Username, "hunter22")
|
|
homeownerCSRF := csrfForCookies(t, handler, homeownerCookies)
|
|
otherCSRF := csrfForCookies(t, handler, otherCookies)
|
|
adminCSRF := csrfForCookies(t, handler, adminCookies)
|
|
secondAdminCSRF := csrfForCookies(t, handler, secondAdminCookies)
|
|
|
|
root := &store.Post{
|
|
AuthorID: homeowner.ID,
|
|
Title: "Leaky sink",
|
|
Body: "Original body",
|
|
PostDate: pacific.Today(),
|
|
}
|
|
if err := mem.CreatePost(context.Background(), root); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rootID := root.ID
|
|
adminReply := &store.Post{
|
|
ParentID: &rootID,
|
|
AuthorID: admin.ID,
|
|
Body: "Original answer",
|
|
}
|
|
if err := mem.CreatePost(context.Background(), adminReply); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
anonRec := httptest.NewRecorder()
|
|
handler.ServeHTTP(anonRec, httptest.NewRequest(http.MethodGet, "/login", nil))
|
|
rec := postForm(handler, "/posts/"+root.ID+"/edit", url.Values{
|
|
"_csrf": {csrfFrom(anonRec.Body.String())},
|
|
"body": {"Anonymous edit"},
|
|
}, anonRec.Result().Cookies())
|
|
if rec.Code != http.StatusUnauthorized {
|
|
t.Fatalf("anonymous edit status = %d, want 401", rec.Code)
|
|
}
|
|
|
|
rec = postForm(handler, "/posts/"+root.ID+"/edit", url.Values{
|
|
"_csrf": {homeownerCSRF},
|
|
"body": {"Updated homeowner body"},
|
|
"parent_id": {adminReply.ID},
|
|
"author_id": {other.ID},
|
|
}, homeownerCookies)
|
|
if rec.Code != http.StatusSeeOther {
|
|
t.Fatalf("homeowner edit status = %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
saved, err := mem.GetPost(context.Background(), root.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if saved.Body != "Updated homeowner body" ||
|
|
saved.ParentID != nil ||
|
|
saved.AuthorID != homeowner.ID {
|
|
t.Fatalf("homeowner edit changed immutable fields: %+v", saved)
|
|
}
|
|
|
|
for name, session := range map[string]struct {
|
|
cookies []*http.Cookie
|
|
csrf string
|
|
}{
|
|
"other homeowner": {otherCookies, otherCSRF},
|
|
"admin": {adminCookies, adminCSRF},
|
|
} {
|
|
t.Run(name+" cannot edit homeowner post", func(t *testing.T) {
|
|
rec := postForm(handler, "/posts/"+root.ID+"/edit", url.Values{
|
|
"_csrf": {session.csrf},
|
|
"body": {"Unauthorized edit"},
|
|
}, session.cookies)
|
|
if rec.Code != http.StatusForbidden {
|
|
t.Fatalf("status = %d, want 403", rec.Code)
|
|
}
|
|
})
|
|
}
|
|
|
|
rec = postForm(handler, "/posts/"+adminReply.ID+"/edit", url.Values{
|
|
"_csrf": {homeownerCSRF},
|
|
"body": {"Homeowner edit"},
|
|
}, homeownerCookies)
|
|
if rec.Code != http.StatusForbidden {
|
|
t.Fatalf("homeowner editing admin post status = %d, want 403", rec.Code)
|
|
}
|
|
|
|
rec = postForm(handler, "/posts/"+adminReply.ID+"/edit", url.Values{
|
|
"_csrf": {secondAdminCSRF},
|
|
"body": {"Updated admin answer"},
|
|
}, secondAdminCookies)
|
|
if rec.Code != http.StatusSeeOther {
|
|
t.Fatalf("admin edit status = %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
saved, err = mem.GetPost(context.Background(), adminReply.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if saved.Body != "Updated admin answer" ||
|
|
saved.ParentID == nil ||
|
|
*saved.ParentID != root.ID ||
|
|
saved.AuthorID != admin.ID {
|
|
t.Fatalf("admin edit changed immutable fields: %+v", saved)
|
|
}
|
|
}
|
|
|
|
func TestPostReplyNotifications(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
recording := &mail.Recording{}
|
|
srv, mem := newTestServer(t, Config{Mail: recording})
|
|
handler := srv.Handler()
|
|
homeowner := seedUser(t, mem, uniq("homeowner"), "hunter22", store.RoleUser)
|
|
admin := seedUser(t, mem, uniq("admin"), "hunter22", store.RoleAdmin)
|
|
homeownerCookies := loginUser(t, handler, homeowner.Username, "hunter22")
|
|
adminCookies := loginUser(t, handler, admin.Username, "hunter22")
|
|
homeownerCSRF := csrfForCookies(t, handler, homeownerCookies)
|
|
adminCSRF := csrfForCookies(t, handler, adminCookies)
|
|
|
|
rec := postForm(handler, "/posts", url.Values{
|
|
"_csrf": {homeownerCSRF},
|
|
"title": {"Leaky sink"},
|
|
"body": {"Water under the cabinet."},
|
|
}, homeownerCookies)
|
|
if rec.Code != http.StatusSeeOther {
|
|
t.Fatalf("root create status = %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
if recording.Len() != 0 {
|
|
t.Fatalf("root create sent %d notifications", recording.Len())
|
|
}
|
|
roots, err := mem.ListRootPosts(context.Background(), pacific.Today(), homeowner.ID)
|
|
if err != nil || len(roots) != 1 {
|
|
t.Fatalf("created roots = %+v, %v", roots, err)
|
|
}
|
|
root := roots[0]
|
|
|
|
rec = postForm(handler, "/posts", url.Values{
|
|
"_csrf": {adminCSRF},
|
|
"parent_id": {root.ID},
|
|
"body": {"Replace the cartridge."},
|
|
}, adminCookies)
|
|
if rec.Code != http.StatusSeeOther {
|
|
t.Fatalf("admin reply status = %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
thread, err := mem.GetPostThread(context.Background(), root.ID)
|
|
if err != nil || len(thread.Replies) != 1 {
|
|
t.Fatalf("admin reply thread = %+v, %v", thread, err)
|
|
}
|
|
adminReply := thread.Replies[0]
|
|
msgs := waitForMail(t, recording, 1)
|
|
if msg := msgs[0]; msg.ToEmail != homeowner.Email ||
|
|
msg.RootID != root.ID ||
|
|
msg.RootTitle != root.Title ||
|
|
msg.ReplyID != adminReply.ID ||
|
|
msg.ReplyBody != adminReply.Body ||
|
|
msg.ReplyAuthorName != admin.Name {
|
|
t.Fatalf("admin reply notification = %+v", msg)
|
|
}
|
|
|
|
rec = postForm(handler, "/posts", url.Values{
|
|
"_csrf": {homeownerCSRF},
|
|
"parent_id": {adminReply.ID},
|
|
"body": {"That fixed the drip."},
|
|
}, homeownerCookies)
|
|
if rec.Code != http.StatusSeeOther {
|
|
t.Fatalf("homeowner reply status = %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
thread, err = mem.GetPostThread(context.Background(), root.ID)
|
|
if err != nil || len(thread.Replies[0].Replies) != 1 {
|
|
t.Fatalf("homeowner nested reply thread = %+v, %v", thread, err)
|
|
}
|
|
homeownerReply := thread.Replies[0].Replies[0]
|
|
msgs = waitForMail(t, recording, 2)
|
|
if msg := msgs[1]; msg.ToEmail != admin.Email ||
|
|
msg.RootID != root.ID ||
|
|
msg.ReplyID != homeownerReply.ID ||
|
|
msg.ReplyAuthorName != homeowner.Name {
|
|
t.Fatalf("homeowner reply notification = %+v", msg)
|
|
}
|
|
|
|
rec = postForm(handler, "/posts", url.Values{
|
|
"_csrf": {adminCSRF},
|
|
"parent_id": {adminReply.ID},
|
|
"body": {"One more plumber detail."},
|
|
}, adminCookies)
|
|
if rec.Code != http.StatusSeeOther {
|
|
t.Fatalf("nested admin reply status = %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
msgs = waitForMail(t, recording, 3)
|
|
if msg := msgs[2]; msg.ToEmail != homeowner.Email ||
|
|
msg.RootID != root.ID ||
|
|
msg.ReplyBody != "One more plumber detail." ||
|
|
msg.ReplyAuthorName != admin.Name {
|
|
t.Fatalf("nested admin reply notification = %+v", msg)
|
|
}
|
|
|
|
rec = postForm(handler, "/posts", url.Values{
|
|
"_csrf": {homeownerCSRF},
|
|
"parent_id": {root.ID},
|
|
"body": {"A note to myself."},
|
|
}, homeownerCookies)
|
|
if rec.Code != http.StatusSeeOther {
|
|
t.Fatalf("self reply status = %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
rec = postForm(handler, "/posts/"+adminReply.ID+"/edit", url.Values{
|
|
"_csrf": {adminCSRF},
|
|
"body": {"Replace the ceramic cartridge."},
|
|
}, adminCookies)
|
|
if rec.Code != http.StatusSeeOther {
|
|
t.Fatalf("edit status = %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
|
|
noEmail := &store.User{
|
|
Username: uniq("no-email"),
|
|
PasswordHash: homeowner.PasswordHash,
|
|
Role: store.RoleUser,
|
|
}
|
|
if err := mem.CreateUser(context.Background(), noEmail); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
noEmailRoot := &store.Post{
|
|
AuthorID: noEmail.ID,
|
|
Title: "Quiet thread",
|
|
Body: "No email configured.",
|
|
PostDate: pacific.Today(),
|
|
}
|
|
if err := mem.CreatePost(context.Background(), noEmailRoot); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rec = postForm(handler, "/posts", url.Values{
|
|
"_csrf": {adminCSRF},
|
|
"parent_id": {noEmailRoot.ID},
|
|
"body": {"This should not send."},
|
|
}, adminCookies)
|
|
if rec.Code != http.StatusSeeOther {
|
|
t.Fatalf("no-email reply status = %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
time.Sleep(50 * time.Millisecond)
|
|
if recording.Len() != 3 {
|
|
t.Fatalf("self, edit, or no-email action sent a notification: %+v", recording.Snapshot())
|
|
}
|
|
}
|
|
|
|
func TestQuestionPageRendersNestedPostControls(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
srv, mem := newTestServer(t, Config{})
|
|
handler := srv.Handler()
|
|
homeowner := seedUser(t, mem, uniq("homeowner"), "hunter22", store.RoleUser)
|
|
admin := seedUser(t, mem, uniq("admin"), "hunter22", store.RoleAdmin)
|
|
homeownerCookies := loginUser(t, handler, homeowner.Username, "hunter22")
|
|
adminCookies := loginUser(t, handler, admin.Username, "hunter22")
|
|
|
|
root := &store.Post{
|
|
AuthorID: homeowner.ID,
|
|
Title: "Leaky sink",
|
|
Body: "Water under the cabinet.",
|
|
City: "Oakland",
|
|
PostDate: pacific.Today(),
|
|
}
|
|
if err := mem.CreatePost(context.Background(), root); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
homeownerReply := &store.Post{
|
|
ParentID: &root.ID,
|
|
AuthorID: homeowner.ID,
|
|
Body: "The model number is 123.",
|
|
}
|
|
if err := mem.CreatePost(context.Background(), homeownerReply); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
adminReply := &store.Post{
|
|
ParentID: &homeownerReply.ID,
|
|
AuthorID: admin.ID,
|
|
Body: "Replace the cartridge.",
|
|
}
|
|
if err := mem.CreatePost(context.Background(), adminReply); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
homeownerReply.Body = "The model number is 123A."
|
|
if err := mem.UpdatePost(context.Background(), homeownerReply); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, "/questions/"+root.ID, nil)
|
|
for _, cookie := range homeownerCookies {
|
|
req.AddCookie(cookie)
|
|
}
|
|
handler.ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("question page status = %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
body := rec.Body.String()
|
|
for _, want := range []string{
|
|
`id="post-` + root.ID + `"`,
|
|
`id="post-` + homeownerReply.ID + `"`,
|
|
`id="post-` + adminReply.ID + `"`,
|
|
`class="thread-post thread-post-branch`,
|
|
`class="thread-post thread-post-deep is-shop"`,
|
|
"Homeowner",
|
|
"Shop response",
|
|
"Edited",
|
|
`action="/posts"`,
|
|
`data-submit-once`,
|
|
`data-submit-button`,
|
|
`action="/posts/` + root.ID + `/edit"`,
|
|
`action="/posts/` + homeownerReply.ID + `/edit"`,
|
|
`href="/questions/` + root.ID + `#post-` + root.ID + `"`,
|
|
`href="/questions/` + root.ID + `#post-` + homeownerReply.ID + `"`,
|
|
`href="/questions/` + root.ID + `#post-` + adminReply.ID + `"`,
|
|
`>The model number is 123A.</textarea>`,
|
|
`removeAttribute('open')`,
|
|
} {
|
|
if !strings.Contains(body, want) {
|
|
t.Fatalf("question page missing %q: %s", want, body)
|
|
}
|
|
}
|
|
if got := strings.Count(body, ">Permalink</a>"); got != 3 {
|
|
t.Fatalf("question page rendered %d permalinks, want 3: %s", got, body)
|
|
}
|
|
if strings.Contains(body, `action="/posts/`+adminReply.ID+`/edit"`) {
|
|
t.Fatalf("homeowner can edit admin reply: %s", body)
|
|
}
|
|
|
|
rec = httptest.NewRecorder()
|
|
req = httptest.NewRequest(http.MethodGet, "/questions/"+root.ID, nil)
|
|
for _, cookie := range adminCookies {
|
|
req.AddCookie(cookie)
|
|
}
|
|
handler.ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusOK ||
|
|
!strings.Contains(rec.Body.String(), `action="/posts/`+adminReply.ID+`/edit"`) ||
|
|
strings.Contains(rec.Body.String(), `action="/posts/`+root.ID+`/edit"`) {
|
|
t.Fatalf("admin edit controls are incorrect: %d %s", rec.Code, rec.Body.String())
|
|
}
|
|
}
|
|
|
|
func waitForMail(t *testing.T, recording *mail.Recording, want int) []mail.PostReply {
|
|
t.Helper()
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
if recording.Len() >= want {
|
|
return recording.Snapshot()
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
t.Fatalf("recorded %d notifications, want %d", recording.Len(), want)
|
|
return nil
|
|
}
|
|
|
|
func csrfForCookies(t *testing.T, handler http.Handler, cookies []*http.Cookie) string {
|
|
t.Helper()
|
|
req := httptest.NewRequest(http.MethodGet, "/submit", nil)
|
|
for _, cookie := range cookies {
|
|
req.AddCookie(cookie)
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
handler.ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("load CSRF form status = %d", rec.Code)
|
|
}
|
|
csrf := csrfFrom(rec.Body.String())
|
|
if csrf == "" {
|
|
t.Fatal("CSRF token missing")
|
|
}
|
|
return csrf
|
|
}
|
|
|
|
func postForm(
|
|
handler http.Handler,
|
|
path string,
|
|
values url.Values,
|
|
cookies []*http.Cookie,
|
|
) *httptest.ResponseRecorder {
|
|
req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(values.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
for _, cookie := range cookies {
|
|
req.AddCookie(cookie)
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
handler.ServeHTTP(rec, req)
|
|
return rec
|
|
}
|