ch14.6-14.7
This commit is contained in:
@@ -134,8 +134,9 @@ func (app *application) snippetCreatePost(w http.ResponseWriter, r *http.Request
|
|||||||
func (app *application) userSignupPost(w http.ResponseWriter, r *http.Request) {
|
func (app *application) userSignupPost(w http.ResponseWriter, r *http.Request) {
|
||||||
var form userSignupForm
|
var form userSignupForm
|
||||||
err := app.decodePostForm(r, &form)
|
err := app.decodePostForm(r, &form)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.clientError(w, http.StatusBadRequest)
|
app.notFound(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gitea.local.lab/Lbenedar/snippetbox/internal/assert"
|
"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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"html"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -15,6 +18,18 @@ import (
|
|||||||
"github.com/go-playground/form/v4"
|
"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 {
|
func newTestApplication(t *testing.T) *application {
|
||||||
templateCache, err := newTemplateCache()
|
templateCache, err := newTemplateCache()
|
||||||
if err != nil {
|
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)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,3 +20,11 @@ func StringContains(t *testing.T, actual, expectedString string) {
|
|||||||
t.Errorf("got: %q; expected to contain: %q", actual, expectedString)
|
t.Errorf("got: %q; expected to contain: %q", actual, expectedString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NilError(t *testing.T, actual error) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if actual != nil {
|
||||||
|
t.Errorf("got: %v; expected: nil", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
26
internal/models/testdata/setup.sql
vendored
Normal file
26
internal/models/testdata/setup.sql
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
CREATE TABLE snippets (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
title VARCHAR(100) NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
created DATETIME NOT NULL,
|
||||||
|
expires DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_snippets_created ON snippets(created);
|
||||||
|
|
||||||
|
CREATE TABLE users (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
email VARCHAR(255) NOT NULL,
|
||||||
|
hashed_password CHAR(60) NOT NULL,
|
||||||
|
created DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE users ADD CONSTRAINT users_uc_email UNIQUE (email);
|
||||||
|
|
||||||
|
INSERT INTO users (name, email, hashed_password, created) VALUES (
|
||||||
|
'Alice Jones',
|
||||||
|
'alice@example.com',
|
||||||
|
'$2a$12$NuTjWXm3KKntReFwyBVHyuf/to.HEwTy.eS206TNfkGfr6HzGJSWG',
|
||||||
|
'2022-01-01 10:00:00'
|
||||||
|
);
|
||||||
3
internal/models/testdata/teardown.sql
vendored
Normal file
3
internal/models/testdata/teardown.sql
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
DROP TABLE users;
|
||||||
|
|
||||||
|
DROP TABLE snippets;
|
||||||
37
internal/models/testutils_test.go
Normal file
37
internal/models/testutils_test.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTestDB(t *testing.T) *sql.DB {
|
||||||
|
db, err := sql.Open("mysql", "test_web:pass@/test_snippetbox?parseTime=true&multiStatements=true")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
script, err := os.ReadFile("./testdata/setup.sql")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = db.Exec(string(script))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
script, err := os.ReadFile("./testdata/teardown.sql")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = db.Exec(string(script))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Close()
|
||||||
|
})
|
||||||
|
return db
|
||||||
|
}
|
||||||
45
internal/models/users_test.go
Normal file
45
internal/models/users_test.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.local.lab/Lbenedar/snippetbox/internal/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserModelExists(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("models: skipping integration test")
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
userID int
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid ID",
|
||||||
|
userID: 1,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Zero ID",
|
||||||
|
userID: 0,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Non-existent ID",
|
||||||
|
userID: 2,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
db := newTestDB(t)
|
||||||
|
m := UserModel{DB: db}
|
||||||
|
|
||||||
|
exists, err := m.Exists(tt.userID)
|
||||||
|
assert.Equal(t, exists, tt.want)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user