package auth import ( "crypto/sha256" "encoding/base64" "net/http" "net/http/httptest" "strings" "testing" "golang.org/x/crypto/bcrypt" ) func TestMintSessionToken_ReturnsUrlSafeBase64(t *testing.T) { token, err := MintSessionToken() if err != nil { t.Fatalf("MintSessionToken: %v", err) } if len(token) < 40 { t.Errorf("token length = %d, want >= 40 (32B base64 url-safe)", len(token)) } if strings.ContainsAny(token, "+/=") { t.Errorf("token %q contains non-url-safe chars", token) } } func TestHashSessionToken_IsDeterministicSHA256(t *testing.T) { token := "test-token-xyz" want := sha256.Sum256([]byte(token)) got := HashSessionToken(token) if len(got) != sha256.Size { t.Fatalf("hash length = %d, want %d", len(got), sha256.Size) } if base64.StdEncoding.EncodeToString(got) != base64.StdEncoding.EncodeToString(want[:]) { t.Errorf("hash = %x, want %x", got, want) } } func TestVerifyPassword(t *testing.T) { hash, err := bcrypt.GenerateFromPassword([]byte("hunter2"), bcrypt.MinCost) if err != nil { t.Fatalf("GenerateFromPassword: %v", err) } if !VerifyPassword(string(hash), "hunter2") { t.Error("correct password rejected") } if VerifyPassword(string(hash), "wrong") { t.Error("wrong password accepted") } if VerifyPassword("not-a-hash", "hunter2") { t.Error("malformed hash accepted") } } func TestRequireUser_RejectsWhenNoCookieOrBearer(t *testing.T) { next := http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { t.Fatal("handler must not be called") }) h := RequireUser(nil)(next) req := httptest.NewRequest(http.MethodGet, "/api/me", nil) w := httptest.NewRecorder() h.ServeHTTP(w, req) if w.Code != http.StatusUnauthorized { t.Errorf("status = %d, want 401", w.Code) } } func TestExtractBearerToken(t *testing.T) { cases := []struct { header string want string }{ {"", ""}, {"Bearer abc", "abc"}, {"bearer abc", "abc"}, {"Token abc", ""}, {"Bearer", ""}, {"Bearer whitespace-token ", "whitespace-token"}, } for _, c := range cases { got := extractBearerToken(c.header) if got != c.want { t.Errorf("extractBearerToken(%q) = %q, want %q", c.header, got, c.want) } } }