-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/rag chat and error handling #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
1251198
style: fix UI broken issues, transition to lucide icons and resolve l…
SanghunYun95 c75e849
style: address CodeRabbit PR feedback for config, a11y, and security
SanghunYun95 5260bef
feat: implement RAG-based chat streaming, dynamic sidebar, and Gemini…
SanghunYun95 20a2e9b
Merge remote-tracking branch 'origin/main' into feat/rag-chat-and-err…
SanghunYun95 1f94581
fix(backend, frontend): resolve review comments, test errors, and ui …
SanghunYun95 99ef72a
style: address second round of CodeRabbit PR feedback for SSE parsing…
SanghunYun95 951a23f
fix: address second round of CodeRabbit PR feedback for backend confi…
SanghunYun95 b0a0e29
Merge main, resolve conflicts, and apply CodeRabbit feedback
SanghunYun95 782e78c
style: address third round of CodeRabbit PR feedback
SanghunYun95 362158c
style: address fourth round of CodeRabbit PR feedback (nitpicks)
SanghunYun95 595e057
style: address fifth round of CodeRabbit PR feedback (nitpicks)
SanghunYun95 db9ecfa
feat: implement cost defense and stateless history
SanghunYun95 57ead53
fix: resolve CodeRabbit review feedback, fix merge conflicts and test…
SanghunYun95 f6ebeea
fix: apply CodeRabbit PR review feedback
SanghunYun95 ba8092d
fix: apply additional CodeRabbit PR review feedback
SanghunYun95 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| from slowapi import Limiter | ||
| from slowapi.util import get_remote_address | ||
| from starlette.requests import Request | ||
|
|
||
| def get_real_client_ip(request: Request) -> str: | ||
| """Get real client IP considering X-Forwarded-For header in a proxy environment.""" | ||
| forwarded = request.headers.get("X-Forwarded-For") | ||
| if forwarded: | ||
| return forwarded.split(",")[0].strip() | ||
| return get_remote_address(request) | ||
|
|
||
| limiter = Limiter(key_func=get_real_client_ip) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| pytest-asyncio>=0.23.0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import os | ||
| import pytest | ||
| import asyncio | ||
|
|
||
| @pytest.fixture(autouse=True) | ||
| def mock_env_vars(): | ||
| """Set dummy environment variables for tests to prevent import errors.""" | ||
| os.environ["GEMINI_API_KEY"] = "dummy_test_key" | ||
| os.environ["SUPABASE_URL"] = "http://localhost:8000" | ||
| os.environ["SUPABASE_SERVICE_KEY"] = "dummy_service_key" | ||
|
|
||
| @pytest.fixture(scope="session") | ||
| def event_loop(): | ||
| """Create a single event loop instance shared across the entire test session. | ||
| Scoped to session to prevent global client implementations (e.g. Supabase Python client) | ||
| from binding `asyncio.locks.Event` instances to closed function-scoped event loops.""" | ||
| loop = asyncio.get_event_loop_policy().new_event_loop() | ||
| yield loop | ||
| loop.close() | ||
|
|
||
| @pytest.fixture(autouse=True) | ||
| def reset_sse_starlette_appstatus(): | ||
| """Reset the global AppStatus event in sse-starlette to prevent event loop leakage between tests.""" | ||
| import sse_starlette.sse | ||
| sse_starlette.sse.AppStatus.should_exit_event = None | ||
| yield | ||
|
|
||
| @pytest.fixture(autouse=True) | ||
| def reset_rate_limiter_storage(): | ||
| """Reset the rate limiter's storage between tests so that asyncio Locks are not shared across TestClient event loops.""" | ||
| from app.core.rate_limit import limiter | ||
| from limits.storage import MemoryStorage | ||
|
|
||
| # limits 3.x MemoryStorage creates an asyncio.Lock internally. | ||
| # Resetting it forces re-bind to current test context. | ||
| limiter._storage = MemoryStorage() | ||
| yield |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| from fastapi.testclient import TestClient | ||
| import pytest | ||
| from app.main import app | ||
| from unittest.mock import patch | ||
|
|
||
| @patch("app.api.routes.chat.generate_chat_events") | ||
| def test_chat_rate_limiting(mock_events): | ||
| """Test that the chat endpoint correctly limits requests to 5 per minute.""" | ||
|
|
||
| # Mock event generator to bypass ML initializations and remote calls | ||
| async def mock_generator(*_args, **_kwargs): | ||
| yield "data: ok\n\n" | ||
| def _mock_events_factory(*_args, **_kwargs): | ||
| return mock_generator() | ||
| mock_events.side_effect = _mock_events_factory | ||
|
|
||
| # We will use the synchronous TestClient to avoid asyncio event loop leaking from SSE Streams | ||
| with TestClient(app) as client: | ||
| # Define a unique client IP for this test to avoid interfering with other tests | ||
| headers = {"X-Forwarded-For": "192.168.1.100"} | ||
|
|
||
| # Send 5 requests which should succeed | ||
| for _ in range(5): | ||
| # We use stream matching to verify successful request dispatching | ||
| with client.stream("POST", "/api/v1/chat", json={"query": "Test"}, headers=headers) as response: | ||
| assert response.status_code == 200 | ||
|
|
||
| # The 6th request should fail with 429 Too Many Requests | ||
| response = client.post("/api/v1/chat", json={"query": "Test"}, headers=headers) | ||
| assert response.status_code == 429 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.