1
0
mirror of https://github.com/redis/go-redis.git synced 2025-09-07 07:47:24 +03:00
Files
go-redis/push/processor_unit_test.go
Nedyalko Dyakov 5649ffb314 feat(hitless): Introduce handlers for hitless upgrades
This commit includes all the work on hitless upgrades with the addition
of:

- Pubsub Pool
- Examples
- Refactor of push
- Refactor of pool (using atomics for most things)
- Introducing of hooks in pool
2025-08-18 22:14:06 +03:00

316 lines
8.9 KiB
Go

package push
import (
"context"
"testing"
)
// TestProcessorCreation tests processor creation and initialization
func TestProcessorCreation(t *testing.T) {
t.Run("NewProcessor", func(t *testing.T) {
processor := NewProcessor()
if processor == nil {
t.Fatal("NewProcessor should not return nil")
}
if processor.registry == nil {
t.Error("Processor should have a registry")
}
})
t.Run("NewVoidProcessor", func(t *testing.T) {
voidProcessor := NewVoidProcessor()
if voidProcessor == nil {
t.Fatal("NewVoidProcessor should not return nil")
}
})
}
// TestProcessorHandlerManagement tests handler registration and retrieval
func TestProcessorHandlerManagement(t *testing.T) {
processor := NewProcessor()
handler := &UnitTestHandler{name: "test-handler"}
t.Run("RegisterHandler", func(t *testing.T) {
err := processor.RegisterHandler("TEST", handler, false)
if err != nil {
t.Errorf("RegisterHandler should not error: %v", err)
}
// Verify handler is registered
retrievedHandler := processor.GetHandler("TEST")
if retrievedHandler != handler {
t.Error("GetHandler should return the registered handler")
}
})
t.Run("RegisterProtectedHandler", func(t *testing.T) {
protectedHandler := &UnitTestHandler{name: "protected-handler"}
err := processor.RegisterHandler("PROTECTED", protectedHandler, true)
if err != nil {
t.Errorf("RegisterHandler should not error for protected handler: %v", err)
}
// Verify handler is registered
retrievedHandler := processor.GetHandler("PROTECTED")
if retrievedHandler != protectedHandler {
t.Error("GetHandler should return the protected handler")
}
})
t.Run("GetNonExistentHandler", func(t *testing.T) {
handler := processor.GetHandler("NONEXISTENT")
if handler != nil {
t.Error("GetHandler should return nil for non-existent handler")
}
})
t.Run("UnregisterHandler", func(t *testing.T) {
err := processor.UnregisterHandler("TEST")
if err != nil {
t.Errorf("UnregisterHandler should not error: %v", err)
}
// Verify handler is removed
retrievedHandler := processor.GetHandler("TEST")
if retrievedHandler != nil {
t.Error("GetHandler should return nil after unregistering")
}
})
t.Run("UnregisterProtectedHandler", func(t *testing.T) {
err := processor.UnregisterHandler("PROTECTED")
if err == nil {
t.Error("UnregisterHandler should error for protected handler")
}
// Verify handler is still there
retrievedHandler := processor.GetHandler("PROTECTED")
if retrievedHandler == nil {
t.Error("Protected handler should not be removed")
}
})
}
// TestVoidProcessorBehavior tests void processor behavior
func TestVoidProcessorBehavior(t *testing.T) {
voidProcessor := NewVoidProcessor()
handler := &UnitTestHandler{name: "test-handler"}
t.Run("GetHandler", func(t *testing.T) {
retrievedHandler := voidProcessor.GetHandler("ANY")
if retrievedHandler != nil {
t.Error("VoidProcessor GetHandler should always return nil")
}
})
t.Run("RegisterHandler", func(t *testing.T) {
err := voidProcessor.RegisterHandler("TEST", handler, false)
if err == nil {
t.Error("VoidProcessor RegisterHandler should return error")
}
// Check error type
if !IsVoidProcessorError(err) {
t.Error("Error should be a VoidProcessorError")
}
})
t.Run("UnregisterHandler", func(t *testing.T) {
err := voidProcessor.UnregisterHandler("TEST")
if err == nil {
t.Error("VoidProcessor UnregisterHandler should return error")
}
// Check error type
if !IsVoidProcessorError(err) {
t.Error("Error should be a VoidProcessorError")
}
})
}
// TestProcessPendingNotificationsNilReader tests handling of nil reader
func TestProcessPendingNotificationsNilReader(t *testing.T) {
t.Run("ProcessorWithNilReader", func(t *testing.T) {
processor := NewProcessor()
ctx := context.Background()
handlerCtx := NotificationHandlerContext{}
err := processor.ProcessPendingNotifications(ctx, handlerCtx, nil)
if err != nil {
t.Errorf("ProcessPendingNotifications should not error with nil reader: %v", err)
}
})
t.Run("VoidProcessorWithNilReader", func(t *testing.T) {
voidProcessor := NewVoidProcessor()
ctx := context.Background()
handlerCtx := NotificationHandlerContext{}
err := voidProcessor.ProcessPendingNotifications(ctx, handlerCtx, nil)
if err != nil {
t.Errorf("VoidProcessor ProcessPendingNotifications should not error with nil reader: %v", err)
}
})
}
// TestWillHandleNotificationInClient tests the notification filtering logic
func TestWillHandleNotificationInClient(t *testing.T) {
testCases := []struct {
name string
notificationType string
shouldHandle bool
}{
// Pub/Sub notifications (should be handled in client)
{"message", "message", true},
{"pmessage", "pmessage", true},
{"subscribe", "subscribe", true},
{"unsubscribe", "unsubscribe", true},
{"psubscribe", "psubscribe", true},
{"punsubscribe", "punsubscribe", true},
{"smessage", "smessage", true},
{"ssubscribe", "ssubscribe", true},
{"sunsubscribe", "sunsubscribe", true},
// Push notifications (should be handled by processor)
{"MOVING", "MOVING", false},
{"MIGRATING", "MIGRATING", false},
{"MIGRATED", "MIGRATED", false},
{"FAILING_OVER", "FAILING_OVER", false},
{"FAILED_OVER", "FAILED_OVER", false},
{"custom", "custom", false},
{"unknown", "unknown", false},
{"empty", "", false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := willHandleNotificationInClient(tc.notificationType)
if result != tc.shouldHandle {
t.Errorf("willHandleNotificationInClient(%q) = %v, want %v", tc.notificationType, result, tc.shouldHandle)
}
})
}
}
// TestProcessorErrorHandlingUnit tests error handling scenarios
func TestProcessorErrorHandlingUnit(t *testing.T) {
processor := NewProcessor()
t.Run("RegisterNilHandler", func(t *testing.T) {
err := processor.RegisterHandler("TEST", nil, false)
if err == nil {
t.Error("RegisterHandler should error with nil handler")
}
// Check error type
if !IsHandlerNilError(err) {
t.Error("Error should be a HandlerNilError")
}
})
t.Run("RegisterDuplicateHandler", func(t *testing.T) {
handler1 := &UnitTestHandler{name: "handler1"}
handler2 := &UnitTestHandler{name: "handler2"}
// Register first handler
err := processor.RegisterHandler("DUPLICATE", handler1, false)
if err != nil {
t.Errorf("First RegisterHandler should not error: %v", err)
}
// Try to register second handler with same name
err = processor.RegisterHandler("DUPLICATE", handler2, false)
if err == nil {
t.Error("RegisterHandler should error when registering duplicate handler")
}
// Verify original handler is still there
retrievedHandler := processor.GetHandler("DUPLICATE")
if retrievedHandler != handler1 {
t.Error("Original handler should remain after failed duplicate registration")
}
})
t.Run("UnregisterNonExistentHandler", func(t *testing.T) {
err := processor.UnregisterHandler("NONEXISTENT")
if err != nil {
t.Errorf("UnregisterHandler should not error for non-existent handler: %v", err)
}
})
}
// TestProcessorConcurrentAccess tests concurrent access to processor
func TestProcessorConcurrentAccess(t *testing.T) {
processor := NewProcessor()
t.Run("ConcurrentRegisterAndGet", func(t *testing.T) {
done := make(chan bool, 2)
// Goroutine 1: Register handlers
go func() {
defer func() { done <- true }()
for i := 0; i < 100; i++ {
handler := &UnitTestHandler{name: "concurrent-handler"}
processor.RegisterHandler("CONCURRENT", handler, false)
processor.UnregisterHandler("CONCURRENT")
}
}()
// Goroutine 2: Get handlers
go func() {
defer func() { done <- true }()
for i := 0; i < 100; i++ {
processor.GetHandler("CONCURRENT")
}
}()
// Wait for both goroutines to complete
<-done
<-done
})
}
// TestProcessorInterfaceCompliance tests interface compliance
func TestProcessorInterfaceCompliance(t *testing.T) {
t.Run("ProcessorImplementsInterface", func(t *testing.T) {
var _ NotificationProcessor = (*Processor)(nil)
})
t.Run("VoidProcessorImplementsInterface", func(t *testing.T) {
var _ NotificationProcessor = (*VoidProcessor)(nil)
})
}
// UnitTestHandler is a test implementation of NotificationHandler
type UnitTestHandler struct {
name string
lastNotification []interface{}
errorToReturn error
callCount int
}
func (h *UnitTestHandler) HandlePushNotification(ctx context.Context, handlerCtx NotificationHandlerContext, notification []interface{}) error {
h.callCount++
h.lastNotification = notification
return h.errorToReturn
}
// Helper methods for UnitTestHandler
func (h *UnitTestHandler) GetCallCount() int {
return h.callCount
}
func (h *UnitTestHandler) GetLastNotification() []interface{} {
return h.lastNotification
}
func (h *UnitTestHandler) SetErrorToReturn(err error) {
h.errorToReturn = err
}
func (h *UnitTestHandler) Reset() {
h.callCount = 0
h.lastNotification = nil
h.errorToReturn = nil
}