package auth import ( "errors" "testing" "time" "github.com/google/uuid" ) const testSecret = "test-secret-must-be-at-least-32-bytes-long-xx" func TestJWTRoundTrip(t *testing.T) { s, err := NewJWTSigner(testSecret, 5*time.Minute, "guestguard-test") if err != nil { t.Fatalf("signer: %v", err) } uid := uuid.New() tok, exp, err := s.Issue(uid, time.Now()) if err != nil { t.Fatalf("issue: %v", err) } if tok == "" { t.Fatal("empty token") } if time.Until(exp) <= 0 { t.Fatalf("expiry in past: %v", exp) } claims, err := s.Parse(tok) if err != nil { t.Fatalf("parse: %v", err) } if claims.UserID != uid { t.Fatalf("user mismatch: got %s want %s", claims.UserID, uid) } } func TestJWTExpired(t *testing.T) { s, err := NewJWTSigner(testSecret, 1*time.Second, "guestguard-test") if err != nil { t.Fatalf("signer: %v", err) } tok, _, err := s.Issue(uuid.New(), time.Now().Add(-1*time.Hour)) if err != nil { t.Fatalf("issue: %v", err) } if _, err := s.Parse(tok); !errors.Is(err, ErrExpiredJWT) { t.Fatalf("expected ErrExpiredJWT, got %v", err) } } func TestJWTTamper(t *testing.T) { s, err := NewJWTSigner(testSecret, 5*time.Minute, "guestguard-test") if err != nil { t.Fatalf("signer: %v", err) } tok, _, _ := s.Issue(uuid.New(), time.Now()) // Flip a character in the signature segment. tampered := tok[:len(tok)-1] + "a" if tampered == tok { tampered = tok[:len(tok)-1] + "b" } if _, err := s.Parse(tampered); !errors.Is(err, ErrInvalidJWT) { t.Fatalf("expected ErrInvalidJWT, got %v", err) } } func TestJWTSecretTooShort(t *testing.T) { if _, err := NewJWTSigner("short", time.Minute, "x"); err == nil { t.Fatal("expected error for short secret") } } func TestOpaqueTokenHashStable(t *testing.T) { raw, hash, err := NewOpaqueToken() if err != nil { t.Fatalf("mint: %v", err) } if got := HashOpaque(raw); got != hash { t.Fatalf("hash mismatch: got %s want %s", got, hash) } }