ch14.5
This commit is contained in:
@@ -18,3 +18,61 @@ func TestPing(t *testing.T) {
|
|||||||
assert.Equal(t, statusCode, http.StatusOK)
|
assert.Equal(t, statusCode, http.StatusOK)
|
||||||
assert.Equal(t, body, "OK")
|
assert.Equal(t, body, "OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSnippetView(t *testing.T) {
|
||||||
|
app := newTestApplication(t)
|
||||||
|
|
||||||
|
ts := newTestServer(t, app.routes())
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
urlPath string
|
||||||
|
wantCode int
|
||||||
|
wantBody string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid ID",
|
||||||
|
urlPath: "/snippet/view/1",
|
||||||
|
wantCode: http.StatusOK,
|
||||||
|
wantBody: "An old silent pond...",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Non-existend ID",
|
||||||
|
urlPath: "/snippet/view/2",
|
||||||
|
wantCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Negative ID",
|
||||||
|
urlPath: "/snippet/view/-1",
|
||||||
|
wantCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Decimal ID",
|
||||||
|
urlPath: "/snippet/view/1.23",
|
||||||
|
wantCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "String ID",
|
||||||
|
urlPath: "/snippet/view/foo",
|
||||||
|
wantCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty ID",
|
||||||
|
urlPath: "/snippet/view/",
|
||||||
|
wantCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
code, _, body := ts.get(t, tt.urlPath)
|
||||||
|
|
||||||
|
assert.Equal(t, code, tt.wantCode)
|
||||||
|
|
||||||
|
if tt.wantBody != "" {
|
||||||
|
assert.StringContains(t, body, tt.wantBody)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import (
|
|||||||
type application struct {
|
type application struct {
|
||||||
errorLog *log.Logger
|
errorLog *log.Logger
|
||||||
infoLog *log.Logger
|
infoLog *log.Logger
|
||||||
snippets *models.SnippetModel
|
snippets models.SnippetModelInterface
|
||||||
users *models.UserModel
|
users models.UserModelInterface
|
||||||
templateCache map[string]*template.Template
|
templateCache map[string]*template.Template
|
||||||
formDecoder *form.Decoder
|
formDecoder *form.Decoder
|
||||||
sessionManager *scs.SessionManager
|
sessionManager *scs.SessionManager
|
||||||
|
|||||||
@@ -8,12 +8,33 @@ import (
|
|||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.local.lab/Lbenedar/snippetbox/internal/models/mocks"
|
||||||
|
"github.com/alexedwards/scs/v2"
|
||||||
|
"github.com/go-playground/form/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTestApplication(t *testing.T) *application {
|
func newTestApplication(t *testing.T) *application {
|
||||||
|
templateCache, err := newTemplateCache()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
formDecoder := form.NewDecoder()
|
||||||
|
|
||||||
|
sessionManager := scs.New()
|
||||||
|
sessionManager.Lifetime = 12 * time.Hour
|
||||||
|
sessionManager.Cookie.Secure = true
|
||||||
|
|
||||||
return &application{
|
return &application{
|
||||||
errorLog: log.New(io.Discard, "", 0),
|
errorLog: log.New(io.Discard, "", 0),
|
||||||
infoLog: log.New(io.Discard, "", 0),
|
infoLog: log.New(io.Discard, "", 0),
|
||||||
|
snippets: &mocks.SnippetModel{},
|
||||||
|
users: &mocks.UserModel{},
|
||||||
|
templateCache: templateCache,
|
||||||
|
formDecoder: formDecoder,
|
||||||
|
sessionManager: sessionManager,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package assert
|
package assert
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func Equal[T comparable](t *testing.T, actual, expected T) {
|
func Equal[T comparable](t *testing.T, actual, expected T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -9,3 +12,11 @@ func Equal[T comparable](t *testing.T, actual, expected T) {
|
|||||||
t.Errorf("got: %v; want: %v", actual, expected)
|
t.Errorf("got: %v; want: %v", actual, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StringContains(t *testing.T, actual, expectedString string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if !strings.Contains(actual, expectedString) {
|
||||||
|
t.Errorf("got: %q; expected to contain: %q", actual, expectedString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
34
internal/models/mocks/snippets.go
Normal file
34
internal/models/mocks/snippets.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.local.lab/Lbenedar/snippetbox/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mockSnippet = &models.Snippet{
|
||||||
|
ID: 1,
|
||||||
|
Title: "An old silent pond",
|
||||||
|
Content: "An old silent pond...",
|
||||||
|
Created: time.Now(),
|
||||||
|
Expires: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnippetModel struct{}
|
||||||
|
|
||||||
|
func (m *SnippetModel) Insert(title string, content string, expires int) (int, error) {
|
||||||
|
return 2, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SnippetModel) Get(id int) (*models.Snippet, error) {
|
||||||
|
switch id {
|
||||||
|
case 1:
|
||||||
|
return mockSnippet, nil
|
||||||
|
default:
|
||||||
|
return nil, models.ErrNoRecord
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SnippetModel) Latest() ([]*models.Snippet, error) {
|
||||||
|
return []*models.Snippet{mockSnippet}, nil
|
||||||
|
}
|
||||||
31
internal/models/mocks/users.go
Normal file
31
internal/models/mocks/users.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package mocks
|
||||||
|
|
||||||
|
import "gitea.local.lab/Lbenedar/snippetbox/internal/models"
|
||||||
|
|
||||||
|
type UserModel struct{}
|
||||||
|
|
||||||
|
func (m *UserModel) Insert(name, email, password string) error {
|
||||||
|
switch email {
|
||||||
|
case "dupe@example.com":
|
||||||
|
return models.ErrDuplicateEmail
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *UserModel) Authenticate(email, password string) (int, error) {
|
||||||
|
if email == "alice@example.com" && password == "pa$$word" {
|
||||||
|
return 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, models.ErrInvalidCredentials
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *UserModel) Exists(id int) (bool, error) {
|
||||||
|
switch id {
|
||||||
|
case 1:
|
||||||
|
return true, nil
|
||||||
|
default:
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,11 @@ type Snippet struct {
|
|||||||
Created time.Time
|
Created time.Time
|
||||||
Expires time.Time
|
Expires time.Time
|
||||||
}
|
}
|
||||||
|
type SnippetModelInterface interface {
|
||||||
|
Insert(title string, content string, expires int) (int, error)
|
||||||
|
Get(id int) (*Snippet, error)
|
||||||
|
Latest() ([]*Snippet, error)
|
||||||
|
}
|
||||||
type SnippetModel struct {
|
type SnippetModel struct {
|
||||||
DB *sql.DB
|
DB *sql.DB
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ type User struct {
|
|||||||
Created time.Time
|
Created time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserModelInterface interface {
|
||||||
|
Insert(name, email, password string) error
|
||||||
|
Authenticate(email, password string) (int, error)
|
||||||
|
Exists(id int) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
type UserModel struct {
|
type UserModel struct {
|
||||||
DB *sql.DB
|
DB *sql.DB
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user