summaryrefslogtreecommitdiff
path: root/internal/auth/handlers_test.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-01-20 15:18:57 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-01-20 15:18:57 -1000
commit78e8f597ff28f1b8406f5cfbf934adc22abdf85b (patch)
treef3b7dfff2c460e2d8752b61c131e80a73fa6b08d /internal/auth/handlers_test.go
parent08bbcf18b1207153983261652b4a43a9b36f386c (diff)
Add CSRF protection and auth unit tests
Add CSRF token middleware for state-changing request protection, integrate tokens into templates and HTMX headers, and add unit tests for authentication service and handlers. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal/auth/handlers_test.go')
-rw-r--r--internal/auth/handlers_test.go106
1 files changed, 106 insertions, 0 deletions
diff --git a/internal/auth/handlers_test.go b/internal/auth/handlers_test.go
new file mode 100644
index 0000000..3e154ce
--- /dev/null
+++ b/internal/auth/handlers_test.go
@@ -0,0 +1,106 @@
+package auth
+
+import (
+ "database/sql"
+ "html/template"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/DATA-DOG/go-sqlmock"
+ "github.com/alexedwards/scs/v2"
+ "golang.org/x/crypto/bcrypt"
+)
+
+func TestHandleLogin(t *testing.T) {
+ db, mock, err := sqlmock.New()
+ if err != nil {
+ t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
+ }
+ defer db.Close()
+
+ service := NewService(db)
+ sessionManager := scs.New()
+ templates := template.Must(template.New("login.html").Parse("{{.Error}}"))
+
+ handlers := NewHandlers(service, sessionManager, templates)
+
+ // Setup mock user
+ password := "password"
+ hash, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+ rows := sqlmock.NewRows([]string{"id", "username", "password_hash", "created_at"}).
+ AddRow(1, "testuser", string(hash), time.Now())
+
+ mock.ExpectQuery("SELECT id, username, password_hash, created_at FROM users WHERE username = ?").
+ WithArgs("testuser").
+ WillReturnRows(rows)
+
+ // Create request
+ form := url.Values{}
+ form.Add("username", "testuser")
+ form.Add("password", "password")
+ req := httptest.NewRequest("POST", "/login", strings.NewReader(form.Encode()))
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+ // Wrap request with session middleware
+ ctx, _ := sessionManager.Load(req.Context(), "")
+ req = req.WithContext(ctx)
+
+ rr := httptest.NewRecorder()
+
+ handlers.HandleLogin(rr, req)
+
+ if status := rr.Code; status != http.StatusSeeOther {
+ t.Errorf("handler returned wrong status code: got %v want %v",
+ status, http.StatusSeeOther)
+ }
+
+ if err := mock.ExpectationsWereMet(); err != nil {
+ t.Errorf("there were unfulfilled expectations: %s", err)
+ }
+}
+
+func TestHandleLogin_InvalidCredentials(t *testing.T) {
+ db, mock, err := sqlmock.New()
+ if err != nil {
+ t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
+ }
+ defer db.Close()
+
+ service := NewService(db)
+ sessionManager := scs.New()
+ templates := template.Must(template.New("login.html").Parse("{{.Error}}"))
+
+ handlers := NewHandlers(service, sessionManager, templates)
+
+ mock.ExpectQuery("SELECT id, username, password_hash, created_at FROM users WHERE username = ?").
+ WithArgs("testuser").
+ WillReturnError(sql.ErrNoRows)
+
+ // Create request
+ form := url.Values{}
+ form.Add("username", "testuser")
+ form.Add("password", "wrongpassword")
+ req := httptest.NewRequest("POST", "/login", strings.NewReader(form.Encode()))
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+ // Wrap request with session middleware
+ ctx, _ := sessionManager.Load(req.Context(), "")
+ req = req.WithContext(ctx)
+
+ rr := httptest.NewRecorder()
+
+ handlers.HandleLogin(rr, req)
+
+ if status := rr.Code; status != http.StatusUnauthorized {
+ t.Errorf("handler returned wrong status code: got %v want %v",
+ status, http.StatusUnauthorized)
+ }
+
+ if err := mock.ExpectationsWereMet(); err != nil {
+ t.Errorf("there were unfulfilled expectations: %s", err)
+ }
+}