ch14.6-14.7

This commit is contained in:
lbenedar
2026-03-06 19:58:15 +03:00
parent 577f902ab3
commit 69658a7bd4
8 changed files with 267 additions and 1 deletions

View File

@@ -134,8 +134,9 @@ func (app *application) snippetCreatePost(w http.ResponseWriter, r *http.Request
func (app *application) userSignupPost(w http.ResponseWriter, r *http.Request) {
var form userSignupForm
err := app.decodePostForm(r, &form)
if err != nil {
app.clientError(w, http.StatusBadRequest)
app.notFound(w)
return
}

View File

@@ -2,6 +2,7 @@ package main
import (
"net/http"
"net/url"
"testing"
"gitea.local.lab/Lbenedar/snippetbox/internal/assert"
@@ -76,3 +77,118 @@ func TestSnippetView(t *testing.T) {
})
}
}
func TestUserSignup(t *testing.T) {
app := newTestApplication(t)
ts := newTestServer(t, app.routes())
defer ts.Close()
_, _, body := ts.get(t, "/user/signup")
validCsrfToken := extractCSRFToken(t, body)
t.Logf("CsrfToken: %s", validCsrfToken)
const (
validName = "Bob"
validPassword = "validPa$$word"
validEmail = "bob@example.com"
formTag = "<form action='/user/signup' method='POST' novalidate>"
)
tests := []struct {
name string
userName string
userEmail string
userPassword string
csrfToken string
wantCode int
wantFormTag string
}{
{
name: "Valid submission",
userName: validName,
userEmail: validEmail,
userPassword: validPassword,
csrfToken: validCsrfToken,
wantCode: http.StatusSeeOther,
},
{
name: "Invalid CSRF Token",
userName: validName,
userEmail: validEmail,
userPassword: validPassword,
csrfToken: "wrongToken",
wantCode: http.StatusBadRequest,
},
{
name: "Empty name",
userName: "",
userEmail: validEmail,
userPassword: validPassword,
csrfToken: validCsrfToken,
wantCode: http.StatusUnprocessableEntity,
wantFormTag: formTag,
},
{
name: "Empty email",
userName: validName,
userEmail: "",
userPassword: validPassword,
csrfToken: validCsrfToken,
wantCode: http.StatusUnprocessableEntity,
wantFormTag: formTag,
},
{
name: "Empty password",
userName: validName,
userEmail: validEmail,
userPassword: "",
csrfToken: validCsrfToken,
wantCode: http.StatusUnprocessableEntity,
wantFormTag: formTag,
},
{
name: "Invalid email",
userName: validName,
userEmail: "bob@example.",
userPassword: validPassword,
csrfToken: validCsrfToken,
wantCode: http.StatusUnprocessableEntity,
wantFormTag: formTag,
},
{
name: "Short password",
userName: validName,
userEmail: validEmail,
userPassword: "pa$$",
csrfToken: validCsrfToken,
wantCode: http.StatusUnprocessableEntity,
wantFormTag: formTag,
},
{
name: "Duplicate email",
userName: validName,
userEmail: "dupe@example.com",
userPassword: validPassword,
csrfToken: validCsrfToken,
wantCode: http.StatusUnprocessableEntity,
wantFormTag: formTag,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
form := url.Values{}
form.Add("name", tt.userName)
form.Add("email", tt.userEmail)
form.Add("password", tt.userPassword)
form.Add("csrf_token", tt.csrfToken)
code, _, body := ts.postForm(t, "/user/signup", form)
assert.Equal(t, code, tt.wantCode)
if tt.wantFormTag != "" {
assert.StringContains(t, body, tt.wantFormTag)
}
})
}
}

View File

@@ -2,11 +2,14 @@ package main
import (
"bytes"
"html"
"io"
"log"
"net/http"
"net/http/cookiejar"
"net/http/httptest"
"net/url"
"regexp"
"testing"
"time"
@@ -15,6 +18,18 @@ import (
"github.com/go-playground/form/v4"
)
var csrfTokenRX = regexp.MustCompile(`<input type='hidden' name='csrf_token' value='(.+)'>`)
func extractCSRFToken(t *testing.T, body string) string {
t.Logf("Body: %s", body)
matches := csrfTokenRX.FindStringSubmatch(body)
if len(matches) < 2 {
t.Fatal("no csrf token found in body")
}
return html.UnescapeString(string(matches[1]))
}
func newTestApplication(t *testing.T) *application {
templateCache, err := newTemplateCache()
if err != nil {
@@ -72,3 +87,18 @@ func (ts *testServer) get(t *testing.T, urlPath string) (int, http.Header, strin
return rs.StatusCode, rs.Header, string(body)
}
func (ts *testServer) postForm(t *testing.T, urlPath string, form url.Values) (int, http.Header, string) {
rs, err := ts.Client().PostForm(ts.URL+urlPath, form)
if err != nil {
t.Fatal(err)
}
defer rs.Body.Close()
body, err := io.ReadAll(rs.Body)
if err != nil {
t.Fatal(err)
}
bytes.TrimSpace(body)
return rs.StatusCode, rs.Header, string(body)
}