Skip to content

Conversation

@ochui
Copy link
Member

@ochui ochui commented Jan 26, 2026

This pull request significantly expands and strengthens the test suite for edge cases and functional coverage of the FQ queueing system. It introduces a new dedicated edge case test module with extensive mocking and adds numerous functional tests to ensure correct behavior across a wide range of scenarios, including configuration handling, connection types, queue operations, payload deserialization, and Redis client management.

The most important changes are:

New edge case test coverage:

  • Added a comprehensive new test module tests/test_edge_cases.py that uses mock classes to simulate various Redis behaviors and covers many edge conditions and fallback paths in queue.py and utils.py, such as cluster initialization, dequeue with None payload, queue clearing, client connection handling, and configuration reloads.

Expanded functional tests for robustness:

  • Added multiple new async test methods to tests/test_func.py to cover:
    • The public initialize() method, Redis client getter, and connection close logic.
    • Initialization with both Unix socket and invalid connection types, ensuring proper error handling.
    • Queue clearing with string job UUIDs and mixed job IDs.
    • The deep_status() method for Redis availability.
    • The reload_lua_scripts() method and its effect on subsequent operations.
    • The behavior of get_queue_length(), including invalid parameters and empty queues.
    • The dequeue() method on empty queues.
    • The deserialize_payload() utility with both old (quote-wrapped) and new formats.
    • The convert_to_str() utility with mixed bytes/string sets.

Test infrastructure and imports:

  • Added necessary imports and exception handling in tests/test_func.py to support new tests and error checks.

These changes greatly improve the reliability and maintainability of the codebase by ensuring edge cases and error conditions are thoroughly tested.

Summary by CodeRabbit

  • Tests
    • Added extensive async edge-case and functional tests for the Redis-backed queue covering initialization paths, error handling, socket/unix handling, config reloads, dequeue/clear/purge behaviors, payload formats, deep-status checks, queue length validation, and resource cleanup.
  • Bug Fixes
    • Initialization hardened to validate configuration presence and verify Redis connectivity early, producing faster, clearer failures for missing/invalid config or unreachable Redis.

✏️ Tip: You can customize this high-level summary in your review settings.

ochui added 2 commits January 26, 2026 08:41
Expands test coverage to include public and internal initialization
logic, Redis connection handling with different config scenarios,
queue length and dequeue edge cases, Lua script reloading, and
utility functions for payload deserialization and type conversion.

Enhances reliability by verifying error handling and compatibility
with legacy payload formats, as well as safe resource cleanup.
Introduces comprehensive async unit tests to exercise rare and error-prone execution paths, improving code robustness and coverage for cluster initialization, queue clearing, deep status checks, and input validation. Enables detection of regressions in edge scenarios and aids future refactoring.
Copilot AI review requested due to automatic review settings January 26, 2026 08:02
@coderabbitai
Copy link

coderabbitai bot commented Jan 26, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds extensive async unit and functional tests for Redis/Lua edge cases, tightens config loading, and introduces an early Redis connectivity validation during queue initialization; also removes the top-level docker-compose version field.

Changes

Cohort / File(s) Summary
Edge cases tests
tests/test_edge_cases.py
New comprehensive async tests (~+274 lines) with multiple fake helpers (FakeCluster, FakeRedisForClose, FakeRedisForDeepStatus, FakeLuaDequeue, FakePipe, FakeRedisForClear, FakeRedisConnectionFailure) covering init failures, cluster init via temp config, dequeue with None payload, clear/purge edge cases, close() fallbacks, deep_status behaviors, identifier validation, reload_config, purge_all, and get_queue_length validation.
Functional tests
tests/test_func.py
Expanded functional tests (~+223 lines) adding tests for initialize(), Unix-socket and Redis paths, Lua script reload, queue length before/after enqueue, client getter, close() handling, clear queue/purge semantics (including string UUIDs), payload deserialization formats, dequeue on empty queue, conversion utilities, and teardown Redis flush.
Docker compose
docker-compose.yml
Removed top-level version: "3.9" field; services (including redis) unchanged otherwise.
Queue initialization & validation
src/fq/queue.py
Adds a private Redis connectivity validation (pings Redis and raises FQException on failure), stricter config file existence/reading and required-section checks, and calls the validator early in the _initialize flow before loading Lua scripts. Key areas: _validate_redis_connection, config file checks, and ordering relative to Lua script loading.

Sequence Diagram(s)

sequenceDiagram
  participant Init as Initializer
  participant Conf as Config Reader
  participant Redis as Redis Server
  participant Lua as Lua Script Loader

  Init->>Conf: load config file
  Conf-->>Init: config (redis, fq) or error
  Init->>Redis: open connection
  Redis-->>Init: connection object
  Init->>Redis: PING (via _validate_redis_connection)
  alt ping OK
    Redis-->>Init: PONG
    Init->>Lua: load/refresh scripts
    Lua-->>Init: scripts loaded
  else ping FAIL
    Redis-->>Init: error/false
    Init-->>Init: raise FQException (abort initialization)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped through configs, pings, and queues,
Mocked Lua whispers and Redis hues,
Tests danced nightly, edge-cases found,
Close called gently — no lost sound,
A carrot for coverage, snug and sound.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.54% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: expanding test coverage for initialization, including both public and internal paths (new edge-case tests and functional tests).

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

✨ Finishing touches
  • 📝 Generate docstrings

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

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1c466fee55

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 1931 to 1933
async def test_asyncTearDown(self):
await self.queue._r.flushdb()
await self.queue.close()

Choose a reason for hiding this comment

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

P2 Badge Restore unittest asyncTearDown hook

Renaming the asyncTearDown hook to test_asyncTearDown means unittest will treat it as a normal test and never run teardown after each test case. That leaves Redis state and connections open between tests, which can cause order-dependent failures and resource leaks when the suite grows or runs in parallel. This regression is visible in the new method name and breaks the expected unittest lifecycle.

Useful? React with 👍 / 👎.

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

🤖 Fix all issues with AI agents
In `@tests/test_func.py`:
- Around line 1817-1841: The test test_initialize_unix_socket_connection is
asserting success against a non-existent Unix socket; update the test to either
mock the Redis client or skip when the socket is absent: replace the real
connect attempt in FQ._initialize by patching the Redis constructor (or the
method used inside FQ._initialize) to return a fake/non-blocking client so fq._r
is set, or add a pre-check for the unix_socket_path and call self.skipTest(...)
when the socket file does not exist; reference
test_initialize_unix_socket_connection, class FQ, and method _initialize to
locate and apply the change.
🧹 Nitpick comments (3)
tests/test_func.py (2)

1886-1929: Tests defined as async but don't use await.

The following tests are defined as async but don't perform any async operations:

  • test_deserialize_payload_old_format
  • test_deserialize_payload_new_format
  • test_convert_to_str_with_mixed_types

While this works fine with IsolatedAsyncioTestCase, regular def would be more appropriate for synchronous tests.

♻️ Suggested change
-    async def test_deserialize_payload_old_format(self):
+    def test_deserialize_payload_old_format(self):
         """Test deserialize_payload with old quote-wrapped format - tests utils.py line 63."""
         ...

-    async def test_deserialize_payload_new_format(self):
+    def test_deserialize_payload_new_format(self):
         """Test deserialize_payload handles new unwrapped format - tests utils.py line 63."""
         ...

-    async def test_convert_to_str_with_mixed_types(self):
+    def test_convert_to_str_with_mixed_types(self):
         """Test convert_to_str handles both bytes and strings."""
         ...

1904-1915: Test duplication detected.

test_deep_status_redis_availability (lines 1911-1915) is essentially a duplicate of test_deep_status (lines 1729-1732). Both tests verify the same behavior - that deep_status() returns a non-None result. Consider removing one of them.

tests/test_edge_cases.py (1)

149-155: Redundant assertion on line 155.

Line 154 already asserts fq._r is None. Line 155 (self.assertTrue(fq._r is None)) is redundant and can be removed.

♻️ Suggested fix
     async def test_close_fallback_paths(self):
         """Covers close() fallback paths (queue.py lines 528-549)."""
         fq = FQ(self.config_path)
         fq._r = FakeRedisForClose()
         await fq.close()
         self.assertIsNone(fq._r)
-        self.assertTrue(fq._r is None)

Copy link

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

Expands the test suite for the FQ Redis-backed queue to cover additional initialization paths, edge cases, and utility behavior.

Changes:

  • Added a new edge-case-focused test module with mock Redis/cluster implementations.
  • Added multiple new functional tests covering initialization, client lifecycle, queue operations, and payload/utility handling.
  • Updated uv.lock metadata for the editable local package entry.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.

File Description
uv.lock Adjusts the lock entry for the local editable flowdacity-queue package.
tests/test_func.py Adds functional tests for initialization, client management, queue length, dequeue empty behavior, payload deserialization, and utility conversions.
tests/test_edge_cases.py Introduces a new edge-case test module using fakes/mocks to exercise failure/branch paths.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

self.assertIn("key2", result)
self.assertIn("key3", result)

async def test_asyncTearDown(self):
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

unittest.IsolatedAsyncioTestCase uses asyncTearDown() for per-test cleanup, but this file defines test_asyncTearDown() instead. That means the Redis connection is no longer closed after each test (and this method will run as a normal test instead), which can lead to unclosed-connection ResourceWarnings and test interference over time. Rename this method back to asyncTearDown (and keep the flush/close logic there).

Suggested change
async def test_asyncTearDown(self):
async def asyncTearDown(self):

Copilot uses AI. Check for mistakes.
fq._r = FakeRedisForClose()
await fq.close()
self.assertIsNone(fq._r)
self.assertTrue(fq._r is None)
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

assertTrue(a is b) cannot provide an informative message. Using assertIs(a, b) instead will give more informative messages.

Suggested change
self.assertTrue(fq._r is None)
self.assertIsNone(fq._r)

Copilot uses AI. Check for mistakes.
@codecov
Copy link

codecov bot commented Jan 26, 2026

Codecov Report

❌ Patch coverage is 50.00000% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/fq/queue.py 50.00% 5 Missing and 5 partials ⚠️

📢 Thoughts on this report? Let us know!

@ochui
Copy link
Member Author

ochui commented Jan 26, 2026

@copilot open a new pull request to apply changes based on the comments in this thread

Copy link
Contributor

Copilot AI commented Jan 26, 2026

@ochui I've opened a new pull request, #5, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 2 commits January 26, 2026 08:26
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

🤖 Fix all issues with AI agents
In `@tests/test_edge_cases.py`:
- Around line 18-21: The unused parameters in register_script's inner async
function _runner and in the class/method __call__ should be explicitly prefixed
with underscores to satisfy Ruff ARG001/ARG002; update _runner's signature to
use _args and _kwargs (or prefix each unused param with an underscore) and
likewise rename any unused parameters in __call__ to start with an underscore
(e.g., _args, _kwargs or _param -> _param) so the linter recognizes they are
intentionally unused.
♻️ Duplicate comments (1)
tests/test_func.py (1)

1817-1841: Mock or skip unix-socket init to avoid a hard failure.

This test points to /tmp/redis_nonexistent.sock and expects _initialize() to succeed. On most systems the socket won’t exist, so the test will fail or be flaky. Consider mocking the Redis client (or skipping when the socket is absent).

✅ Safer approach: mock Redis for the unix socket path
 async def test_initialize_unix_socket_connection(self):
     """Test initialization with unix socket connection - tests line 59."""
     # Create a temporary config with unix_sock
     with tempfile.NamedTemporaryFile(mode='w', suffix='.conf', delete=False) as f:
         f.write("""[fq]
@@
 """)
         config_path = f.name
     
     try:
+        from unittest.mock import AsyncMock, patch
         fq = FQ(config_path)
-        # This tests the unix_sock path (line 59)
-        await fq._initialize()
-        self.assertIsNotNone(fq._r)
-        await fq.close()
+        # Mock Redis to avoid requiring a real unix socket
+        with patch("fq.queue.Redis") as mock_redis:
+            mock_client = AsyncMock()
+            mock_redis.return_value = mock_client
+            await fq._initialize()
+            call_kwargs = mock_redis.call_args[1]
+            self.assertEqual(
+                call_kwargs.get("unix_socket_path"),
+                "/tmp/redis_nonexistent.sock",
+            )
+        await fq.close()
     finally:
         os.unlink(config_path)

Comment on lines +18 to +21
def register_script(self, _):
async def _runner(*args, **kwargs):
return []

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find Ruff configuration files
find . -type f \( -name "pyproject.toml" -o -name "setup.cfg" -o -name "ruff.toml" -o -name ".ruff.toml" \) | head -20

Repository: flowdacity/flowdacity-queue

Length of output: 99


🏁 Script executed:

# Check the test file structure and content at specified lines
wc -l tests/test_edge_cases.py

Repository: flowdacity/flowdacity-queue

Length of output: 99


🏁 Script executed:

# View the code at lines 18-21
sed -n '15,25p' tests/test_edge_cases.py

Repository: flowdacity/flowdacity-queue

Length of output: 316


🏁 Script executed:

# View the code at lines 55-56 (and context)
sed -n '50,60p' tests/test_edge_cases.py

Repository: flowdacity/flowdacity-queue

Length of output: 285


🏁 Script executed:

# Check Ruff configuration for ARG rules
cat pyproject.toml

Repository: flowdacity/flowdacity-queue

Length of output: 788


🏁 Script executed:

# Also check the dedicated ruff.toml
cat ruff.toml

Repository: flowdacity/flowdacity-queue

Length of output: 977


🏁 Script executed:

# Run Ruff linting on the test file to confirm the issues
ruff check tests/test_edge_cases.py --select ARG

Repository: flowdacity/flowdacity-queue

Length of output: 1739


Prefix unused parameters with underscores to silence Ruff ARG001/ARG002.

The project enforces Ruff linting with ARG rules enabled. The parameters in _runner and __call__ are intentionally unused, so prefix them with underscores to make this explicit and pass linting.

🔧 Proposed fix
 class FakeCluster:
 
     def register_script(self, _):
-        async def _runner(*args, **kwargs):
+        async def _runner(*_args, **_kwargs):
             return []

 class FakeLuaDequeue:
 
-    async def __call__(self, keys=None, args=None):
+    async def __call__(self, _keys=None, _args=None):
         self.called = True
         return [b"q1", b"j1", None, b"0"]
🧰 Tools
🪛 Ruff (0.14.13)

19-19: Unused function argument: args

(ARG001)


19-19: Unused function argument: kwargs

(ARG001)

🤖 Prompt for AI Agents
In `@tests/test_edge_cases.py` around lines 18 - 21, The unused parameters in
register_script's inner async function _runner and in the class/method __call__
should be explicitly prefixed with underscores to satisfy Ruff ARG001/ARG002;
update _runner's signature to use _args and _kwargs (or prefix each unused param
with an underscore) and likewise rename any unused parameters in __call__ to
start with an underscore (e.g., _args, _kwargs or _param -> _param) so the
linter recognizes they are intentionally unused.

ochui added 2 commits January 26, 2026 11:08
Adds early validation for missing or unreadable config files and ensures Redis connection errors are surfaced during initialization. Updates tests to cover these failure cases, improving robustness and error transparency.
Prevents actual Redis connections during testing by using AsyncMock
for the Redis client and its ping method. Increases test isolation
and reliability by removing dependency on an external Redis server.
Copy link

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

Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

import unittest
import msgpack
import tempfile
from unittest.mock import patch, AsyncMock
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

patch is imported but never used in this test module. If you’re running linting (or plan to), this will be flagged; please remove the unused import or use it in the new tests.

Suggested change
from unittest.mock import patch, AsyncMock
from unittest.mock import AsyncMock

Copilot uses AI. Check for mistakes.
Comment on lines 1836 to 1842
fq = FQ(config_path)
# Mock the Redis ping to avoid actual connection attempt
mock_redis = AsyncMock()
mock_redis.ping = AsyncMock(return_value=True)
fq._r = mock_redis
# This tests the unix_sock path was configured (line 59)
self.assertIsNotNone(fq._r)
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

test_initialize_unix_socket_connection doesn’t actually exercise the unix-socket initialization path: it never calls fq._initialize() / fq.initialize(), and it manually assigns fq._r, so it can pass even if the conn_type == "unix_sock" branch is broken. To make this test meaningful, patch fq.queue.Redis to a fake that records init kwargs and provides an async ping(), then call await fq._initialize() and assert unix_socket_path was used.

Copilot uses AI. Check for mistakes.
Comment on lines 114 to 118
class TestEdgeCases(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self):
cwd = os.path.dirname(os.path.realpath(__file__))
self.config_path = os.path.join(cwd, "test.conf")

Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

This new test module runs against the shared Redis service (several tests call await fq._initialize() and execute commands) but doesn’t consistently clean up Redis state between tests. Other suites flush Redis in setup/teardown (e.g., tests/test_func.py:29-32, tests/test_queue.py:60-66). Consider adding an asyncTearDown (or per-test helper) that flushes the configured DB and closes the client when initialized, to avoid order-dependent failures when running subsets or adding new tests later.

Copilot uses AI. Check for mistakes.
@ochui
Copy link
Member Author

ochui commented Jan 26, 2026

@copilot open a new pull request to apply changes based on the comments in this thread

Copy link
Contributor

Copilot AI commented Jan 26, 2026

@ochui I've opened a new pull request, #6, to work on those changes. Once the pull request is ready, I'll request review from you.

@ochui ochui merged commit 0a54f14 into main Jan 26, 2026
5 of 6 checks passed
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.

2 participants