xtablo-source/go-backend/internal/web/handlers/auth_test.go
2026-05-10 23:14:47 +02:00

181 lines
5.1 KiB
Go

package handlers
import (
"context"
"bytes"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"golang.org/x/crypto/bcrypt"
)
func TestSignupLogsAuthStoreMutations(t *testing.T) {
var buf bytes.Buffer
restore := log.Logger
log.Logger = zerolog.New(&buf)
defer func() {
log.Logger = restore
}()
handler := newTestAuthHandler(t)
form := url.Values{}
form.Set("email", "new@xtablo.com")
form.Set("password", "xtablo-secret")
req := httptest.NewRequest(http.MethodPost, "/signup", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rec := httptest.NewRecorder()
handler.PostSignup().ServeHTTP(rec, req)
output := buf.String()
for _, want := range []string{
`"action":"create_user"`,
`"email":"new@xtablo.com"`,
`"action":"create_session"`,
`"session_id":"`,
} {
if !strings.Contains(output, want) {
t.Fatalf("expected log output to contain %q, got %q", want, output)
}
}
}
func TestSignupHashesPasswordBeforeStoringUser(t *testing.T) {
repo := NewInMemoryAuthRepository()
handler := NewAuthHandler(repo)
form := url.Values{}
form.Set("email", "new@xtablo.com")
form.Set("password", "xtablo-secret")
req := httptest.NewRequest(http.MethodPost, "/signup", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rec := httptest.NewRecorder()
handler.PostSignup().ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
storedUser, err := repo.GetAuthUserByEmail(req.Context(), "new@xtablo.com")
if err != nil {
t.Fatalf("expected stored user, got error %v", err)
}
if storedUser.EncryptedPassword == "xtablo-secret" {
t.Fatalf("expected stored password hash, got plaintext")
}
if bcrypt.CompareHashAndPassword([]byte(storedUser.EncryptedPassword), []byte("xtablo-secret")) != nil {
t.Fatalf("expected stored password to match bcrypt hash")
}
}
func TestLogoutLogsSessionDeletion(t *testing.T) {
var buf bytes.Buffer
restore := log.Logger
log.Logger = zerolog.New(&buf)
defer func() {
log.Logger = restore
}()
handler := newTestAuthHandler(t)
loginForm := url.Values{}
loginForm.Set("email", "demo@xtablo.com")
loginForm.Set("password", "xtablo-demo")
loginReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(loginForm.Encode()))
loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
loginRec := httptest.NewRecorder()
handler.PostLogin().ServeHTTP(loginRec, loginReq)
sessionCookie := loginRec.Result().Cookies()[0]
logoutReq := httptest.NewRequest(http.MethodPost, "/logout", nil)
logoutReq.AddCookie(sessionCookie)
logoutRec := httptest.NewRecorder()
handler.PostLogout().ServeHTTP(logoutRec, logoutReq)
output := buf.String()
for _, want := range []string{
`"action":"delete_session"`,
`"email":"demo@xtablo.com"`,
`"session_id":"`,
} {
if !strings.Contains(output, want) {
t.Fatalf("expected log output to contain %q, got %q", want, output)
}
}
}
func TestTasksPageSidebarShowsRealTablos(t *testing.T) {
repo := NewInMemoryAuthRepository()
handler := NewAuthHandler(repo)
sessionCookie := loginTestUser(t, handler, "demo@xtablo.com", "xtablo-demo")
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.AddCookie(sessionCookie)
userID, ok := handler.currentUserID(req.Context(), req)
if !ok {
t.Fatal("expected user session")
}
for _, tablo := range []CreateTabloInput{
{OwnerID: userID, Name: "Blue", Color: "#3B82F6", Status: TabloStatusTodo},
{OwnerID: userID, Name: "Green", Color: "#22C55E", Status: TabloStatusTodo},
{OwnerID: userID, Name: "Purple", Color: "#A855F7", Status: TabloStatusTodo},
{OwnerID: userID, Name: "Red", Color: "#EF4444", Status: TabloStatusTodo},
{OwnerID: userID, Name: "Hidden Fifth", Color: "#EAB308", Status: TabloStatusTodo},
} {
if _, err := repo.CreateTablo(context.Background(), tablo); err != nil {
t.Fatalf("create tablo %q: %v", tablo.Name, err)
}
}
pageReq := httptest.NewRequest(http.MethodGet, "/tasks", nil)
pageReq.AddCookie(sessionCookie)
rec := httptest.NewRecorder()
handler.GetTasksPage().ServeHTTP(rec, pageReq)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
body := rec.Body.String()
projectSection := body
if index := strings.Index(body, `id="sidebar-projects-section"`); index >= 0 {
projectSection = body[index:]
if end := strings.Index(projectSection, `<ul class="sidebar-list sidebar-footer-links"`); end >= 0 {
projectSection = projectSection[:end]
}
}
for _, want := range []string{
"Green",
"Purple",
"Red",
"Hidden Fifth",
} {
if !strings.Contains(projectSection, want) {
t.Fatalf("expected sidebar to contain %q, got %q", want, projectSection)
}
}
if strings.Contains(projectSection, "Blue") {
t.Fatalf("expected sidebar to limit to the four most recent tablos, got %q", projectSection)
}
if !strings.Contains(body, `href="/tablos/`) {
t.Fatalf("expected sidebar project links, got %q", body)
}
}
func newTestAuthHandler(t *testing.T) *AuthHandler {
t.Helper()
return NewAuthHandler(NewInMemoryAuthRepository())
}