package e2e import ( "context" "fmt" "os" "strings" "testing" "time" logs2 "github.com/redis/go-redis/v9/internal/maintnotifications/logs" "github.com/redis/go-redis/v9/logging" "github.com/redis/go-redis/v9/maintnotifications" ) // TODO ADD TLS CONFIGS // TestTLSConfigurationsPushNotifications tests push notifications with different TLS configurations func ТestTLSConfigurationsPushNotifications(t *testing.T) { if os.Getenv("E2E_SCENARIO_TESTS") != "true" { t.Skip("Scenario tests require E2E_SCENARIO_TESTS=true") } ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) defer cancel() var dump = true var errorsDetected = false var p = func(format string, args ...interface{}) { printLog("TLS-CONFIGS", false, format, args...) } var e = func(format string, args ...interface{}) { errorsDetected = true printLog("TLS-CONFIGS", true, format, args...) } // Test different TLS configurations // Note: TLS configuration is typically handled at the Redis connection config level // This scenario demonstrates the testing pattern for different TLS setups tlsConfigs := []struct { name string description string skipReason string }{ { name: "NoTLS", description: "No TLS encryption (plain text)", }, { name: "TLSInsecure", description: "TLS with insecure skip verify (testing only)", }, { name: "TLSSecure", description: "Secure TLS with certificate verification", skipReason: "Requires valid certificates in test environment", }, { name: "TLSMinimal", description: "TLS with minimal version requirements", }, { name: "TLSStrict", description: "Strict TLS with TLS 1.3 and specific cipher suites", }, } logCollector.ClearLogs() defer func() { logCollector.Clear() }() // Test each TLS configuration with its own fresh database for _, tlsTest := range tlsConfigs { t.Run(tlsTest.name, func(t *testing.T) { // Setup: Create fresh database and client factory for THIS TLS config test bdbID, factory, cleanup := SetupTestDatabaseAndFactory(t, ctx, "standalone") defer cleanup() t.Logf("[TLS-CONFIGS-%s] Created test database with bdb_id: %d", tlsTest.name, bdbID) // Get endpoint config from factory (now connected to new database) endpointConfig := factory.GetConfig() // Create fault injector faultInjector, err := CreateTestFaultInjector() if err != nil { t.Fatalf("[ERROR] Failed to create fault injector: %v", err) } defer func() { if dump { p("Pool stats:") factory.PrintPoolStats(t) } }() errorsDetected = false var ef = func(format string, args ...interface{}) { printLog("TLS-CONFIGS", true, format, args...) t.FailNow() } if tlsTest.skipReason != "" { t.Skipf("Skipping %s: %s", tlsTest.name, tlsTest.skipReason) } p("Testing TLS configuration: %s - %s", tlsTest.name, tlsTest.description) minIdleConns := 3 poolSize := 8 maxConnections := 12 // Create Redis client with specific TLS configuration // Note: TLS configuration is handled at the factory/connection level client, err := factory.Create(fmt.Sprintf("tls-test-%s", tlsTest.name), &CreateClientOptions{ Protocol: 3, // RESP3 required for push notifications PoolSize: poolSize, MinIdleConns: minIdleConns, MaxActiveConns: maxConnections, MaintNotificationsConfig: &maintnotifications.Config{ Mode: maintnotifications.ModeEnabled, HandoffTimeout: 30 * time.Second, RelaxedTimeout: 10 * time.Second, PostHandoffRelaxedDuration: 2 * time.Second, MaxWorkers: 15, EndpointType: maintnotifications.EndpointTypeExternalIP, }, ClientName: fmt.Sprintf("tls-test-%s", tlsTest.name), }) if err != nil { // Some TLS configurations might fail in test environments if tlsTest.name == "TLSSecure" || tlsTest.name == "TLSStrict" { t.Skipf("TLS configuration %s failed (expected in test environment): %v", tlsTest.name, err) } ef("Failed to create client for %s: %v", tlsTest.name, err) } // Create timeout tracker tracker := NewTrackingNotificationsHook() logger := maintnotifications.NewLoggingHook(int(logging.LogLevelDebug)) setupNotificationHooks(client, tracker, logger) defer func() { tracker.Clear() }() // Verify initial connectivity err = client.Ping(ctx).Err() if err != nil { if tlsTest.name == "TLSSecure" || tlsTest.name == "TLSStrict" { t.Skipf("TLS configuration %s ping failed (expected in test environment): %v", tlsTest.name, err) } ef("Failed to ping Redis with %s TLS config: %v", tlsTest.name, err) } p("Client connected successfully with %s TLS configuration", tlsTest.name) commandsRunner, _ := NewCommandRunner(client) defer func() { if dump { stats := commandsRunner.GetStats() p("%s TLS config stats: Operations: %d, Errors: %d, Timeout Errors: %d", tlsTest.name, stats.Operations, stats.Errors, stats.TimeoutErrors) } commandsRunner.Stop() }() // Start command traffic go func() { commandsRunner.FireCommandsUntilStop(ctx) }() // Test migration with this TLS configuration p("Testing migration with %s TLS configuration...", tlsTest.name) migrateResp, err := faultInjector.TriggerAction(ctx, ActionRequest{ Type: "migrate", Parameters: map[string]interface{}{ "bdb_id": endpointConfig.BdbID, }, }) if err != nil { ef("Failed to trigger migrate action for %s: %v", tlsTest.name, err) } // Wait for MIGRATING notification match, found := logCollector.MatchOrWaitForLogMatchFunc(func(s string) bool { return strings.Contains(s, logs2.ProcessingNotificationMessage) && strings.Contains(s, "MIGRATING") }, 60*time.Second) if !found { ef("MIGRATING notification was not received for %s TLS config", tlsTest.name) } migrateData := logs2.ExtractDataFromLogMessage(match) p("MIGRATING notification received for %s: %v", tlsTest.name, migrateData) // Wait for migration to complete status, err := faultInjector.WaitForAction(ctx, migrateResp.ActionID, WithMaxWaitTime(240*time.Second), WithPollInterval(2*time.Second), ) if err != nil { ef("[FI] Migrate action failed for %s: %v", tlsTest.name, err) } p("[FI] Migrate action completed for %s: %s %s", tlsTest.name, status.Status, actionOutputIfFailed(status)) // Continue traffic for a bit to observe TLS behavior time.Sleep(5 * time.Second) commandsRunner.Stop() // Analyze results for this TLS configuration trackerAnalysis := tracker.GetAnalysis() if trackerAnalysis.NotificationProcessingErrors > 0 { e("Notification processing errors with %s TLS config: %d", tlsTest.name, trackerAnalysis.NotificationProcessingErrors) } if trackerAnalysis.UnexpectedNotificationCount > 0 { e("Unexpected notifications with %s TLS config: %d", tlsTest.name, trackerAnalysis.UnexpectedNotificationCount) } // Validate we received expected notifications if trackerAnalysis.FailingOverCount == 0 { e("Expected FAILING_OVER notifications with %s TLS config, got none", tlsTest.name) } if trackerAnalysis.FailedOverCount == 0 { e("Expected FAILED_OVER notifications with %s TLS config, got none", tlsTest.name) } if trackerAnalysis.MigratingCount == 0 { e("Expected MIGRATING notifications with %s TLS config, got none", tlsTest.name) } if errorsDetected { logCollector.DumpLogs() trackerAnalysis.Print(t) logCollector.Clear() tracker.Clear() ef("[FAIL] Errors detected with %s TLS config", tlsTest.name) } // TLS-specific validations stats := commandsRunner.GetStats() switch tlsTest.name { case "NoTLS": // Plain text should work fine p("Plain text connection processed %d operations", stats.Operations) case "TLSInsecure", "TLSMinimal": // Insecure TLS should work in test environments p("Insecure TLS connection processed %d operations", stats.Operations) if stats.Operations == 0 { e("Expected operations with %s TLS config, got none", tlsTest.name) } case "TLSStrict": // Strict TLS might have different performance characteristics p("Strict TLS connection processed %d operations", stats.Operations) } p("TLS configuration %s test completed successfully", tlsTest.name) }) // Clear logs between TLS configuration tests logCollector.ClearLogs() } p("All TLS configurations tested successfully") }