Skip to content

Test/backend core coverage#260

Open
muhammadtihame wants to merge 6 commits intoAOSSIE-Org:mainfrom
muhammadtihame:test/backend-core-coverage
Open

Test/backend core coverage#260
muhammadtihame wants to merge 6 commits intoAOSSIE-Org:mainfrom
muhammadtihame:test/backend-core-coverage

Conversation

@muhammadtihame
Copy link

@muhammadtihame muhammadtihame commented Jan 30, 2026

📝 Description

This PR significantly improves backend reliability by adding comprehensive unit test coverage for core handler, event, and agent state modules. While writing tests, a production bug was discovered and fixed in HandlerRegistry.get_handler() related to incorrect assumptions about event value types.

The changes are intentionally scoped to core backend logic and do not introduce new features or alter public APIs.


🔧 Changes Made

  • Added 74 unit tests covering core backend modules:
    • HandlerRegistry
    • BaseHandler
    • MessageHandler
    • FAQHandler
    • ClassificationRouter
    • AgentState
    • BaseEvent
  • Fixed a production bug in HandlerRegistry.get_handler():
    • Previously assumed event.platform and event.event_type were enums with .value
    • Updated logic to safely support both enum and string values
  • Improved test isolation to avoid circular import issues
  • Updated configuration where required to support testing

🐞 Bug Fix Details

Issue
HandlerRegistry.get_handler() assumed event.platform and event.event_type were enum objects, but BaseEvent stores these fields as strings in some cases. This caused handler resolution failures at runtime.

Fix
The handler key resolution now gracefully handles both enum and string inputs.


🧪 Verification

All scoped backend tests pass locally:

python -m pytest \
tests/test_handler_registry.py \
tests/test_base_handler.py \
tests/test_message_handler.py \
tests/test_faq_handler.py \
tests/test_classification_router.py \
tests/test_agent_state.py \
tests/test_events.py -v


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * Configurable async database connections with pooled session management for more reliable backend DB access.
  * More robust event routing that accepts varied platform and event-type inputs.

* **Documentation**
  * New guide explaining database connection setup, pooling, and usage examples.

* **Tests**
  * Large suite of new and expanded tests covering handlers, routing, DB pooling, message/FAQ flows, and state.

* **Bug Fixes**
  * Minor test fixes and cleanups.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Final cleanup pushed.

- Fixed syntax errors in test_weaviate.py
- Removed invalid import in test_supabase.py
- No behavior changes
- All 74 unit tests pass

Thanks for the reviews — please let me know if anything else is needed.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

Added optional database_url setting, a new async DB core with pooled SQLAlchemy sessions and FastAPI dependency, normalized handler registry key resolution, minor message handler cleanup, added DB docs and dependencies, and many new/updated unit tests.

Changes

Cohort / File(s) Summary
Configuration
backend/app/core/config/settings.py
Added optional database_url: Optional[str] = None to Settings.
Database core & docs
backend/app/database/core.py, docs/DATABASE_CONNECTION.md
New async SQLAlchemy engine and async_session_maker (asyncpg), guarded initialization from settings, pooled connection configuration, get_db() async dependency, and documentation describing usage and testing.
Handler layer
backend/app/core/handler/handler_registry.py, backend/app/core/handler/message_handler.py
HandlerRegistry now normalizes enum/string values for platform and event_type lookups; removed placeholder comments from message handler.
Models
backend/app/models/database/supabase.py
Added new CodeChunk Pydantic model (file/chunk metadata fields).
Dependencies & build
pyproject.toml, backend/requirements.txt
Added sqlalchemy, asyncpg, and pytest-asyncio entries; pytest asyncio config added to pyproject.toml.
Tests — setup & fixtures
tests/conftest.py
Added pytest fixtures for sample events, mock Discord bot, and mock LLM clients.
Tests — DB & pooling
tests/test_db_pool.py, tests/tests_db.py
New tests validating engine pool config, concurrent session acquisition, rollback behavior; removed duplicate import in tests_db.
Tests — handlers & routing
tests/test_base_handler.py, tests/test_handler_registry.py, tests/test_message_handler.py, tests/test_faq_handler.py
New/expanded tests for handler pipeline, registry lookup (platform-specific fallback), message/FAQ handling and Discord integration.
Tests — classification & events
tests/test_classification_router.py, tests/test_events.py
Triage/classification tests (LLM parsing and fallbacks) and comprehensive BaseEvent serialization/deserialization tests.
Tests — state, supabase, weaviate
tests/test_agent_state.py, tests/test_supabase.py, tests/test_weaviate.py
AgentState reducer tests; small import/order change in supabase tests; fixed string literal typos in weaviate tests.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant FastAPI_Route as FastAPI Route
    participant DI_get_db as get_db()
    participant DB_Engine as AsyncEngine

    Client->>FastAPI_Route: HTTP request requiring DB
    FastAPI_Route->>DI_get_db: dependency injection -> acquire session
    DI_get_db->>DB_Engine: checkout AsyncSession from pool
    DB_Engine-->>DI_get_db: AsyncSession
    DI_get_db-->>FastAPI_Route: yield AsyncSession
    FastAPI_Route->>DB_Engine: execute queries via AsyncSession
    FastAPI_Route->>DI_get_db: on completion or error -> cleanup
    DI_get_db->>DB_Engine: commit or rollback and release session
    DB_Engine-->>DI_get_db: session returned to pool
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • chandansgowda
  • smokeyScraper

Poem

🐰 I found a URL beneath a log,
I pooled my hops and cleared the fog,
Async sessions in a tidy queue,
Tests hopped in to check what's true,
Code and carrots—hippity-hop, woo!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.63% 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 "Test/backend core coverage" directly reflects the PR's primary objective of adding comprehensive unit-test coverage for core backend logic and a targeted bug fix in HandlerRegistry, accurately summarizing the main change.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
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

🤖 Fix all issues with AI agents
In `@docs/DATABASE_CONNECTION.md`:
- Around line 19-25: Update the module path in the documentation so it matches
the repository layout: change references to app/database/core.py to
backend/app/database/core.py in the "Engine & Pooling" section (and any other
occurrences), ensuring the doc points to the actual module location used by the
codebase.
🧹 Nitpick comments (9)
pyproject.toml (1)

43-44: Keep dependency constraints in sync with backend/requirements.txt.

SQLAlchemy is pinned to a specific version in backend/requirements.txt, while pyproject.toml allows a range. If both are used in different environments, this can cause drift. Consider aligning them to a single source of truth.

tests/test_agent_state.py (1)

6-25: Consider centralizing sys.path manipulation in a shared test hook.

Per-test sys.path edits can be order-dependent. A conftest.py or pytest.ini pythonpath setting would keep test imports consistent and reduce duplication.

tests/test_db_pool.py (1)

59-64: Rename unused async-loop variables to satisfy Ruff B007.

Use _session to avoid lint warnings in the async generator loops.

Proposed diff
-            async for session in mock_db_module.get_db():
+            async for _session in mock_db_module.get_db():
                 # Simulate some work
                 await asyncio.sleep(0.01)
                 return True
-            async for session in mock_db_module.get_db():
+            async for _session in mock_db_module.get_db():
                 raise ValueError("Simulated Error")

Also applies to: 90-91

tests/test_handler_registry.py (1)

19-34: Rename unused handler parameters to avoid Ruff ARG002.

Using _event makes the unused argument explicit and keeps lint clean.

Proposed diff
 class MockHandler(BaseHandler):
     """Concrete handler for testing."""
-    async def handle(self, event: BaseEvent):
+    async def handle(self, _event: BaseEvent):
         return {"handled": True, "handler": "MockHandler"}

 class AnotherMockHandler(BaseHandler):
     """Another concrete handler for testing."""
-    async def handle(self, event: BaseEvent):
+    async def handle(self, _event: BaseEvent):
         return {"handled": True, "handler": "AnotherMockHandler"}

 class DiscordSpecificHandler(BaseHandler):
     """Platform-specific handler for Discord."""
-    async def handle(self, event: BaseEvent):
+    async def handle(self, _event: BaseEvent):
         return {"handled": True, "handler": "DiscordSpecificHandler"}
tests/test_message_handler.py (1)

58-61: Silence the unused tuple element from is_faq.

faq_response isn’t used; renaming avoids lint noise and clarifies intent.

♻️ Suggested tweak
-        is_faq, faq_response = await self.faq_handler.is_faq(content)
+        is_faq, _faq_response = await self.faq_handler.is_faq(content)
tests/test_faq_handler.py (4)

13-13: Remove unused imports.

ABC and abstractmethod are imported but not used in this file.

-from abc import ABC, abstractmethod

72-77: Avoid silent exception swallowing.

The bare except Exception: pass silently swallows all errors, making debugging difficult. Even in a test double, consider logging or at least catching a more specific exception type.

🛡️ Suggested improvement
     try:
         channel = self.bot.get_channel(int(channel_id))
         if channel:
             await channel.send(response)
-    except Exception:
-        pass
+    except (ValueError, AttributeError):
+        # ValueError: invalid channel_id conversion
+        # AttributeError: bot/channel API issues
+        pass

26-31: Consider annotating mutable class attribute with ClassVar.

Static analysis (RUF012) flags that mutable class attributes should be annotated. Since this dict is intended as a constant lookup table, using ClassVar makes the intent explicit.

♻️ Suggested fix
+from typing import ClassVar
+
 class FAQHandlerTestDouble:
     """
     Test double mirroring the production FAQHandler implementation.
     This avoids circular import issues while testing the FAQ pattern.
     """
     
-    FAQ_RESPONSES = {
+    FAQ_RESPONSES: ClassVar[dict[str, str]] = {
         "what is devr.ai?": "Devr.AI is an AI-powered Developer Relations assistant.",

115-141: Replace deprecated asyncio.get_event_loop().run_until_complete() with pytest.mark.asyncio.

The get_event_loop() pattern is deprecated since Python 3.10. Since pytest 8.x and pytest-asyncio are available, use native async test functions instead.

♻️ Example refactor for one test
+@pytest.mark.asyncio
-def test_is_faq_returns_true_for_known_question(self, handler):
+async def test_is_faq_returns_true_for_known_question(self, handler):
     """is_faq returns (True, response) for known FAQ."""
-    result = asyncio.get_event_loop().run_until_complete(handler.is_faq("what is devr.ai?"))
+    result = await handler.is_faq("what is devr.ai?")
     
     assert result[0] is True
     assert result[1] is not None
     assert "AI-powered" in result[1]

Apply this pattern to all async test methods in this file.

Copy link
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

🤖 Fix all issues with AI agents
In `@tests/test_db_pool.py`:
- Around line 59-63: Rename the unused loop variable in the async generators to
silence Ruff B007: in the async def task() where you iterate "async for session
in mock_db_module.get_db():" (and the other occurrence later in the file),
change the loop variable name to "_session" so the variable is clearly marked as
intentionally unused; update both places that consume mock_db_module.get_db() to
use "_session" instead of "session".
🧹 Nitpick comments (4)
tests/test_faq_handler.py (4)

13-13: Unused import ABC.

The ABC import from abc module is not used in this file. Consider removing it.

-from abc import ABC, abstractmethod
+from unittest.mock import MagicMock, AsyncMock

Wait, abstractmethod is also unused. Both can be removed:

-from abc import ABC, abstractmethod

26-31: Consider annotating mutable class attribute with ClassVar.

Static analysis flagged that mutable class attributes should be annotated with typing.ClassVar to indicate they belong to the class, not instances.

✨ Suggested fix
+from typing import ClassVar
+
 class FAQHandlerTestDouble:
     """
     Test double mirroring the production FAQHandler implementation.
     This avoids circular import issues while testing the FAQ pattern.
     """
     
-    FAQ_RESPONSES = {
+    FAQ_RESPONSES: ClassVar[dict[str, str]] = {
         "what is devr.ai?": "Devr.AI is an AI-powered Developer Relations assistant.",

72-77: Silent exception swallowing reduces test fidelity.

The bare except Exception: pass silently swallows all errors, including unexpected ones like TypeError or AttributeError. If the production FAQHandler logs errors, this test double should mirror that behavior. At minimum, consider catching more specific exceptions.

🛡️ Proposed improvement
+import logging
+
+logger = logging.getLogger(__name__)
+
     async def _send_discord_response(self, channel_id: str, response: str):
         """Send a response to Discord channel."""
         if self.bot is None:
             return
         
         try:
             channel = self.bot.get_channel(int(channel_id))
             if channel:
                 await channel.send(response)
-        except Exception:
-            pass
+        except (ValueError, AttributeError) as e:
+            logger.debug("Failed to send Discord response: %s", e)

115-122: Use pytest.mark.asyncio instead of deprecated event loop pattern.

The asyncio.get_event_loop().run_until_complete() pattern is deprecated since Python 3.10 and emits DeprecationWarning. Since pytest-asyncio is already a dependency, convert these to native async tests for cleaner, more idiomatic code.

This applies to all async test methods in this file (11 tests total).

♻️ Example conversion for this test
+    `@pytest.mark.asyncio`
-    def test_is_faq_returns_true_for_known_question(self, handler):
+    async def test_is_faq_returns_true_for_known_question(self, handler):
         """is_faq returns (True, response) for known FAQ."""
-        result = asyncio.get_event_loop().run_until_complete(handler.is_faq("what is devr.ai?"))
+        result = await handler.is_faq("what is devr.ai?")
         
         assert result[0] is True
         assert result[1] is not None
         assert "AI-powered" in result[1]

Apply the same pattern to:

  • test_is_faq_returns_false_for_unknown_question
  • test_is_faq_case_insensitive
  • test_is_faq_how_do_i_contribute
  • test_handle_faq_requested_event
  • test_handle_knowledge_updated_event
  • test_handle_unsupported_event_type
  • test_send_discord_response_with_bot
  • test_send_discord_response_without_bot
  • test_send_discord_response_channel_not_found

@muhammadtihame
Copy link
Author

Update: I’ve pushed fixes addressing all CodeRabbit feedback.

  • Updated documentation paths to match repo layout
  • Aligned SQLAlchemy versions across pyproject.toml and requirements.txt
  • Fixed Ruff lint issues (B007, ARG002, unused imports)
  • Improved exception handling (no silent swallowing)
  • Migrated async tests to pytest.mark.asyncio
  • Added ClassVar annotations where required

All tests pass locally:
74 passed, 3 warnings

Looking forward to your feedback, Thanks.

- test_weaviate.py: Remove extra quote chars causing parse error (L44,57,76)
- test_supabase.py: Remove non-existent CodeChunk import

All 74 unit tests pass.
Copy link
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

🤖 Fix all issues with AI agents
In `@tests/test_supabase.py`:
- Line 1: The test import line removed CodeChunk which causes a NameError later;
restore CodeChunk to the import list from backend.app.models.database.supabase
so the file imports "CodeChunk" alongside "User, Interaction, Repository"
(update the statement that currently references User, Interaction, Repository to
include CodeChunk) to match its later usage in tests.

@muhammadtihame
Copy link
Author

Thanks for catching that, CodeChunk is referenced later in the test.
I’ve restored the import and verified the tests pass.

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