package main import ( "net/http" "net/http/httptest" "testing" "time" ) func TestSessionManager(t *testing.T) { sm := NewSessionManager("test-secret") t.Run("create and get session", func(t *testing.T) { sess := sm.Create("admin") if sess.Username != "admin" { t.Errorf("username = %q, want admin", sess.Username) } if sess.Token == "" { t.Fatal("token is empty") } got := sm.Get(sess.Token) if got == nil { t.Fatal("session not found") } if got.Username != "admin" { t.Errorf("username = %q, want admin", got.Username) } }) t.Run("get nonexistent session", func(t *testing.T) { got := sm.Get("nonexistent") if got != nil { t.Error("expected nil for nonexistent session") } }) t.Run("delete session", func(t *testing.T) { sess := sm.Create("user1") sm.Delete(sess.Token) got := sm.Get(sess.Token) if got != nil { t.Error("expected nil after delete") } }) t.Run("expired session", func(t *testing.T) { sess := sm.Create("user2") // Manually set old creation time sm.mu.Lock() sm.sessions[sess.Token].CreatedAt = time.Now().Add(-25 * time.Hour) sm.mu.Unlock() got := sm.Get(sess.Token) if got != nil { t.Error("expected nil for expired session") } }) t.Run("unique tokens", func(t *testing.T) { s1 := sm.Create("a") s2 := sm.Create("b") if s1.Token == s2.Token { t.Error("tokens should be unique") } }) } func TestAuthMiddleware(t *testing.T) { sm := NewSessionManager("test-secret") sess := sm.Create("admin") protected := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) }) handler := AuthMiddleware(sm, protected) t.Run("no cookie redirects to login", func(t *testing.T) { req := httptest.NewRequest("GET", "/projects", nil) w := httptest.NewRecorder() handler.ServeHTTP(w, req) if w.Code != http.StatusSeeOther { t.Errorf("status = %d, want %d", w.Code, http.StatusSeeOther) } loc := w.Header().Get("Location") if loc != "/login" { t.Errorf("location = %q, want /login", loc) } }) t.Run("invalid cookie redirects to login", func(t *testing.T) { req := httptest.NewRequest("GET", "/projects", nil) req.AddCookie(&http.Cookie{Name: sessionCookieName, Value: "invalid"}) w := httptest.NewRecorder() handler.ServeHTTP(w, req) if w.Code != http.StatusSeeOther { t.Errorf("status = %d, want %d", w.Code, http.StatusSeeOther) } }) t.Run("valid cookie passes through", func(t *testing.T) { req := httptest.NewRequest("GET", "/projects", nil) req.AddCookie(&http.Cookie{Name: sessionCookieName, Value: sess.Token}) w := httptest.NewRecorder() handler.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("status = %d, want %d", w.Code, http.StatusOK) } }) } func TestSetSessionCookie(t *testing.T) { sess := &Session{Token: "test-token", Username: "admin"} w := httptest.NewRecorder() SetSessionCookie(w, sess) cookies := w.Result().Cookies() if len(cookies) != 1 { t.Fatalf("expected 1 cookie, got %d", len(cookies)) } if cookies[0].Name != sessionCookieName { t.Errorf("cookie name = %q", cookies[0].Name) } if cookies[0].Value != "test-token" { t.Errorf("cookie value = %q", cookies[0].Value) } if !cookies[0].HttpOnly { t.Error("expected HttpOnly") } } func TestClearSessionCookie(t *testing.T) { w := httptest.NewRecorder() ClearSessionCookie(w) cookies := w.Result().Cookies() if len(cookies) != 1 { t.Fatalf("expected 1 cookie, got %d", len(cookies)) } if cookies[0].MaxAge != -1 { t.Errorf("MaxAge = %d, want -1", cookies[0].MaxAge) } }