Skip to content

feat(redis): support testcontainers-go in the Redis module (no cluster support yet)#1676

Merged
ReneWerner87 merged 20 commits into
gofiber:mainfrom
mdelapenya:tc-go-redis
Apr 29, 2025
Merged

feat(redis): support testcontainers-go in the Redis module (no cluster support yet)#1676
ReneWerner87 merged 20 commits into
gofiber:mainfrom
mdelapenya:tc-go-redis

Conversation

@mdelapenya
Copy link
Copy Markdown
Contributor

@mdelapenya mdelapenya commented Apr 16, 2025

  • feat(redis): use testcontainers-go for testing redis
  • chore: use redis with TLS

What does this PR do?

It uses the tescontainers-go Redis module to enable local testing of the Redis module.

I intentionally not added the cluster support as the tc-go redis module does not support it.

Besides, this PR represents a PoC for adding TLS support for the Redis module in tc-go, so you'll see code to configure the container with on-the-fly TLS certs, using mdelapenya/tlscert. My idea is, if this PR gets accepted, port the logic to the tc-go module, so everybody in the community gets benefit from the TLS support.

Why is it important?

Enable local testing of the Redis module, serving as an example for the Ruedis and Valkey modules

Summary by CodeRabbit

  • Refactor
    • Updated all Redis storage tests to use dynamically created, containerized Redis instances with flexible configuration options, including support for TLS and mutual TLS.
    • Consolidated and streamlined TLS-related tests for improved coverage and maintainability.
    • Improved test cleanup and reliability by ensuring containers are automatically removed after tests complete.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2025

"""

Walkthrough

The Redis storage test suite has been refactored to dynamically create and configure Redis test containers for each test using a new helper function. This approach replaces static Redis configurations with containerized instances that support various connection parameters and TLS settings. The refactor introduces flexible configuration options, container-managed TLS setup, and automated container lifecycle management. All existing tests and benchmarks have been updated to utilize this new setup, ensuring isolated, configurable, and reproducible test environments.

Changes

File(s) Change Summary
redis/redis_test.go Refactored tests to use newTestStore helper that creates Redis containers with configurable connection and TLS options. Added testStoreSettings struct and option functions (withAddress, withHostPort, withTLS, withURL). Replaced manual TLS cert loading with container-managed TLS. Consolidated TLS tests into parameterized subtests. Updated benchmarks to use containerized Redis. Cluster test remains unchanged.

Sequence Diagram(s)

sequenceDiagram
    participant Test as Test Function
    participant Helper as newTestStore
    participant TC as testcontainers-go
    participant Redis as Redis Container

    Test->>Helper: Call newTestStore(opts)
    Helper->>TC: Request Redis container (with TLS/settings)
    TC->>Redis: Start container with configured options
    Helper->>Redis: Wait for readiness on appropriate port
    Helper->>Test: Return configured Storage instance
    Test->>Storage: Execute test logic
    Test->>TC: Container cleanup deferred after test
Loading

Possibly related PRs

Suggested labels

✏️ Feature

Suggested reviewers

  • gaby

Poem

In burrows deep, with Docker spun,
Each test now has its very own run.
TLS or plain, the choice is yours,
With containers fresh behind each door.
Benchmarks hop with nimble glee,
Redis is tested—wild and free!
🐇✨
"""

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @coderabbitai title anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@mdelapenya mdelapenya marked this pull request as ready for review April 16, 2025 18:42
@mdelapenya mdelapenya requested a review from a team as a code owner April 16, 2025 18:42
@mdelapenya mdelapenya requested review from ReneWerner87, efectn, gaby and sixcolors and removed request for a team April 16, 2025 18:42
@mdelapenya
Copy link
Copy Markdown
Contributor Author

@gaby @ReneWerner87 I think this can add value enabling local testing of redis, although testing the cluster support is not included... yet. If you like this approach, I can work on contributing the TLS support to the upstream Redis module in tc-go, so that upcoming releases benefit from it. Wdyt?

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
redis/redis_test.go (6)

29-39: Use consistent Go naming for “withMTLSdisabled”

Variable naming in Go typically prefers consistent casing (e.g., withMTLSDisabled). Updating this field name would improve readability:

-	withMTLSdisabled bool
+	withMTLSDisabled bool

73-75: Clarify function comment to correctly reference Redis server certificates

The comment mentions generating a “nats certificate,” but it actually creates a Redis server certificate. Update the comment for clarity:

-// createTLSCerts creates a CA certificate, a client certificate and a nats certificate,
+// createTLSCerts creates a CA certificate, a client certificate and a Redis server certificate,

184-189: Consider restricting file permissions for private keys

Mounting the private key with file mode 0o644 may be fine in ephemeral test containers, but stricter permissions like 0o600 can reduce risk:

-	FileMode:          0o644,
+	FileMode:          0o600,

404-404: Fix spelling typo in test function name

“Test_Redis_Initalize_WithHostPort” is missing the second “i” in “Initialize”:

-func Test_Redis_Initalize_WithHostPort(t *testing.T) {
+func Test_Redis_Initialize_WithHostPort(t *testing.T) {

424-424: Fix spelling typo in test function name

“Test_Redis_Initalize_WithURL_TLS_Verify” also has a missing “i” in “Initialize”:

-func Test_Redis_Initalize_WithURL_TLS_Verify(t *testing.T) {
+func Test_Redis_Initialize_WithURL_TLS_Verify(t *testing.T) {

627-629: Optimize container management in benchmarks

Repeatedly spinning up a container in each benchmark may lead to longer execution times. If your goal is to measure the overhead of interactions rather than container creation, consider reusing a single container across benchmark tests.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cedaaf2 and dc1bf75.

⛔ Files ignored due to path filters (4)
  • .github/workflows/benchmark.yml is excluded by !**/*.yml
  • .github/workflows/test-redis.yml is excluded by !**/*.yml
  • redis/go.mod is excluded by !**/*.mod
  • redis/go.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (1)
  • redis/redis_test.go (13 hunks)
🔇 Additional comments (1)
redis/redis_test.go (1)

4-9: Imports look good

These newly introduced imports appear necessary and are properly utilized:
• "bytes" for Buffer I/O when mounting TLS certificates
• "context" for container orchestration
• "net" for IP handling
• "os" for environment variables
• "strings" for scheme trimming

@mdelapenya
Copy link
Copy Markdown
Contributor Author

mdelapenya commented Apr 16, 2025

Away from keyboard now, but the tests fail because of the nitpick suggestion by @coderabbitai on changing the cert permissions 😅

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2025

Your organization has reached its limit of developer seats under the Pro Plan. In order to use the Chat feature, you need to buy additional seats

@mdelapenya
Copy link
Copy Markdown
Contributor Author

I could not resist it: testcontainers/testcontainers-go#3115

@gaby
Copy link
Copy Markdown
Member

gaby commented Apr 17, 2025

I could not resist it: testcontainers/testcontainers-go#3115

Awesome! 💪

* main: (48 commits)
  chore(deps): bump github.com/microsoft/go-mssqldb in /mssql
  chore(deps): bump github.com/testcontainers/testcontainers-go
  Add missing Go versions
  Remove new benchmarks, they are not needed.
  Update redis/README.md
  Add missing condition
  Add missing condition for Valkey during Benchmarks
  Update dependencies, add script for updating in the future
  Add unit tests for Redis connection
  Add example for using existing Redis connection
  Added TLS/SSL options in Readme
  Added TLS/SSL options
  chore(deps): bump github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue
  chore(deps): bump github.com/aws/aws-sdk-go-v2/service/dynamodb
  chore(deps): bump github.com/mdelapenya/tlscert in /nats
  chore(deps): bump github.com/minio/minio-go/v7 in /minio
  chore(deps): bump github.com/Azure/azure-sdk-for-go/sdk/storage/azblob
  chore(deps): bump github.com/aws/aws-sdk-go-v2/feature/s3/manager in /s3
  chore(deps): bump github.com/stretchr/testify in /mssql
  chore: apply suggestions from code review
  ...
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
redis/redis_test.go (1)

89-90: Use t.Context() (Go 1.20+) instead of context.Background()

Tying the context to the test lifecycle allows automatic cancellation when a test is aborted or times out.

-ctx := context.Background()
+ctx := t.Context()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c059b0 and c544464.

⛔ Files ignored due to path filters (4)
  • .github/workflows/benchmark.yml is excluded by !**/*.yml
  • .github/workflows/test-redis.yml is excluded by !**/*.yml
  • redis/go.mod is excluded by !**/*.mod
  • redis/go.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (1)
  • redis/redis_test.go (13 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Tests (1.24.x, 7)
🔇 Additional comments (1)
redis/redis_test.go (1)

120-123: ⚠️ Potential issue

Check err before registering cleanup to prevent nil‐pointer issues

testcontainers.CleanupContainer(t, c) is called before verifying that redis.Run succeeded.
If err != nil, c is nil; some future change in the helper could dereference the container pointer and panic.

-    c, err := redis.Run(ctx, img, tcOpts...)
-    testcontainers.CleanupContainer(t, c)
-    require.NoError(t, err)
+    c, err := redis.Run(ctx, img, tcOpts...)
+    require.NoError(t, err)
+    testcontainers.CleanupContainer(t, c)
⛔ Skipped due to learnings
Learnt from: mdelapenya
PR: gofiber/storage#1665
File: cassandra/cassandra_test.go:35-38
Timestamp: 2025-04-20T23:52:03.362Z
Learning: In testcontainers-go, calling `testcontainers.CleanupContainer(t, c)` before checking the error from container creation is safe due to the implementation details of the library. The CleanupContainer function handles nil or partially initialized containers gracefully.

Comment thread redis/redis_test.go Outdated
Comment thread redis/redis_test.go Outdated
Comment thread redis/redis_test.go Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
redis/redis_test.go (6)

71-78: Consider adding unit tests for option functions

While your options pattern is well-implemented, consider adding unit tests that verify each option function correctly modifies the settings struct. This would ensure configuration behavior remains consistent if options are modified in the future.


506-538: Document future cluster support plans

The Test_Redis_Cluster test still uses hardcoded addresses instead of testcontainers. Consider adding a comment explaining that cluster support is pending testcontainers-go Redis module enhancements, as mentioned in the PR description.

func Test_Redis_Cluster(t *testing.T) {
+    // TODO: Replace with testcontainers-based setup once Redis cluster support
+    // is available in testcontainers-go Redis module
    testStoreUniversal := New(Config{
        Addrs: []string{

17-22: Consider adding validation for environment variable input

When reading the Redis image from an environment variable, consider validating that it's a valid image name to prevent potential failures with invalid inputs.

func newTestStore(t testing.TB, opts ...testStoreOption) *Storage {
    // ...
    img := redisImage
    if imgFromEnv := os.Getenv(redisImageEnvVar); imgFromEnv != "" {
+        // Validate image name format
+        if !strings.Contains(imgFromEnv, "/") && !strings.Contains(imgFromEnv, ":") {
+            t.Logf("Warning: Potentially invalid Redis image format: %s, using default", imgFromEnv)
+        }
        img = imgFromEnv
    }
    // ...
}

85-87: Consider making Reset configurable

You're always setting Reset: true in the test Config. For some test scenarios, it might be useful to make this configurable through an option.

// Add a new option function
+func withReset(reset bool) testStoreOption {
+    return func(o *testStoreSettings) {
+        o.reset = reset
+    }
+}

// Update the testStoreSettings struct
type testStoreSettings struct {
    withAddress  bool
    withHostPort bool
    withURL      bool
+    reset       bool

    // TLS settings
    withSecureURL    bool
    withMTLSDisabled bool
    withTLS          bool
}

// Update the initialization in newTestStore
settings := &testStoreSettings{
    withURL:      true, // by default, the URL will be set to the URI provided by the testcontainer
    withAddress:  false,
    withHostPort: false,
+    reset:       true, // by default, reset the Redis database
}

// Update the Config creation
cfg := Config{
-    Reset: true,
+    Reset: settings.reset,
}

68-158: Consider adding error handling with context for container creation failures

The error handling for container creation is minimal. Consider adding more context to the error to help with debugging container initialization issues.

c, err := redis.Run(ctx, img, tcOpts...)
testcontainers.CleanupContainer(t, c)
-require.NoError(t, err)
+if err != nil {
+    t.Fatalf("Failed to create Redis container with image %s: %v", img, err)
+}

94-95: Consider customizable container startup timeout

The container startup timeout is hardcoded to 10 seconds. For CI environments with slower resources, this might not be enough. Consider making this configurable.

// Add a constant for default timeout
+const defaultContainerStartupTimeout = 10 * time.Second

// Add to testStoreSettings
type testStoreSettings struct {
    // ... existing fields
+    startupTimeout time.Duration
}

// Add a new option function
+func withStartupTimeout(timeout time.Duration) testStoreOption {
+    return func(o *testStoreSettings) {
+        o.startupTimeout = timeout
+    }
+}

// Update initialization
settings := &testStoreSettings{
    // ... existing initialization
+    startupTimeout: defaultContainerStartupTimeout,
}

// Update the waitStrategy
waitStrategies := []wait.Strategy{
-    wait.ForListeningPort(redisPort).WithStartupTimeout(time.Second * 10),
+    wait.ForListeningPort(redisPort).WithStartupTimeout(settings.startupTimeout),
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c544464 and 7af98b4.

📒 Files selected for processing (1)
  • redis/redis_test.go (13 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Tests (1.24.x, 7)
  • GitHub Check: Tests (1.23.x, 7)
  • GitHub Check: Tests (1.24.x, 6)
  • GitHub Check: Tests (1.23.x, 6)
🔇 Additional comments (4)
redis/redis_test.go (4)

97-117: TLS configuration looks good

The approach to conditionally set TLS configuration for Redis container looks correct. You're properly setting the TLS certificates and handling the MTLS disabled case.


143-145: Good fix for address scheme handling

Properly trimming both redis:// and rediss:// schemes from the URI addresses a common issue when using TLS connections.


335-377: Well-structured TLS test cases

The parametrized test approach with distinct sub-tests for all four combinations of secure URL and MTLS settings is well organized. Good job passing the testing.T parameter to the test function to fix the previous issue with sub-test reporting.


57-66: Good documentation on the withURL option

The detailed comments explaining the purpose and behavior of the withURL option function are very helpful for maintainers and future contributors.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
redis/redis_test.go (3)

104-111: Simplify TLS auth-client flag handling

The current conditional logic for --tls-auth-clients could be simplified for better readability.

   if settings.withMTLSDisabled {
     cmds = append(cmds, "--tls-auth-clients", "no")
   } else {
     cmds = append(cmds, "--tls-auth-clients", "yes")
   }
+  // Alternative approach:
+  // cmds = append(cmds, "--tls-auth-clients", func() string {
+  //   if settings.withMTLSDisabled {
+  //     return "no"
+  //   }
+  //   return "yes"
+  // }())

211-233: Consider polling instead of fixed sleep to avoid flaky tests

The test uses a fixed sleep duration (1100ms) to wait for key expiration, which could lead to flaky tests if the system is under load or in CI environments.

Consider using a polling approach with timeout instead:

-  time.Sleep(1100 * time.Millisecond)
+  // Poll with timeout to make test more reliable
+  timeout := time.Now().Add(2 * time.Second)
+  var result []byte
+  var err error
+  for time.Now().Before(timeout) {
+    result, err = testStore.Get(key)
+    if err != nil || len(result) == 0 {
+      break
+    }
+    time.Sleep(100 * time.Millisecond)
+  }
-  result, err := testStore.Get(key)
   require.NoError(t, err)
   require.Zero(t, len(result))

23-32: Consider renaming struct fields for consistency

The testStoreSettings struct fields use a with prefix which is redundant since the option functions also use this prefix.

 type testStoreSettings struct {
-	withAddress  bool
-	withHostPort bool
-	withURL      bool
+	address  bool
+	hostPort bool
+	url      bool

 	// TLS settings
-	withSecureURL    bool
-	withMTLSDisabled bool
-	withTLS          bool
+	secureURL    bool
+	mtlsDisabled bool
+	tls          bool
 }

This would require updating the field references in the rest of the code. While this is a pure style improvement, it would make the code more consistent and readable.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a5580e and aece23f.

📒 Files selected for processing (1)
  • redis/redis_test.go (15 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: compare (redis)
  • GitHub Check: Tests (1.23.x, 6)
  • GitHub Check: Tests (1.23.x, 7)
  • GitHub Check: Tests (1.24.x, 7)
  • GitHub Check: Tests (1.24.x, 6)
🔇 Additional comments (4)
redis/redis_test.go (4)

67-151: Well-structured container configuration function with good separation of concerns

The newConfigFromContainer function is comprehensive and follows good practices:

  • Proper use of the options pattern
  • Marking as test helper for better errors
  • Configuring TLS correctly
  • Ensuring container cleanup
  • Supporting multiple connection modes (URL, host/port, address)

332-374: Excellent TLS testing structure with comprehensive coverage

This test is well-structured and covers all combinations of secure/insecure URLs and mTLS enabled/disabled. The parameterized test function with subtests is a clean approach.


537-583: Well-implemented benchmarks using test containers

The benchmarks properly use the new container-based test infrastructure, ensuring consistent testing methodology between tests and benchmarks.


503-535:

Details

❓ Verification inconclusive

Consider containerizing the Redis cluster test for consistency

While I understand that cluster support isn't yet available in testcontainers-go Redis module (as mentioned in the PR description), it would be good to add a TODO comment explaining why this test isn't using containers like the others.


🏁 Script executed:

#!/bin/bash
# Check if cluster support is now available in testcontainers-go
cd /tmp
go mod init example.com/check
go get github.com/testcontainers/testcontainers-go
go get github.com/testcontainers/testcontainers-go/modules/redis
echo "Checking for cluster support in testcontainers-go Redis module:"
find $GOPATH/pkg/mod/github.com/testcontainers -type f -name "*.go" | xargs grep -l "cluster" | grep -i "redis"

Length of output: 1434


Add a TODO for why this test isn’t containerized

Test_Redis_Cluster in redis/redis_test.go (lines 503–535) currently runs against a local cluster, whereas other tests leverage testcontainers-go. Since the Redis module in testcontainers-go doesn’t yet support cluster mode, please add a // TODO: comment above this test explaining the discrepancy (e.g. “TODO: migrate to testcontainers-go Redis cluster container once supported; see PR description for details”).

  • File: redis/redis_test.go
  • Lines: 503–535

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
redis/redis_test.go (5)

34-46: Add documentation to option functions

While the option functions are well-implemented, they lack documentation explaining their purpose, similar to how withURL is documented.

Consider adding documentation comments to the withAddress and withHostPort functions like:

+// withAddress sets the test store to use address-based connection.
 func withAddress() testStoreOption {
     return func(o *testStoreSettings) {
         o.address = true
     }
 }

+// withHostPort sets the test store to use host and port based connection.
 func withHostPort() testStoreOption {
     return func(o *testStoreSettings) {
         o.hostPort = true
     }
 }

48-54: Document the withTLS option function

The withTLS function lacks documentation explaining its purpose and parameters.

Consider adding documentation like:

+// withTLS configures the test store to use TLS.
+// Parameters:
+// - secureURL: when true, uses "rediss://" scheme, otherwise uses "redis://"
+// - mtlsDisabled: when true, disables mutual TLS authentication (--tls-auth-clients no)
 func withTLS(secureURL bool, mtlsDisabled bool) testStoreOption {
     return func(o *testStoreSettings) {
         o.tls = true
         o.secureURL = secureURL
         o.mtlsDisabled = mtlsDisabled
     }
 }

67-87: Add documentation to newConfigFromContainer function

The newConfigFromContainer function is well-implemented but lacks documentation explaining its purpose.

Consider adding documentation like:

+// newConfigFromContainer creates a Redis configuration using a test container.
+// It configures the container based on the provided options and returns a Config
+// that can be used to connect to the container.
+// The container is cleaned up when the test completes.
 func newConfigFromContainer(t testing.TB, opts ...testStoreOption) Config {
     t.Helper()
     // rest of function...

154-156: Add documentation to newTestStore function

The newTestStore function lacks documentation explaining its purpose.

Consider adding documentation like:

+// newTestStore creates a new Redis storage instance backed by a test container.
+// It configures the container based on the provided options and returns a Storage
+// instance connected to the container. The caller is responsible for calling
+// Close() on the returned Storage when done.
 func newTestStore(t testing.TB, opts ...testStoreOption) *Storage {
     return New(newConfigFromContainer(t, opts...))
 }

504-536: Consider containerizing the Redis cluster test

The Redis cluster test still uses a static configuration without containers, which differs from the approach used in all other tests. According to the PR objectives, this is because cluster support is not yet available in the testcontainers-go Redis module. Consider adding a comment explaining this limitation.

Add a comment explaining why this test doesn't use the containerized approach:

 func Test_Redis_Cluster(t *testing.T) {
+    // TODO: Replace with containerized cluster when testcontainers-go Redis module supports clustering
     testStoreUniversal := New(Config{
         Addrs: []string{
             "localhost:7000",
             "localhost:7001",
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aece23f and 0d583fa.

📒 Files selected for processing (1)
  • redis/redis_test.go (15 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Tests (1.23.x, 6)
  • GitHub Check: Tests (1.24.x, 7)
  • GitHub Check: Tests (1.24.x, 6)
  • GitHub Check: Tests (1.23.x, 7)
🔇 Additional comments (8)
redis/redis_test.go (8)

4-14: Import and package organization looks good

The import additions for the test container dependencies and context management are appropriate. Including both the main testcontainers package and the Redis module is a good practice.


16-21: Constants are well-defined with comments

The constants provide good flexibility with default values and environment variable override capability for the Redis image. This approach allows for testing with different Redis versions.


23-32: Well-structured test settings struct

The testStoreSettings struct provides a clean way to configure test containers with different connection and TLS options. The comments for the TLS settings section add good clarity.


104-109: Good fix for the TLS auth clients flag issue

Your implementation of the TLS auth clients flag setting correctly addresses the past review comment by using a function to conditionally return the appropriate value rather than adding duplicate flags.


137-139: Good handling of URL schemes

The code properly handles both redis:// and rediss:// URI schemes when extracting the address, which addresses the previous review comment.


333-358: Well-structured TLS test with proper test helper pattern

The TLS test function is well-implemented, using a helper function that takes the testing.T instance to properly report errors in subtests. This addresses the previous review comment about sub-tests reporting to the wrong testing.T instance.


589-590: Good use of container for NewFromConnection test

Using the container configuration for the NewFromConnection test ensures consistency with the other tests and avoids relying on a local Redis instance.


538-541:

Details

❓ Verification inconclusive

Consider benchmark implications of using containers

Using containers in benchmarks might affect the results due to container overhead. Consider documenting this potential impact or providing a way to use a non-containerized approach for accurate benchmarking.

While the current implementation works, containers add overhead that might impact benchmark results. Consider if it makes sense to document this limitation or provide a flag to use a non-containerized Redis instance for benchmarks.

Also applies to: 553-556, 570-573


🏁 Script executed:

#!/bin/bash
# Test if the benchmark results differ significantly between containerized and non-containerized Redis
# Run this only if you have a local Redis instance running on the default port

# First, let's check if non-containerized Redis is available
if redis-cli ping > /dev/null 2>&1; then
  echo "Local Redis instance is available for benchmarking comparison"
else
  echo "No local Redis instance available, skipping comparison"
  exit 0
fi

# Extract a benchmark function to a temporary file to run with both approaches
cat > /tmp/redis_benchmark.go << 'EOF'
package main

import (
	"testing"
	"github.com/gofiber/storage/redis"
)

func BenchmarkRedisSet(b *testing.B) {
	store := redis.New(redis.Config{Reset: true})
	defer store.Close()
	
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		store.Set("key", []byte("value"), 0)
	}
}

func main() {
	b := testing.B{}
	BenchmarkRedisSet(&b)
}
EOF

# This is a simplified check and won't actually run - would need more setup to execute properly
echo "This comparison would need a proper benchmark harness to execute"

Length of output: 144


Consider container overhead in Redis benchmarks

We couldn’t validate the performance delta locally (no non-containerized Redis instance detected), so the risk of container overhead remains. It’s still worth:

  • Documenting that these benchmarks run against a containerized Redis and may include extra latency.
  • Providing an option or flag to run benchmarks against a local/non-containerized instance for closer-to-bare-metal measurements.

Locations to update:

  • redis/redis_test.go: lines 538–541, 553–556, 570–573

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
redis/redis_test.go (1)

156-159: Consider enhancing URL scheme replacement

The URL scheme replacement works, but it might be more robust to check if the URL starts with "rediss://" before attempting to replace it.

-	if !settings.secureURL {
-		// Replace the scheme with the insecure one
-		cfg.URL = strings.Replace(cfg.URL, "rediss://", "redis://", 1)
+	if !settings.secureURL && strings.HasPrefix(cfg.URL, "rediss://") {
+		// Replace the scheme with the insecure one
+		cfg.URL = strings.Replace(cfg.URL, "rediss://", "redis://", 1)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d583fa and 9113e44.

📒 Files selected for processing (1)
  • redis/redis_test.go (16 hunks)
🔇 Additional comments (9)
redis/redis_test.go (9)

16-21: Good use of constants and environment variable for Redis configuration

The constants provide a clear way to configure the Redis test container, and the environment variable allows for flexible testing with different Redis versions, which is a great practice for compatibility testing.


23-32: Clean and well-structured test settings implementation

The testStoreSettings struct provides a clear separation of concerns between connection methods and TLS configuration, making the test setup highly flexible and maintainable.


36-71: Well-implemented functional options pattern

Great job using the functional options pattern for configuring test stores. The functions are well-documented and provide clear semantics for different connection methods and TLS configurations.


103-123: TLS configuration is properly handled

The TLS configuration properly handles both secure and mutual TLS scenarios. The approach of using a function to determine the --tls-auth-clients value is elegant and avoids duplicate flag issues.


145-150: Correct handling of both Redis URL schemes

Good job properly trimming both redis:// and rediss:// schemes when populating the Addrs field, ensuring addresses are always in the format host:port.


164-170: Elegant and concise test store creation

The newTestStore function provides a clean API for test setup, encapsulating the container creation and configuration logic.


347-389: Well-structured TLS verification tests

Excellent organization of TLS tests using subtests for different configurations. The helper function properly uses t.Helper() and receives the test context parameter, enabling accurate test failure reporting.


518-520: Clear TODO for future containerized cluster support

Good job documenting the future enhancement for containerized cluster testing when the testcontainers-go Redis module adds this capability.


73-162: Comprehensive container configuration with proper resource management

The newConfigFromContainer function is well-implemented with:

  1. Proper container cleanup using testcontainers.CleanupContainer
  2. Flexible configuration based on provided options
  3. Dynamic TLS setup with proper certificate paths
  4. Proper error handling throughout

This approach ensures tests run in isolated, reproducible environments while managing resources efficiently.

Comment thread redis/redis_test.go
Comment on lines +604 to 605
connection := New(newConfigFromContainer(t))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding a cleanup for the connection in Test_Redis_NewFromConnection

The connection created in this test is never closed, which could lead to resource leaks during testing.

-	connection := New(newConfigFromContainer(t))
+	connection := New(newConfigFromContainer(t))
+	defer connection.Close()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
connection := New(newConfigFromContainer(t))
connection := New(newConfigFromContainer(t))
defer connection.Close()

@mdelapenya
Copy link
Copy Markdown
Contributor Author

@gaby @ReneWerner87 after the latest release of testcontainers-go, v0.37.0, Redis comes with TLS support 💪 so this PR has been updated to include it.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
redis/redis_test.go (1)

601-605: ⚠️ Potential issue

Add cleanup for the connection in Test_Redis_NewFromConnection

The connection created in this test is never closed, which could lead to resource leaks during testing.

-	connection := New(newConfigFromContainer(t))
+	connection := New(newConfigFromContainer(t))
+	defer connection.Close()
🧹 Nitpick comments (4)
redis/redis_test.go (4)

518-520: Consider using a Skip mechanism for the cluster test

The TODO comment is helpful, but this test might fail if the user doesn't have a local Redis cluster running on the specified ports. Consider adding a t.Skip() mechanism until containerized cluster support is available.

func Test_Redis_Cluster(t *testing.T) {
	// TODO: Replace with containerized cluster when testcontainers-go Redis module supports clustering
+	// Skip this test if the local Redis cluster isn't running
+	conn, err := net.DialTimeout("tcp", "localhost:7000", 1*time.Second)
+	if err != nil {
+		t.Skip("Redis cluster not available, skipping test")
+	}
+	conn.Close()
+
	testStoreUniversal := New(Config{

4-14: Consider importing packages in alphabetical order

While not critical, many Go projects follow the convention of organizing imports in alphabetical order, with the standard library packages first, followed by external packages.

import (
	"context"
	"os"
	"strings"
	"testing"
	"time"

-	"github.com/stretchr/testify/require"
-
	"github.com/testcontainers/testcontainers-go"
	"github.com/testcontainers/testcontainers-go/modules/redis"
+
+	"github.com/stretchr/testify/require"
)

89-92: Consider adding a comment explaining why an environment variable override is needed

Adding a brief comment about why someone might want to override the Redis image would be helpful for maintainers.

	img := redisImage
+	// Allow overriding the Redis image via environment variable (e.g., for testing with different versions)
	if imgFromEnv := os.Getenv(redisImageEnvVar); imgFromEnv != "" {
		img = imgFromEnv
	}

104-123: Document why TLS port 6379 is used instead of the standard port

The TLS configuration is using port 6379 for TLS while disabling the standard port (--port 0). Adding a comment explaining this decision would help future maintainers understand the rationale.

	if settings.tls {
		tcOpts = append(tcOpts, redis.WithTLS())

+		// Disable standard Redis port and use only TLS port to ensure all connections use TLS
		// Use Redis module's TLS options, but for the MTLS case, disable the auth-clients flag
		cmds := []string{
			"--port", "0",
			"--tls-port", "6379",
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9113e44 and 52dda9d.

📒 Files selected for processing (1)
  • redis/redis_test.go (16 hunks)
🔇 Additional comments (2)
redis/redis_test.go (2)

73-162: Great implementation of containerized Redis tests with TLS support!

The newConfigFromContainer function is well-structured and handles all the necessary configuration for Redis test containers, including TLS support. The cleanup is properly registered with testcontainers.CleanupContainer(t, c) before checking for errors, which is good practice.

I particularly like how you've implemented the flexible TLS configuration in lines 114-119, addressing the previous comment about duplicate flags.


347-389: Well-structured parameterized tests for TLS configurations!

Great job refactoring the TLS tests to use a parameterized approach that covers all combinations of secure URL and mutual TLS settings. You've properly addressed the previous review comment by passing the testing.T parameter to the test function.

@gaby gaby requested a review from Copilot April 29, 2025 11:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces support for testing the Redis module using testcontainers-go, including a proof-of-concept implementation for TLS support.

  • Updated the test workflow to dynamically configure Redis containers using testcontainers-go.
  • Revised Redis version specifications in the workflow matrix and consolidated test environment variable configurations.

Reviewed Changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.

File Description
.github/workflows/test-redis.yml Updated Redis image version matrix and environment variable setup to align with testcontainers-go usage and TLS PoC.
.github/workflows/benchmark.yml Removed manual Redis setup steps and hardcoded Redis image version for benchmarks.
Files not reviewed (1)
  • redis/go.mod: Language not supported
Comments suppressed due to low confidence (2)

.github/workflows/test-redis.yml:21

  • [nitpick] Confirm that changing the Redis version tag from '6.x' to '6' matches the expected Docker image tags to avoid potential version mismatches.
+                    - '6'

.github/workflows/test-redis.yml:22

  • [nitpick] Confirm that switching the Redis version tag from '7.x' to '7' aligns with the official Docker image tags for Redis.
+                    - '7'

Comment thread .github/workflows/benchmark.yml
Comment thread .github/workflows/test-redis.yml
@gaby
Copy link
Copy Markdown
Member

gaby commented Apr 29, 2025

@mdelapenya I think the rueidis driver is basically a copy/paste of the redis one in terms of Tests.

Copy link
Copy Markdown
Member

@gaby gaby left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 LGTM

@ReneWerner87 ReneWerner87 merged commit 2a50072 into gofiber:main Apr 29, 2025
11 checks passed
@mdelapenya mdelapenya deleted the tc-go-redis branch April 30, 2025 08:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants