1
0
mirror of https://github.com/redis/go-redis.git synced 2025-07-19 11:43:14 +03:00
Files
go-redis/push_notifications_test.go
Nedyalko Dyakov 5972b4c23f refactor: move all push notification logic to root package and remove adapters
Consolidate all push notification handling logic in the root package to eliminate
adapters and simplify the architecture. This provides direct access to concrete
types without any intermediate layers or type conversions.

Key Changes:

1. Moved Core Types to Root Package:
   - Moved Registry, Processor, VoidProcessor to push_notifications.go
   - Moved all push notification constants to root package
   - Removed internal/pushnotif package dependencies
   - Direct implementation without internal abstractions

2. Eliminated All Adapters:
   - Removed handlerAdapter that bridged internal and public interfaces
   - Removed voidProcessorAdapter for void processor functionality
   - Removed convertInternalToPublicContext conversion functions
   - Direct usage of concrete types throughout

3. Simplified Architecture:
   - PushNotificationHandlerContext directly implemented in root package
   - PushNotificationHandler directly implemented in root package
   - Registry, Processor, VoidProcessor directly in root package
   - No intermediate layers or type conversions needed

4. Direct Type Usage:
   - GetClusterClient() returns *ClusterClient directly
   - GetSentinelClient() returns *SentinelClient directly
   - GetRegularClient() returns *Client directly
   - GetPubSub() returns *PubSub directly
   - No interface casting or type assertions required

5. Updated All Integration Points:
   - Updated redis.go to use direct types
   - Updated pubsub.go to use direct types
   - Updated sentinel.go to use direct types
   - Removed all internal/pushnotif imports
   - Simplified context creation and usage

6. Core Implementation in Root Package:
   ```go
   // Direct implementation - no adapters needed
   type Registry struct {
       handlers  map[string]PushNotificationHandler
       protected map[string]bool
   }

   type Processor struct {
       registry *Registry
   }

   type VoidProcessor struct{}
   ```

7. Handler Context with Concrete Types:
   ```go
   type PushNotificationHandlerContext interface {
       GetClusterClient() *ClusterClient    // Direct concrete type
       GetSentinelClient() *SentinelClient  // Direct concrete type
       GetRegularClient() *Client           // Direct concrete type
       GetPubSub() *PubSub                  // Direct concrete type
   }
   ```

8. Comprehensive Test Suite:
   - Added push_notifications_test.go with full test coverage
   - Tests for Registry, Processor, VoidProcessor
   - Tests for HandlerContext with concrete type access
   - Tests for all push notification constants
   - Validates all functionality works correctly

9. Benefits:
   - Eliminated complex adapter pattern
   - Removed unnecessary type conversions
   - Simplified codebase with direct type usage
   - Better performance without adapter overhead
   - Cleaner architecture with single source of truth
   - Enhanced developer experience with direct access

10. Architecture Simplification:
    Before: Client -> Adapter -> Internal -> Adapter -> Handler
    After:  Client -> Handler (direct)

    No more:
    - handlerAdapter bridging interfaces
    - voidProcessorAdapter for void functionality
    - convertInternalToPublicContext conversions
    - Complex type mapping between layers

This refactoring provides a much cleaner, simpler architecture where all
push notification logic lives in the root package with direct access to
concrete Redis client types, eliminating unnecessary complexity while
maintaining full functionality and type safety.
2025-07-04 21:13:47 +03:00

243 lines
6.4 KiB
Go

package redis
import (
"context"
"testing"
"github.com/redis/go-redis/v9/internal/pool"
)
// TestHandler implements PushNotificationHandler interface for testing
type TestHandler struct {
name string
handled [][]interface{}
returnValue bool
}
func NewTestHandler(name string, returnValue bool) *TestHandler {
return &TestHandler{
name: name,
handled: make([][]interface{}, 0),
returnValue: returnValue,
}
}
func (h *TestHandler) HandlePushNotification(ctx context.Context, handlerCtx PushNotificationHandlerContext, notification []interface{}) bool {
h.handled = append(h.handled, notification)
return h.returnValue
}
func (h *TestHandler) GetHandledNotifications() [][]interface{} {
return h.handled
}
func (h *TestHandler) Reset() {
h.handled = make([][]interface{}, 0)
}
func TestPushNotificationRegistry(t *testing.T) {
t.Run("NewRegistry", func(t *testing.T) {
registry := NewRegistry()
if registry == nil {
t.Error("NewRegistry should not return nil")
}
if len(registry.GetRegisteredPushNotificationNames()) != 0 {
t.Error("New registry should have no registered handlers")
}
})
t.Run("RegisterHandler", func(t *testing.T) {
registry := NewRegistry()
handler := NewTestHandler("test", true)
err := registry.RegisterHandler("TEST", handler, false)
if err != nil {
t.Errorf("RegisterHandler should not error: %v", err)
}
retrievedHandler := registry.GetHandler("TEST")
if retrievedHandler != handler {
t.Error("GetHandler should return the registered handler")
}
})
t.Run("UnregisterHandler", func(t *testing.T) {
registry := NewRegistry()
handler := NewTestHandler("test", true)
registry.RegisterHandler("TEST", handler, false)
err := registry.UnregisterHandler("TEST")
if err != nil {
t.Errorf("UnregisterHandler should not error: %v", err)
}
retrievedHandler := registry.GetHandler("TEST")
if retrievedHandler != nil {
t.Error("GetHandler should return nil after unregistering")
}
})
t.Run("ProtectedHandler", func(t *testing.T) {
registry := NewRegistry()
handler := NewTestHandler("test", true)
// Register protected handler
err := registry.RegisterHandler("TEST", handler, true)
if err != nil {
t.Errorf("RegisterHandler should not error: %v", err)
}
// Try to unregister protected handler
err = registry.UnregisterHandler("TEST")
if err == nil {
t.Error("UnregisterHandler should error for protected handler")
}
// Handler should still be there
retrievedHandler := registry.GetHandler("TEST")
if retrievedHandler != handler {
t.Error("Protected handler should still be registered")
}
})
}
func TestPushNotificationProcessor(t *testing.T) {
t.Run("NewProcessor", func(t *testing.T) {
processor := NewProcessor()
if processor == nil {
t.Error("NewProcessor should not return nil")
}
})
t.Run("RegisterAndGetHandler", func(t *testing.T) {
processor := NewProcessor()
handler := NewTestHandler("test", true)
err := processor.RegisterHandler("TEST", handler, false)
if err != nil {
t.Errorf("RegisterHandler should not error: %v", err)
}
retrievedHandler := processor.GetHandler("TEST")
if retrievedHandler != handler {
t.Error("GetHandler should return the registered handler")
}
})
}
func TestVoidProcessor(t *testing.T) {
t.Run("NewVoidProcessor", func(t *testing.T) {
processor := NewVoidProcessor()
if processor == nil {
t.Error("NewVoidProcessor should not return nil")
}
})
t.Run("GetHandler", func(t *testing.T) {
processor := NewVoidProcessor()
handler := processor.GetHandler("TEST")
if handler != nil {
t.Error("VoidProcessor GetHandler should always return nil")
}
})
t.Run("RegisterHandler", func(t *testing.T) {
processor := NewVoidProcessor()
handler := NewTestHandler("test", true)
err := processor.RegisterHandler("TEST", handler, false)
if err == nil {
t.Error("VoidProcessor RegisterHandler should return error")
}
})
t.Run("ProcessPendingNotifications", func(t *testing.T) {
processor := NewVoidProcessor()
ctx := context.Background()
handlerCtx := NewPushNotificationHandlerContext(nil, nil, nil, nil, false)
// VoidProcessor should always succeed and do nothing
err := processor.ProcessPendingNotifications(ctx, handlerCtx, nil)
if err != nil {
t.Errorf("VoidProcessor ProcessPendingNotifications should never error, got: %v", err)
}
})
}
func TestPushNotificationHandlerContext(t *testing.T) {
t.Run("NewHandlerContext", func(t *testing.T) {
client := &Client{}
connPool := &pool.ConnPool{}
pubSub := &PubSub{}
conn := &pool.Conn{}
ctx := NewPushNotificationHandlerContext(client, connPool, pubSub, conn, true)
if ctx == nil {
t.Error("NewPushNotificationHandlerContext should not return nil")
}
if ctx.GetClient() != client {
t.Error("GetClient should return the provided client")
}
if ctx.GetConnPool() != connPool {
t.Error("GetConnPool should return the provided connection pool")
}
if ctx.GetPubSub() != pubSub {
t.Error("GetPubSub should return the provided PubSub")
}
if ctx.GetConn() != conn {
t.Error("GetConn should return the provided connection")
}
if !ctx.IsBlocking() {
t.Error("IsBlocking should return true")
}
})
t.Run("TypedGetters", func(t *testing.T) {
client := &Client{}
ctx := NewPushNotificationHandlerContext(client, nil, nil, nil, false)
// Test regular client getter
regularClient := ctx.GetRegularClient()
if regularClient != client {
t.Error("GetRegularClient should return the client when it's a regular client")
}
// Test cluster client getter (should be nil for regular client)
clusterClient := ctx.GetClusterClient()
if clusterClient != nil {
t.Error("GetClusterClient should return nil when client is not a cluster client")
}
})
}
func TestPushNotificationConstants(t *testing.T) {
t.Run("Constants", func(t *testing.T) {
if PushNotificationMoving != "MOVING" {
t.Error("PushNotificationMoving should be 'MOVING'")
}
if PushNotificationMigrating != "MIGRATING" {
t.Error("PushNotificationMigrating should be 'MIGRATING'")
}
if PushNotificationMigrated != "MIGRATED" {
t.Error("PushNotificationMigrated should be 'MIGRATED'")
}
if PushNotificationFailingOver != "FAILING_OVER" {
t.Error("PushNotificationFailingOver should be 'FAILING_OVER'")
}
if PushNotificationFailedOver != "FAILED_OVER" {
t.Error("PushNotificationFailedOver should be 'FAILED_OVER'")
}
})
}