mirror of
https://github.com/redis/go-redis.git
synced 2025-07-18 00:20:57 +03:00
feat: add protected handler support and rename command to pushNotificationName
- Add protected flag to RegisterHandler methods across all types - Protected handlers cannot be unregistered, UnregisterHandler returns error - Rename 'command' parameter to 'pushNotificationName' for clarity - Update PushNotificationInfo.Command field to Name field - Add comprehensive test for protected handler functionality - Update all existing tests to use new protected parameter (false by default) - Improve error messages to use 'push notification' terminology Benefits: - Critical handlers can be protected from accidental unregistration - Clearer naming reflects that these are notification names, not commands - Better error handling with informative error messages - Backward compatible (existing handlers work with protected=false)
This commit is contained in:
@ -118,7 +118,7 @@ func TestConnectionHealthCheckWithPushNotifications(t *testing.T) {
|
|||||||
// Register a handler to ensure processor is active
|
// Register a handler to ensure processor is active
|
||||||
err := client.RegisterPushNotificationHandler("TEST_HEALTH", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
err := client.RegisterPushNotificationHandler("TEST_HEALTH", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to register handler: %v", err)
|
t.Fatalf("Failed to register handler: %v", err)
|
||||||
}
|
}
|
||||||
@ -165,7 +165,7 @@ func TestConnPushNotificationMethods(t *testing.T) {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
err := conn.RegisterPushNotificationHandler("TEST_CONN_HANDLER", handler)
|
err := conn.RegisterPushNotificationHandler("TEST_CONN_HANDLER", handler, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Failed to register handler on Conn: %v", err)
|
t.Errorf("Failed to register handler on Conn: %v", err)
|
||||||
}
|
}
|
||||||
@ -173,13 +173,13 @@ func TestConnPushNotificationMethods(t *testing.T) {
|
|||||||
// Test RegisterPushNotificationHandler with function wrapper
|
// Test RegisterPushNotificationHandler with function wrapper
|
||||||
err = conn.RegisterPushNotificationHandler("TEST_CONN_FUNC", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
err = conn.RegisterPushNotificationHandler("TEST_CONN_FUNC", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Failed to register handler func on Conn: %v", err)
|
t.Errorf("Failed to register handler func on Conn: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test duplicate handler error
|
// Test duplicate handler error
|
||||||
err = conn.RegisterPushNotificationHandler("TEST_CONN_HANDLER", handler)
|
err = conn.RegisterPushNotificationHandler("TEST_CONN_HANDLER", handler, false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Should get error when registering duplicate handler")
|
t.Error("Should get error when registering duplicate handler")
|
||||||
}
|
}
|
||||||
@ -222,7 +222,7 @@ func TestConnWithoutPushNotifications(t *testing.T) {
|
|||||||
// Test RegisterPushNotificationHandler returns nil (no error)
|
// Test RegisterPushNotificationHandler returns nil (no error)
|
||||||
err := conn.RegisterPushNotificationHandler("TEST", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
err := conn.RegisterPushNotificationHandler("TEST", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Should return nil error when no processor: %v", err)
|
t.Errorf("Should return nil error when no processor: %v", err)
|
||||||
}
|
}
|
||||||
@ -230,7 +230,7 @@ func TestConnWithoutPushNotifications(t *testing.T) {
|
|||||||
// Test RegisterPushNotificationHandler returns nil (no error)
|
// Test RegisterPushNotificationHandler returns nil (no error)
|
||||||
err = conn.RegisterPushNotificationHandler("TEST", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
err = conn.RegisterPushNotificationHandler("TEST", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Should return nil error when no processor: %v", err)
|
t.Errorf("Should return nil error when no processor: %v", err)
|
||||||
}
|
}
|
||||||
@ -279,7 +279,7 @@ func TestClonedClientPushNotifications(t *testing.T) {
|
|||||||
// Register handler on original
|
// Register handler on original
|
||||||
err := client.RegisterPushNotificationHandler("TEST_CLONE", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
err := client.RegisterPushNotificationHandler("TEST_CLONE", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to register handler: %v", err)
|
t.Fatalf("Failed to register handler: %v", err)
|
||||||
}
|
}
|
||||||
@ -305,7 +305,7 @@ func TestClonedClientPushNotifications(t *testing.T) {
|
|||||||
// Test registering new handler on cloned client
|
// Test registering new handler on cloned client
|
||||||
err = clonedClient.RegisterPushNotificationHandler("TEST_CLONE_NEW", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
err = clonedClient.RegisterPushNotificationHandler("TEST_CLONE_NEW", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Failed to register handler on cloned client: %v", err)
|
t.Errorf("Failed to register handler on cloned client: %v", err)
|
||||||
}
|
}
|
||||||
@ -350,22 +350,22 @@ func TestPushNotificationInfoStructure(t *testing.T) {
|
|||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
info := ParsePushNotificationInfo(tc.notification)
|
info := ParsePushNotificationInfo(tc.notification)
|
||||||
|
|
||||||
if info.Command != tc.expectedCmd {
|
if info.Name != tc.expectedCmd {
|
||||||
t.Errorf("Expected command %s, got %s", tc.expectedCmd, info.Command)
|
t.Errorf("Expected name %s, got %s", tc.expectedCmd, info.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(info.Args) != tc.expectedArgs {
|
if len(info.Args) != tc.expectedArgs {
|
||||||
t.Errorf("Expected %d args, got %d", tc.expectedArgs, len(info.Args))
|
t.Errorf("Expected %d args, got %d", tc.expectedArgs, len(info.Args))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify no unused fields exist by checking the struct only has Command and Args
|
// Verify no unused fields exist by checking the struct only has Name and Args
|
||||||
// This is a compile-time check - if unused fields were added back, this would fail
|
// This is a compile-time check - if unused fields were added back, this would fail
|
||||||
_ = struct {
|
_ = struct {
|
||||||
Command string
|
Name string
|
||||||
Args []interface{}
|
Args []interface{}
|
||||||
}{
|
}{
|
||||||
Command: info.Command,
|
Name: info.Name,
|
||||||
Args: info.Args,
|
Args: info.Args,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -18,36 +18,47 @@ type PushNotificationHandler interface {
|
|||||||
|
|
||||||
// PushNotificationRegistry manages handlers for different types of push notifications.
|
// PushNotificationRegistry manages handlers for different types of push notifications.
|
||||||
type PushNotificationRegistry struct {
|
type PushNotificationRegistry struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
handlers map[string]PushNotificationHandler // command -> single handler
|
handlers map[string]PushNotificationHandler // pushNotificationName -> single handler
|
||||||
|
protected map[string]bool // pushNotificationName -> protected flag
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPushNotificationRegistry creates a new push notification registry.
|
// NewPushNotificationRegistry creates a new push notification registry.
|
||||||
func NewPushNotificationRegistry() *PushNotificationRegistry {
|
func NewPushNotificationRegistry() *PushNotificationRegistry {
|
||||||
return &PushNotificationRegistry{
|
return &PushNotificationRegistry{
|
||||||
handlers: make(map[string]PushNotificationHandler),
|
handlers: make(map[string]PushNotificationHandler),
|
||||||
|
protected: make(map[string]bool),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterHandler registers a handler for a specific push notification command.
|
// RegisterHandler registers a handler for a specific push notification name.
|
||||||
// Returns an error if a handler is already registered for this command.
|
// Returns an error if a handler is already registered for this push notification name.
|
||||||
func (r *PushNotificationRegistry) RegisterHandler(command string, handler PushNotificationHandler) error {
|
// If protected is true, the handler cannot be unregistered.
|
||||||
|
func (r *PushNotificationRegistry) RegisterHandler(pushNotificationName string, handler PushNotificationHandler, protected bool) error {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
if _, exists := r.handlers[command]; exists {
|
if _, exists := r.handlers[pushNotificationName]; exists {
|
||||||
return fmt.Errorf("handler already registered for command: %s", command)
|
return fmt.Errorf("handler already registered for push notification: %s", pushNotificationName)
|
||||||
}
|
}
|
||||||
r.handlers[command] = handler
|
r.handlers[pushNotificationName] = handler
|
||||||
|
r.protected[pushNotificationName] = protected
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnregisterHandler removes the handler for a specific push notification command.
|
// UnregisterHandler removes the handler for a specific push notification name.
|
||||||
func (r *PushNotificationRegistry) UnregisterHandler(command string) {
|
// Returns an error if the handler is protected.
|
||||||
|
func (r *PushNotificationRegistry) UnregisterHandler(pushNotificationName string) error {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
delete(r.handlers, command)
|
if r.protected[pushNotificationName] {
|
||||||
|
return fmt.Errorf("cannot unregister protected handler for push notification: %s", pushNotificationName)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(r.handlers, pushNotificationName)
|
||||||
|
delete(r.protected, pushNotificationName)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleNotification processes a push notification by calling the registered handler.
|
// HandleNotification processes a push notification by calling the registered handler.
|
||||||
@ -56,8 +67,8 @@ func (r *PushNotificationRegistry) HandleNotification(ctx context.Context, notif
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract command from notification
|
// Extract push notification name from notification
|
||||||
command, ok := notification[0].(string)
|
pushNotificationName, ok := notification[0].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -66,23 +77,23 @@ func (r *PushNotificationRegistry) HandleNotification(ctx context.Context, notif
|
|||||||
defer r.mu.RUnlock()
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
// Call specific handler
|
// Call specific handler
|
||||||
if handler, exists := r.handlers[command]; exists {
|
if handler, exists := r.handlers[pushNotificationName]; exists {
|
||||||
return handler.HandlePushNotification(ctx, notification)
|
return handler.HandlePushNotification(ctx, notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRegisteredCommands returns a list of commands that have registered handlers.
|
// GetRegisteredPushNotificationNames returns a list of push notification names that have registered handlers.
|
||||||
func (r *PushNotificationRegistry) GetRegisteredCommands() []string {
|
func (r *PushNotificationRegistry) GetRegisteredPushNotificationNames() []string {
|
||||||
r.mu.RLock()
|
r.mu.RLock()
|
||||||
defer r.mu.RUnlock()
|
defer r.mu.RUnlock()
|
||||||
|
|
||||||
commands := make([]string, 0, len(r.handlers))
|
names := make([]string, 0, len(r.handlers))
|
||||||
for command := range r.handlers {
|
for name := range r.handlers {
|
||||||
commands = append(commands, command)
|
names = append(names, name)
|
||||||
}
|
}
|
||||||
return commands
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasHandlers returns true if there are any handlers registered.
|
// HasHandlers returns true if there are any handlers registered.
|
||||||
@ -176,13 +187,14 @@ func (p *PushNotificationProcessor) ProcessPendingNotifications(ctx context.Cont
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterHandler is a convenience method to register a handler for a specific command.
|
// RegisterHandler is a convenience method to register a handler for a specific push notification name.
|
||||||
// Returns an error if a handler is already registered for this command.
|
// Returns an error if a handler is already registered for this push notification name.
|
||||||
func (p *PushNotificationProcessor) RegisterHandler(command string, handler PushNotificationHandler) error {
|
// If protected is true, the handler cannot be unregistered.
|
||||||
return p.registry.RegisterHandler(command, handler)
|
func (p *PushNotificationProcessor) RegisterHandler(pushNotificationName string, handler PushNotificationHandler, protected bool) error {
|
||||||
|
return p.registry.RegisterHandler(pushNotificationName, handler, protected)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redis Cluster push notification commands
|
// Redis Cluster push notification names
|
||||||
const (
|
const (
|
||||||
PushNotificationMoving = "MOVING"
|
PushNotificationMoving = "MOVING"
|
||||||
PushNotificationMigrating = "MIGRATING"
|
PushNotificationMigrating = "MIGRATING"
|
||||||
@ -193,8 +205,8 @@ const (
|
|||||||
|
|
||||||
// PushNotificationInfo contains metadata about a push notification.
|
// PushNotificationInfo contains metadata about a push notification.
|
||||||
type PushNotificationInfo struct {
|
type PushNotificationInfo struct {
|
||||||
Command string
|
Name string
|
||||||
Args []interface{}
|
Args []interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParsePushNotificationInfo extracts information from a push notification.
|
// ParsePushNotificationInfo extracts information from a push notification.
|
||||||
@ -203,14 +215,14 @@ func ParsePushNotificationInfo(notification []interface{}) *PushNotificationInfo
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
command, ok := notification[0].(string)
|
name, ok := notification[0].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &PushNotificationInfo{
|
return &PushNotificationInfo{
|
||||||
Command: command,
|
Name: name,
|
||||||
Args: notification[1:],
|
Args: notification[1:],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,5 +231,5 @@ func (info *PushNotificationInfo) String() string {
|
|||||||
if info == nil {
|
if info == nil {
|
||||||
return "<nil>"
|
return "<nil>"
|
||||||
}
|
}
|
||||||
return info.Command
|
return info.Name
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ func TestPushNotificationRegistry(t *testing.T) {
|
|||||||
t.Error("Registry should not have handlers initially")
|
t.Error("Registry should not have handlers initially")
|
||||||
}
|
}
|
||||||
|
|
||||||
commands := registry.GetRegisteredCommands()
|
commands := registry.GetRegisteredPushNotificationNames()
|
||||||
if len(commands) != 0 {
|
if len(commands) != 0 {
|
||||||
t.Errorf("Expected 0 registered commands, got %d", len(commands))
|
t.Errorf("Expected 0 registered commands, got %d", len(commands))
|
||||||
}
|
}
|
||||||
@ -44,7 +44,7 @@ func TestPushNotificationRegistry(t *testing.T) {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
err := registry.RegisterHandler("TEST_COMMAND", handler)
|
err := registry.RegisterHandler("TEST_COMMAND", handler, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to register handler: %v", err)
|
t.Fatalf("Failed to register handler: %v", err)
|
||||||
}
|
}
|
||||||
@ -53,7 +53,7 @@ func TestPushNotificationRegistry(t *testing.T) {
|
|||||||
t.Error("Registry should have handlers after registration")
|
t.Error("Registry should have handlers after registration")
|
||||||
}
|
}
|
||||||
|
|
||||||
commands = registry.GetRegisteredCommands()
|
commands = registry.GetRegisteredPushNotificationNames()
|
||||||
if len(commands) != 1 || commands[0] != "TEST_COMMAND" {
|
if len(commands) != 1 || commands[0] != "TEST_COMMAND" {
|
||||||
t.Errorf("Expected ['TEST_COMMAND'], got %v", commands)
|
t.Errorf("Expected ['TEST_COMMAND'], got %v", commands)
|
||||||
}
|
}
|
||||||
@ -75,11 +75,11 @@ func TestPushNotificationRegistry(t *testing.T) {
|
|||||||
duplicateHandler := newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
duplicateHandler := newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
err = registry.RegisterHandler("TEST_COMMAND", duplicateHandler)
|
err = registry.RegisterHandler("TEST_COMMAND", duplicateHandler, false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected error when registering duplicate handler")
|
t.Error("Expected error when registering duplicate handler")
|
||||||
}
|
}
|
||||||
expectedError := "handler already registered for command: TEST_COMMAND"
|
expectedError := "handler already registered for push notification: TEST_COMMAND"
|
||||||
if err.Error() != expectedError {
|
if err.Error() != expectedError {
|
||||||
t.Errorf("Expected error '%s', got '%s'", expectedError, err.Error())
|
t.Errorf("Expected error '%s', got '%s'", expectedError, err.Error())
|
||||||
}
|
}
|
||||||
@ -106,7 +106,7 @@ func TestPushNotificationProcessor(t *testing.T) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to register handler: %v", err)
|
t.Fatalf("Failed to register handler: %v", err)
|
||||||
}
|
}
|
||||||
@ -155,7 +155,7 @@ func TestClientPushNotificationIntegration(t *testing.T) {
|
|||||||
err := client.RegisterPushNotificationHandler("CUSTOM_EVENT", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
err := client.RegisterPushNotificationHandler("CUSTOM_EVENT", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
handlerCalled = true
|
handlerCalled = true
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to register handler: %v", err)
|
t.Fatalf("Failed to register handler: %v", err)
|
||||||
}
|
}
|
||||||
@ -191,7 +191,7 @@ func TestClientWithoutPushNotifications(t *testing.T) {
|
|||||||
// Registering handlers should not panic
|
// Registering handlers should not panic
|
||||||
err := client.RegisterPushNotificationHandler("TEST", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
err := client.RegisterPushNotificationHandler("TEST", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected nil error when processor is nil, got: %v", err)
|
t.Errorf("Expected nil error when processor is nil, got: %v", err)
|
||||||
}
|
}
|
||||||
@ -221,7 +221,7 @@ func TestPushNotificationEnabledClient(t *testing.T) {
|
|||||||
err := client.RegisterPushNotificationHandler("TEST_NOTIFICATION", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
err := client.RegisterPushNotificationHandler("TEST_NOTIFICATION", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
handlerCalled = true
|
handlerCalled = true
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to register handler: %v", err)
|
t.Fatalf("Failed to register handler: %v", err)
|
||||||
}
|
}
|
||||||
@ -241,6 +241,58 @@ func TestPushNotificationEnabledClient(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPushNotificationProtectedHandlers(t *testing.T) {
|
||||||
|
registry := redis.NewPushNotificationRegistry()
|
||||||
|
|
||||||
|
// Register a protected handler
|
||||||
|
protectedHandler := newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
err := registry.RegisterHandler("PROTECTED_HANDLER", protectedHandler, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to register protected handler: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register a non-protected handler
|
||||||
|
normalHandler := newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
err = registry.RegisterHandler("NORMAL_HANDLER", normalHandler, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to register normal handler: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to unregister the protected handler - should fail
|
||||||
|
err = registry.UnregisterHandler("PROTECTED_HANDLER")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Should not be able to unregister protected handler")
|
||||||
|
}
|
||||||
|
expectedError := "cannot unregister protected handler for push notification: PROTECTED_HANDLER"
|
||||||
|
if err.Error() != expectedError {
|
||||||
|
t.Errorf("Expected error '%s', got '%s'", expectedError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to unregister the normal handler - should succeed
|
||||||
|
err = registry.UnregisterHandler("NORMAL_HANDLER")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Should be able to unregister normal handler: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify protected handler is still registered
|
||||||
|
commands := registry.GetRegisteredPushNotificationNames()
|
||||||
|
if len(commands) != 1 || commands[0] != "PROTECTED_HANDLER" {
|
||||||
|
t.Errorf("Expected only protected handler to remain, got %v", commands)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify protected handler still works
|
||||||
|
ctx := context.Background()
|
||||||
|
notification := []interface{}{"PROTECTED_HANDLER", "data"}
|
||||||
|
handled := registry.HandleNotification(ctx, notification)
|
||||||
|
if !handled {
|
||||||
|
t.Error("Protected handler should still work")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPushNotificationConstants(t *testing.T) {
|
func TestPushNotificationConstants(t *testing.T) {
|
||||||
// Test that Redis Cluster push notification constants are defined correctly
|
// Test that Redis Cluster push notification constants are defined correctly
|
||||||
constants := map[string]string{
|
constants := map[string]string{
|
||||||
@ -267,8 +319,8 @@ func TestPushNotificationInfo(t *testing.T) {
|
|||||||
t.Fatal("Push notification info should not be nil")
|
t.Fatal("Push notification info should not be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.Command != "MOVING" {
|
if info.Name != "MOVING" {
|
||||||
t.Errorf("Expected command 'MOVING', got '%s'", info.Command)
|
t.Errorf("Expected name 'MOVING', got '%s'", info.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(info.Args) != 2 {
|
if len(info.Args) != 2 {
|
||||||
@ -307,7 +359,7 @@ func TestPubSubWithGenericPushNotifications(t *testing.T) {
|
|||||||
customNotificationReceived = true
|
customNotificationReceived = true
|
||||||
t.Logf("Received custom push notification in PubSub context: %v", notification)
|
t.Logf("Received custom push notification in PubSub context: %v", notification)
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to register handler: %v", err)
|
t.Fatalf("Failed to register handler: %v", err)
|
||||||
}
|
}
|
||||||
@ -336,27 +388,6 @@ func TestPubSubWithGenericPushNotifications(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPushNotificationMessageType(t *testing.T) {
|
|
||||||
// Test the PushNotificationMessage type
|
|
||||||
msg := &redis.PushNotificationMessage{
|
|
||||||
Command: "CUSTOM_EVENT",
|
|
||||||
Args: []interface{}{"arg1", "arg2", 123},
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.Command != "CUSTOM_EVENT" {
|
|
||||||
t.Errorf("Expected command 'CUSTOM_EVENT', got '%s'", msg.Command)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(msg.Args) != 3 {
|
|
||||||
t.Errorf("Expected 3 args, got %d", len(msg.Args))
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedString := "push: CUSTOM_EVENT"
|
|
||||||
if msg.String() != expectedString {
|
|
||||||
t.Errorf("Expected string '%s', got '%s'", expectedString, msg.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPushNotificationRegistryUnregisterHandler(t *testing.T) {
|
func TestPushNotificationRegistryUnregisterHandler(t *testing.T) {
|
||||||
// Test unregistering handlers
|
// Test unregistering handlers
|
||||||
registry := redis.NewPushNotificationRegistry()
|
registry := redis.NewPushNotificationRegistry()
|
||||||
@ -368,13 +399,13 @@ func TestPushNotificationRegistryUnregisterHandler(t *testing.T) {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
err := registry.RegisterHandler("TEST_CMD", handler)
|
err := registry.RegisterHandler("TEST_CMD", handler, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to register handler: %v", err)
|
t.Fatalf("Failed to register handler: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify handler is registered
|
// Verify handler is registered
|
||||||
commands := registry.GetRegisteredCommands()
|
commands := registry.GetRegisteredPushNotificationNames()
|
||||||
if len(commands) != 1 || commands[0] != "TEST_CMD" {
|
if len(commands) != 1 || commands[0] != "TEST_CMD" {
|
||||||
t.Errorf("Expected ['TEST_CMD'], got %v", commands)
|
t.Errorf("Expected ['TEST_CMD'], got %v", commands)
|
||||||
}
|
}
|
||||||
@ -395,7 +426,7 @@ func TestPushNotificationRegistryUnregisterHandler(t *testing.T) {
|
|||||||
registry.UnregisterHandler("TEST_CMD")
|
registry.UnregisterHandler("TEST_CMD")
|
||||||
|
|
||||||
// Verify handler is unregistered
|
// Verify handler is unregistered
|
||||||
commands = registry.GetRegisteredCommands()
|
commands = registry.GetRegisteredPushNotificationNames()
|
||||||
if len(commands) != 0 {
|
if len(commands) != 0 {
|
||||||
t.Errorf("Expected no registered commands after unregister, got %v", commands)
|
t.Errorf("Expected no registered commands after unregister, got %v", commands)
|
||||||
}
|
}
|
||||||
@ -459,24 +490,24 @@ func TestPushNotificationRegistryDuplicateHandlerError(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Register first handler - should succeed
|
// Register first handler - should succeed
|
||||||
err := registry.RegisterHandler("DUPLICATE_CMD", handler1)
|
err := registry.RegisterHandler("DUPLICATE_CMD", handler1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("First handler registration should succeed: %v", err)
|
t.Fatalf("First handler registration should succeed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register second handler for same command - should fail
|
// Register second handler for same command - should fail
|
||||||
err = registry.RegisterHandler("DUPLICATE_CMD", handler2)
|
err = registry.RegisterHandler("DUPLICATE_CMD", handler2, false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Second handler registration should fail")
|
t.Error("Second handler registration should fail")
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedError := "handler already registered for command: DUPLICATE_CMD"
|
expectedError := "handler already registered for push notification: DUPLICATE_CMD"
|
||||||
if err.Error() != expectedError {
|
if err.Error() != expectedError {
|
||||||
t.Errorf("Expected error '%s', got '%s'", expectedError, err.Error())
|
t.Errorf("Expected error '%s', got '%s'", expectedError, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify only one handler is registered
|
// Verify only one handler is registered
|
||||||
commands := registry.GetRegisteredCommands()
|
commands := registry.GetRegisteredPushNotificationNames()
|
||||||
if len(commands) != 1 || commands[0] != "DUPLICATE_CMD" {
|
if len(commands) != 1 || commands[0] != "DUPLICATE_CMD" {
|
||||||
t.Errorf("Expected ['DUPLICATE_CMD'], got %v", commands)
|
t.Errorf("Expected ['DUPLICATE_CMD'], got %v", commands)
|
||||||
}
|
}
|
||||||
@ -491,7 +522,7 @@ func TestPushNotificationRegistrySpecificHandlerOnly(t *testing.T) {
|
|||||||
err := registry.RegisterHandler("SPECIFIC_CMD", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
err := registry.RegisterHandler("SPECIFIC_CMD", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
specificCalled = true
|
specificCalled = true
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to register specific handler: %v", err)
|
t.Fatalf("Failed to register specific handler: %v", err)
|
||||||
}
|
}
|
||||||
@ -538,7 +569,7 @@ func TestPushNotificationProcessorEdgeCases(t *testing.T) {
|
|||||||
processor.RegisterHandler("TEST_CMD", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
processor.RegisterHandler("TEST_CMD", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
handlerCalled = true
|
handlerCalled = true
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
|
|
||||||
// Even with handlers registered, disabled processor shouldn't process
|
// Even with handlers registered, disabled processor shouldn't process
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@ -570,7 +601,7 @@ func TestPushNotificationProcessorConvenienceMethods(t *testing.T) {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
err := processor.RegisterHandler("CONV_CMD", handler)
|
err := processor.RegisterHandler("CONV_CMD", handler, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to register handler: %v", err)
|
t.Fatalf("Failed to register handler: %v", err)
|
||||||
}
|
}
|
||||||
@ -580,7 +611,7 @@ func TestPushNotificationProcessorConvenienceMethods(t *testing.T) {
|
|||||||
err = processor.RegisterHandler("FUNC_CMD", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
err = processor.RegisterHandler("FUNC_CMD", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
funcHandlerCalled = true
|
funcHandlerCalled = true
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to register func handler: %v", err)
|
t.Fatalf("Failed to register func handler: %v", err)
|
||||||
}
|
}
|
||||||
@ -628,14 +659,14 @@ func TestClientPushNotificationEdgeCases(t *testing.T) {
|
|||||||
// These should not panic even when processor is nil and should return nil error
|
// These should not panic even when processor is nil and should return nil error
|
||||||
err := client.RegisterPushNotificationHandler("TEST", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
err := client.RegisterPushNotificationHandler("TEST", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected nil error when processor is nil, got: %v", err)
|
t.Errorf("Expected nil error when processor is nil, got: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.RegisterPushNotificationHandler("TEST_FUNC", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
err = client.RegisterPushNotificationHandler("TEST_FUNC", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected nil error when processor is nil, got: %v", err)
|
t.Errorf("Expected nil error when processor is nil, got: %v", err)
|
||||||
}
|
}
|
||||||
@ -700,8 +731,8 @@ func TestPushNotificationInfoEdgeCases(t *testing.T) {
|
|||||||
t.Fatal("Info should not be nil")
|
t.Fatal("Info should not be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.Command != "COMPLEX_CMD" {
|
if info.Name != "COMPLEX_CMD" {
|
||||||
t.Errorf("Expected command 'COMPLEX_CMD', got '%s'", info.Command)
|
t.Errorf("Expected command 'COMPLEX_CMD', got '%s'", info.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(info.Args) != 4 {
|
if len(info.Args) != 4 {
|
||||||
@ -757,7 +788,7 @@ func TestPushNotificationRegistryConcurrency(t *testing.T) {
|
|||||||
command := fmt.Sprintf("CMD_%d_%d", id, j)
|
command := fmt.Sprintf("CMD_%d_%d", id, j)
|
||||||
registry.RegisterHandler(command, newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
registry.RegisterHandler(command, newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
|
|
||||||
// Handle notification
|
// Handle notification
|
||||||
notification := []interface{}{command, "data"}
|
notification := []interface{}{command, "data"}
|
||||||
@ -765,7 +796,7 @@ func TestPushNotificationRegistryConcurrency(t *testing.T) {
|
|||||||
|
|
||||||
// Check registry state
|
// Check registry state
|
||||||
registry.HasHandlers()
|
registry.HasHandlers()
|
||||||
registry.GetRegisteredCommands()
|
registry.GetRegisteredPushNotificationNames()
|
||||||
}
|
}
|
||||||
}(i)
|
}(i)
|
||||||
}
|
}
|
||||||
@ -780,7 +811,7 @@ func TestPushNotificationRegistryConcurrency(t *testing.T) {
|
|||||||
t.Error("Registry should have handlers after concurrent operations")
|
t.Error("Registry should have handlers after concurrent operations")
|
||||||
}
|
}
|
||||||
|
|
||||||
commands := registry.GetRegisteredCommands()
|
commands := registry.GetRegisteredPushNotificationNames()
|
||||||
if len(commands) == 0 {
|
if len(commands) == 0 {
|
||||||
t.Error("Registry should have registered commands after concurrent operations")
|
t.Error("Registry should have registered commands after concurrent operations")
|
||||||
}
|
}
|
||||||
@ -805,7 +836,7 @@ func TestPushNotificationProcessorConcurrency(t *testing.T) {
|
|||||||
command := fmt.Sprintf("PROC_CMD_%d_%d", id, j)
|
command := fmt.Sprintf("PROC_CMD_%d_%d", id, j)
|
||||||
processor.RegisterHandler(command, newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
processor.RegisterHandler(command, newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
|
|
||||||
// Handle notifications
|
// Handle notifications
|
||||||
notification := []interface{}{command, "data"}
|
notification := []interface{}{command, "data"}
|
||||||
@ -859,7 +890,7 @@ func TestPushNotificationClientConcurrency(t *testing.T) {
|
|||||||
command := fmt.Sprintf("CLIENT_CMD_%d_%d", id, j)
|
command := fmt.Sprintf("CLIENT_CMD_%d_%d", id, j)
|
||||||
client.RegisterPushNotificationHandler(command, newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
client.RegisterPushNotificationHandler(command, newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
|
|
||||||
// Access processor
|
// Access processor
|
||||||
processor := client.GetPushNotificationProcessor()
|
processor := client.GetPushNotificationProcessor()
|
||||||
@ -903,7 +934,7 @@ func TestPushNotificationConnectionHealthCheck(t *testing.T) {
|
|||||||
err := client.RegisterPushNotificationHandler("TEST_CONNCHECK", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
err := client.RegisterPushNotificationHandler("TEST_CONNCHECK", newTestHandler(func(ctx context.Context, notification []interface{}) bool {
|
||||||
t.Logf("Received test notification: %v", notification)
|
t.Logf("Received test notification: %v", notification)
|
||||||
return true
|
return true
|
||||||
}))
|
}), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to register handler: %v", err)
|
t.Fatalf("Failed to register handler: %v", err)
|
||||||
}
|
}
|
||||||
|
18
redis.go
18
redis.go
@ -824,11 +824,12 @@ func (c *Client) initializePushProcessor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterPushNotificationHandler registers a handler for a specific push notification command.
|
// RegisterPushNotificationHandler registers a handler for a specific push notification name.
|
||||||
// Returns an error if a handler is already registered for this command.
|
// Returns an error if a handler is already registered for this push notification name.
|
||||||
func (c *Client) RegisterPushNotificationHandler(command string, handler PushNotificationHandler) error {
|
// If protected is true, the handler cannot be unregistered.
|
||||||
|
func (c *Client) RegisterPushNotificationHandler(pushNotificationName string, handler PushNotificationHandler, protected bool) error {
|
||||||
if c.pushProcessor != nil {
|
if c.pushProcessor != nil {
|
||||||
return c.pushProcessor.RegisterHandler(command, handler)
|
return c.pushProcessor.RegisterHandler(pushNotificationName, handler, protected)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -996,11 +997,12 @@ func (c *Conn) Process(ctx context.Context, cmd Cmder) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterPushNotificationHandler registers a handler for a specific push notification command.
|
// RegisterPushNotificationHandler registers a handler for a specific push notification name.
|
||||||
// Returns an error if a handler is already registered for this command.
|
// Returns an error if a handler is already registered for this push notification name.
|
||||||
func (c *Conn) RegisterPushNotificationHandler(command string, handler PushNotificationHandler) error {
|
// If protected is true, the handler cannot be unregistered.
|
||||||
|
func (c *Conn) RegisterPushNotificationHandler(pushNotificationName string, handler PushNotificationHandler, protected bool) error {
|
||||||
if c.pushProcessor != nil {
|
if c.pushProcessor != nil {
|
||||||
return c.pushProcessor.RegisterHandler(command, handler)
|
return c.pushProcessor.RegisterHandler(pushNotificationName, handler, protected)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user