Pulse/internal/api/magic_link_test.go
2026-03-18 16:06:30 +00:00

117 lines
3.3 KiB
Go

package api
import (
"errors"
"testing"
"time"
)
func TestMagicLinkTokenRoundTrip(t *testing.T) {
svc := NewMagicLinkServiceWithKey([]byte("0123456789abcdef0123456789abcdef"), nil, nil, nil)
t.Cleanup(func() { svc.Stop() })
token, err := svc.GenerateToken("User@Example.com", "org-123")
if err != nil {
t.Fatalf("GenerateToken error: %v", err)
}
got, err := svc.ValidateToken(token)
if err != nil {
t.Fatalf("ValidateToken error: %v", err)
}
if got.Email != "user@example.com" {
t.Fatalf("Email = %q, want user@example.com", got.Email)
}
if got.OrgID != "org-123" {
t.Fatalf("OrgID = %q, want org-123", got.OrgID)
}
if !got.Used {
t.Fatalf("Used = false, want true")
}
if got.Token != token {
t.Fatalf("Token mismatch")
}
}
func TestMagicLinkExpiredTokenRejected(t *testing.T) {
svc := NewMagicLinkServiceWithKey([]byte("0123456789abcdef0123456789abcdef"), nil, nil, nil)
t.Cleanup(func() { svc.Stop() })
base := time.Date(2026, 2, 10, 12, 0, 0, 0, time.UTC)
svc.now = func() time.Time { return base }
token, err := svc.GenerateToken("user@example.com", "org-123")
if err != nil {
t.Fatalf("GenerateToken error: %v", err)
}
svc.now = func() time.Time { return base.Add(16 * time.Minute) }
_, err = svc.ValidateToken(token)
if !errors.Is(err, ErrMagicLinkExpired) {
t.Fatalf("ValidateToken err = %v, want %v", err, ErrMagicLinkExpired)
}
}
func TestMagicLinkUsedTokenRejected(t *testing.T) {
svc := NewMagicLinkServiceWithKey([]byte("0123456789abcdef0123456789abcdef"), nil, nil, nil)
t.Cleanup(func() { svc.Stop() })
token, err := svc.GenerateToken("user@example.com", "org-123")
if err != nil {
t.Fatalf("GenerateToken error: %v", err)
}
if _, err := svc.ValidateToken(token); err != nil {
t.Fatalf("first ValidateToken error: %v", err)
}
_, err = svc.ValidateToken(token)
if !errors.Is(err, ErrMagicLinkUsed) {
t.Fatalf("second ValidateToken err = %v, want %v", err, ErrMagicLinkUsed)
}
}
func TestMagicLinkInvalidTokenRejected(t *testing.T) {
svc := NewMagicLinkServiceWithKey([]byte("0123456789abcdef0123456789abcdef"), nil, nil, nil)
t.Cleanup(func() { svc.Stop() })
token, err := svc.GenerateToken("user@example.com", "org-123")
if err != nil {
t.Fatalf("GenerateToken error: %v", err)
}
bad := token + "x"
_, err = svc.ValidateToken(bad)
if !errors.Is(err, ErrMagicLinkInvalidToken) {
t.Fatalf("ValidateToken err = %v, want %v", err, ErrMagicLinkInvalidToken)
}
}
func TestMagicLinkRateLimiting(t *testing.T) {
limiter := NewRateLimiter(3, 1*time.Hour)
svc := NewMagicLinkServiceWithKey([]byte("0123456789abcdef0123456789abcdef"), nil, nil, limiter)
t.Cleanup(func() { svc.Stop() })
email := "user@example.com"
if !svc.AllowRequest(email) {
t.Fatalf("1st request unexpectedly blocked")
}
if !svc.AllowRequest(email) {
t.Fatalf("2nd request unexpectedly blocked")
}
if !svc.AllowRequest(email) {
t.Fatalf("3rd request unexpectedly blocked")
}
if svc.AllowRequest(email) {
t.Fatalf("4th request allowed, expected rate limited")
}
}
func TestMagicLinkGenerateTokenRejectsInvalidOrgID(t *testing.T) {
svc := NewMagicLinkServiceWithKey([]byte("0123456789abcdef0123456789abcdef"), nil, nil, nil)
t.Cleanup(func() { svc.Stop() })
if _, err := svc.GenerateToken("user@example.com", "../org"); err == nil {
t.Fatalf("expected invalid orgID to be rejected")
}
}