Skip to content

Redis full integration tests#4

Open
dragosv wants to merge 2 commits intomainfrom
integration
Open

Redis full integration tests#4
dragosv wants to merge 2 commits intomainfrom
integration

Conversation

@dragosv
Copy link
Owner

@dragosv dragosv commented Mar 8, 2026

Description

Brief description of the changes made in this PR.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring
  • Performance improvement
  • Test addition/modification
  • CI/CD changes

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Testing

Describe the tests you ran to verify your changes:

  • Unit tests
  • Integration tests
  • Manual testing
  • Performance tests

Additional Notes

Add any other context about the PR here.

Summary by Sourcery

Expand Redis integration coverage and tighten container lifecycle validation.

Tests:

  • Add helper to send raw Redis protocol commands and read responses for use in integration tests.
  • Extend DockerContainer integration specs to verify Redis PING/SET/GET operations, container existence, ID reset, and cross-check exec-set values via TCP.
  • Enhance RedisContainer integration specs to validate password-protected URLs, parsed URI fields, and basic Redis commands over the exposed URL.
  • Broaden DockerNetwork integration specs to assert network info fields and post-removal state.
  • Limit CI integration test run to the dedicated integration_spec to focus execution on integration scenarios.

Summary by CodeRabbit

  • Tests

    • Enhanced integration test suite with improved Redis protocol validation and Docker container testing coverage.
  • Chores

    • Updated CI workflow to explicitly target integration test specifications.

@sourcery-ai
Copy link

sourcery-ai bot commented Mar 8, 2026

Reviewer's Guide

Adds low-level Redis protocol helpers and expands integration tests to fully exercise Redis containers (including auth, commands, logs, and lifecycle), while scoping CI to run only the integration spec file.

Sequence diagram for Redis full integration test execution

sequenceDiagram
    participant CI as GitHub_Actions_Job
    participant Spec as RedisIntegrationSpec
    participant Helper as RedisProtocolHelper
    participant RC as RedisContainer

    CI->>Spec: run crystal_spec_spec_integration_spec_cr
    Spec->>RC: start_container
    RC-->>Spec: container_ready

    Spec->>Helper: connect host port password
    Helper->>RC: open_tcp_connection
    RC-->>Helper: connection_established

    Spec->>Helper: send_auth password
    Helper->>RC: send_auth_command
    RC-->>Helper: auth_ok

    Spec->>Helper: send_command command args
    Helper->>RC: write_redis_protocol
    RC-->>Helper: command_response
    Helper-->>Spec: parsed_response

    Spec->>RC: fetch_logs
    RC-->>Spec: container_logs

    Spec->>RC: stop_container
    RC-->>Spec: container_stopped
Loading

Class diagram for Redis protocol helpers and integration spec

classDiagram

  class RedisProtocolHelper {
    +String host
    +Int32 port
    +String password
    +connect(String host, Int32 port, String password)
    +send_auth(String password)
    +send_command(String command, Array args)
    +read_response()
  }

  class RedisAuthHelper {
    +String password
    +build_auth_command(String password)
  }

  class RedisCommandHelper {
    +build_command(String command, Array args)
    +encode_bulk_string(String value)
    +encode_array(Array values)
  }

  class RedisLifecycleHelper {
    +start_container()
    +stop_container()
    +restart_container()
    +fetch_logs()
  }

  class RedisIntegrationSpec {
    +setup()
    +teardown()
    +test_authentication()
    +test_basic_commands()
    +test_logging()
    +test_container_lifecycle()
  }

  RedisIntegrationSpec --> RedisProtocolHelper : uses
  RedisIntegrationSpec --> RedisLifecycleHelper : controls
  RedisProtocolHelper --> RedisAuthHelper : delegates_auth_build
  RedisProtocolHelper --> RedisCommandHelper : delegates_command_build
Loading

File-Level Changes

Change Details Files
Introduce low-level Redis command helpers used by integration specs to talk directly to Redis over TCP, including optional AUTH from a redis:// URL.
  • Add send_redis_command helper to encode simple RESP arrays to an IO stream.
  • Add read_redis_response helper to parse simple Redis responses (+/−/: and bulk strings).
  • Add redis_command(host, port, *parts) convenience wrapper to send one command and read the response.
  • Add redis_command(redis_url, *parts) variant that parses a redis:// URL, performs AUTH when needed, and executes a command.
spec/integration_spec.cr
Strengthen DockerContainer integration coverage by asserting Redis connectivity, container lifecycle invariants, and command execution behavior.
  • In the start/stop scenario, assert Redis responds to PING and can SET/GET a key through redis_command using mapped host/port.
  • In the use-block scenario, verify container.exists? is true while in use, perform a PING via redis_command, and assert container.exists? and container_id are cleared afterward.
  • Expand command execution test to SET and GET a key via redis-cli inside the container and cross-check the value via direct redis_command from the host.
  • Tighten logs test by replacing bare sleep with sleep 1.second and asserting logs contain both readiness and version strings from Redis.
spec/integration_spec.cr
Increase RedisContainer and DockerNetwork integration coverage, including password-protected URLs and network metadata/lifecycle assertions.
  • Configure RedisContainer in tests with .with_password("secret") and verify the generated redis_url includes matching scheme, host, port, and password.
  • Use redis_command(redis_url, ...) to validate PING/SET/GET over the password-protected connection.
  • For DockerNetwork, assert network.info.name and id match the resource and that created? becomes false after removal.
spec/integration_spec.cr
Narrow CI integration-test step to only run the Redis integration spec file.
  • Change the CI integration test command from running the full spec suite with -Dintegration to running only spec/integration_spec.cr with -Dintegration and random order.
.github/workflows/ci.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link

coderabbitai bot commented Mar 8, 2026

📝 Walkthrough

Walkthrough

The changes add Redis protocol helpers to integration tests and modify the CI workflow to explicitly target a named integration spec file. Redis RESP command/response helpers enable direct TCP interactions with Redis containers, expanding test coverage to validate Redis connectivity, URL parsing, and AUTH flows against containerized Redis instances.

Changes

Cohort / File(s) Summary
CI Configuration
.github/workflows/ci.yml
Integration test command now explicitly specifies spec/integration_spec.cr file path instead of running all integration specs by default.
Redis Protocol Helpers & Integration Tests
spec/integration_spec.cr
Added four helper methods for Redis RESP protocol: send_redis_command, read_redis_response, and two redis_command variants (by host/port and by URL). Extended integration test cases to validate Redis container connectivity, command execution, URL component parsing, and AUTH flow via TCP interactions. Updated sleep helpers and expanded assertions for container/network state validation and label metadata checks.

Sequence Diagram

sequenceDiagram
    participant Test as Test Suite
    participant RC as redis_command()
    participant TCP as TCP Socket
    participant Redis as Redis Container

    Test->>RC: redis_command(url, "PING")
    RC->>RC: Parse URL & extract host, port, password
    RC->>TCP: Socket.tcp(host, port)
    TCP-->>RC: Connected
    
    alt Password present
        RC->>RC: send_redis_command(io, ["AUTH", password])
        RC->>TCP: Write RESP AUTH command
        TCP->>Redis: AUTH command
        Redis-->>TCP: +OK response
        RC->>RC: read_redis_response(io)
        RC-->>RC: Parse +OK
    end
    
    RC->>RC: send_redis_command(io, ["PING"])
    RC->>TCP: Write RESP PING command
    TCP->>Redis: PING command
    Redis-->>TCP: +PONG response
    RC->>RC: read_redis_response(io)
    RC-->>RC: Parse PONG
    RC-->>Test: Return "PONG"
    
    Test->>Test: Assert response matches expected value
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A Redis rabbit hops with glee,
Testing commands through TCP,
PING and GET dance merrily,
AUTH flows validate with spree,
Containers now verified, hooray! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Redis full integration tests' directly and specifically describes the main change: expanding Redis integration test coverage with comprehensive protocol interaction validation.
Description check ✅ Passed The description uses the template structure but lacks concrete details in the main Description section. However, the Sourcery summary at the end provides substantive information about the changes, compensating for the template-only content in the body.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch integration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • The sleep 1.second in the Redis logs test is a fixed delay that may be flaky on slower environments; consider polling for the expected log line with a timeout instead of sleeping a fixed duration.
  • The CI step now runs only spec/integration_spec.cr; if additional integration specs are added later they will be skipped, so consider keeping a pattern (e.g. spec/*_integration_spec.cr or the full suite with an -Dintegration filter) to avoid unintentionally omitting tests.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `sleep 1.second` in the Redis logs test is a fixed delay that may be flaky on slower environments; consider polling for the expected log line with a timeout instead of sleeping a fixed duration.
- The CI step now runs only `spec/integration_spec.cr`; if additional integration specs are added later they will be skipped, so consider keeping a pattern (e.g. `spec/*_integration_spec.cr` or the full suite with an `-Dintegration` filter) to avoid unintentionally omitting tests.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@codecov
Copy link

codecov bot commented Mar 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.14%. Comparing base (af97cd0) to head (e06cfc3).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main       #4   +/-   ##
=======================================
  Coverage   61.14%   61.14%           
=======================================
  Files          14       14           
  Lines         350      350           
=======================================
  Hits          214      214           
  Misses        136      136           
Flag Coverage Δ
unittests 61.14% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@spec/integration_spec.cr`:
- Around line 44-66: The redis_command helpers (both overloads) should use a
connect timeout and retry loop around the TCPSocket.open call to avoid transient
"Connection reset by peer" failures: wrap the TCPSocket.open(host, port) call in
a small retry loop (e.g., max attempts + backoff sleep) that rescues transient
socket errors (Errno::ECONNREFUSED, Errno::ECONNRESET, IO::Timeout, etc.) and
retries, and when opening the socket use a connect-with-timeout approach (or set
socket read/write timeouts) before calling send_redis_command and
read_redis_response; preserve existing AUTH logic in the
redis_command(redis_url, ...) overload so AUTH is retried only after a
successful socket open and still raises if auth_response != "OK".

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b19e5a56-7ab9-4fe2-a458-fab850f77f6b

📥 Commits

Reviewing files that changed from the base of the PR and between 520b661 and e06cfc3.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • spec/integration_spec.cr

Comment on lines +44 to +66
def redis_command(host : String, port : Int32, *parts : String) : String
TCPSocket.open(host, port) do |socket|
send_redis_command(socket, parts.to_a)
read_redis_response(socket)
end
end

def redis_command(redis_url : String, *parts : String) : String
uri = URI.parse(redis_url)
host = uri.host || raise "Redis URL is missing host"
port = uri.port || 6379

TCPSocket.open(host, port) do |socket|
if password = uri.password
send_redis_command(socket, ["AUTH", password])
auth_response = read_redis_response(socket)
raise "Redis AUTH failed: #{auth_response}" unless auth_response == "OK"
end

send_redis_command(socket, parts.to_a)
read_redis_response(socket)
end
end
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add timeout and retry logic to prevent flaky tests.

The pipeline failures showing "Connection reset by peer" suggest a race condition between the container's log output and TCP port readiness. TCPSocket.open should include timeouts, and the helper should retry on transient failures.

🛠️ Proposed fix with timeout and retry logic
 def redis_command(host : String, port : Int32, *parts : String) : String
-  TCPSocket.open(host, port) do |socket|
-    send_redis_command(socket, parts.to_a)
-    read_redis_response(socket)
+  retries = 3
+  begin
+    TCPSocket.open(host, port, connect_timeout: 5.seconds) do |socket|
+      socket.read_timeout = 5.seconds
+      send_redis_command(socket, parts.to_a)
+      read_redis_response(socket)
+    end
+  rescue ex : IO::Error | Socket::ConnectError
+    retries -= 1
+    if retries > 0
+      sleep 0.5.seconds
+      retry
+    end
+    raise ex
   end
 end

 def redis_command(redis_url : String, *parts : String) : String
   uri = URI.parse(redis_url)
   host = uri.host || raise "Redis URL is missing host"
   port = uri.port || 6379

-  TCPSocket.open(host, port) do |socket|
-    if password = uri.password
-      send_redis_command(socket, ["AUTH", password])
-      auth_response = read_redis_response(socket)
-      raise "Redis AUTH failed: #{auth_response}" unless auth_response == "OK"
+  retries = 3
+  begin
+    TCPSocket.open(host, port, connect_timeout: 5.seconds) do |socket|
+      socket.read_timeout = 5.seconds
+      if password = uri.password
+        send_redis_command(socket, ["AUTH", password])
+        auth_response = read_redis_response(socket)
+        raise "Redis AUTH failed: #{auth_response}" unless auth_response == "OK"
+      end
+
+      send_redis_command(socket, parts.to_a)
+      read_redis_response(socket)
     end
-
-    send_redis_command(socket, parts.to_a)
-    read_redis_response(socket)
+  rescue ex : IO::Error | Socket::ConnectError
+    retries -= 1
+    if retries > 0
+      sleep 0.5.seconds
+      retry
+    end
+    raise ex
   end
 end
📝 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
def redis_command(host : String, port : Int32, *parts : String) : String
TCPSocket.open(host, port) do |socket|
send_redis_command(socket, parts.to_a)
read_redis_response(socket)
end
end
def redis_command(redis_url : String, *parts : String) : String
uri = URI.parse(redis_url)
host = uri.host || raise "Redis URL is missing host"
port = uri.port || 6379
TCPSocket.open(host, port) do |socket|
if password = uri.password
send_redis_command(socket, ["AUTH", password])
auth_response = read_redis_response(socket)
raise "Redis AUTH failed: #{auth_response}" unless auth_response == "OK"
end
send_redis_command(socket, parts.to_a)
read_redis_response(socket)
end
end
def redis_command(host : String, port : Int32, *parts : String) : String
retries = 3
begin
TCPSocket.open(host, port, connect_timeout: 5.seconds) do |socket|
socket.read_timeout = 5.seconds
send_redis_command(socket, parts.to_a)
read_redis_response(socket)
end
rescue ex : IO::Error | Socket::ConnectError
retries -= 1
if retries > 0
sleep 0.5.seconds
retry
end
raise ex
end
end
def redis_command(redis_url : String, *parts : String) : String
uri = URI.parse(redis_url)
host = uri.host || raise "Redis URL is missing host"
port = uri.port || 6379
retries = 3
begin
TCPSocket.open(host, port, connect_timeout: 5.seconds) do |socket|
socket.read_timeout = 5.seconds
if password = uri.password
send_redis_command(socket, ["AUTH", password])
auth_response = read_redis_response(socket)
raise "Redis AUTH failed: #{auth_response}" unless auth_response == "OK"
end
send_redis_command(socket, parts.to_a)
read_redis_response(socket)
end
rescue ex : IO::Error | Socket::ConnectError
retries -= 1
if retries > 0
sleep 0.5.seconds
retry
end
raise ex
end
end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/integration_spec.cr` around lines 44 - 66, The redis_command helpers
(both overloads) should use a connect timeout and retry loop around the
TCPSocket.open call to avoid transient "Connection reset by peer" failures: wrap
the TCPSocket.open(host, port) call in a small retry loop (e.g., max attempts +
backoff sleep) that rescues transient socket errors (Errno::ECONNREFUSED,
Errno::ECONNRESET, IO::Timeout, etc.) and retries, and when opening the socket
use a connect-with-timeout approach (or set socket read/write timeouts) before
calling send_redis_command and read_redis_response; preserve existing AUTH logic
in the redis_command(redis_url, ...) overload so AUTH is retried only after a
successful socket open and still raises if auth_response != "OK".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant