Skip to content

LCORE-716: integration tests for conversation management endpoints#1362

Open
anik120 wants to merge 1 commit intolightspeed-core:mainfrom
anik120:integration-test-conversation-endpoints
Open

LCORE-716: integration tests for conversation management endpoints#1362
anik120 wants to merge 1 commit intolightspeed-core:mainfrom
anik120:integration-test-conversation-endpoints

Conversation

@anik120
Copy link
Copy Markdown
Contributor

@anik120 anik120 commented Mar 19, 2026

Description

This PR introduces comprehensive integration tests for conversation management endpoints in both v1 (database + Llama Stack) and v2 (cache-based) APIs.

Test Coverage:

  • List conversations (GET /v{1,2}/conversations)
  • Get conversation details (GET /v{1,2}/conversations/{id})
  • Delete conversation (DELETE /v{1,2}/conversations/{id})
  • Update conversation (PUT /v{1,2}/conversations/{id})

Scenarios Tested:

  • Success paths with real database/cache integration
  • Error handling (invalid IDs, not found, connection errors, API errors)
  • Edge cases (empty lists, non-existent resources, cache unavailable)
  • Special features (tool calls, turn metadata, multi-turn conversations)

Notes

  • Tests use real database sessions (SQLite in-memory) and cache instances for true integration testing
  • Only external Llama Stack client is mocked to avoid external dependencies

Type of change

  • Refactor
  • New feature
  • Bug fix
  • CVE fix
  • Optimization
  • Documentation Update
  • Configuration Update
  • Bump-up service version
  • Bump-up dependent library
  • Bump-up library or tool used for development (does not change the final image)
  • CI configuration change
  • Konflux configuration change
  • Unit tests improvement
  • Integration tests improvement
  • End to end tests improvement
  • Benchmarks improvement

Tools used to create PR

Identify any AI code assistants used in this PR (for transparency and review context)

  • Assisted-by: Claude

Related Tickets & Documents

  • Related Issue #
  • Closes #

Checklist before requesting a review

  • I have performed a self-review of my code.
  • PR has passed all pre-merge test jobs.
  • If it is a core feature, I have added thorough tests.

Testing

All 33 integration tests introduced here pass in 0.62s:

$ uv run pytest tests/integration/endpoints/test_conversations_v1_integration.py tests/integration/endpoints/test_conversations_v2_integration.py -v --tb=short 
  ⎿  ============================= test session starts ==============================                                                                             
     platform darwin -- Python 3.13.7, pytest-9.0.2, pluggy-1.6.0 -- /Users/anbhatta/go/src/github.com/lightspeed/core/lightspeed-stack/.venv/bin/python3         
     cachedir: .pytest_cache                                                                                                                                      
     benchmark: 5.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False       
  warmup_iterations=100000)                                                                                                                                       
     rootdir: /Users/anbhatta/go/src/github.com/lightspeed/core/lightspeed-stack                                                                                  
     configfile: pyproject.toml                                                                                                                                   
     plugins: benchmark-5.2.3, mock-3.15.1, cov-7.1.0, asyncio-1.3.0, anyio-4.13.0                                                                                
     asyncio: mode=Mode.AUTO, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function                                      
     collecting ... collected 33 items                                                                                                                            
                                                                                                                                                                  
     tests/integration/endpoints/test_conversations_v1_integration.py::test_list_conversations_returns_user_conversations PASSED [  3%]                           
     tests/integration/endpoints/test_conversations_v1_integration.py::test_get_conversation_returns_chat_history PASSED [  6%]                                   
     tests/integration/endpoints/test_conversations_v1_integration.py::test_get_conversation_invalid_id_format_returns_400 PASSED [  9%]                          
     tests/integration/endpoints/test_conversations_v1_integration.py::test_get_conversation_not_found_returns_404 PASSED [ 12%]                                  
     tests/integration/endpoints/test_conversations_v1_integration.py::test_get_conversation_handles_connection_error PASSED [ 15%]                               
     tests/integration/endpoints/test_conversations_v1_integration.py::test_get_conversation_handles_api_status_error PASSED [ 18%]                               
     tests/integration/endpoints/test_conversations_v1_integration.py::test_get_conversation_with_turns_metadata PASSED [ 21%]                                    
     tests/integration/endpoints/test_conversations_v1_integration.py::test_delete_conversation_deletes_from_database_and_llama_stack PASSED [ 24%]               
     tests/integration/endpoints/test_conversations_v1_integration.py::test_delete_conversation_invalid_id_format_returns_400 PASSED [ 27%]                       
     tests/integration/endpoints/test_conversations_v1_integration.py::test_delete_conversation_handles_connection_error PASSED [ 30%]                            
     tests/integration/endpoints/test_conversations_v1_integration.py::test_delete_conversation_handles_not_found_in_llama_stack PASSED [ 33%]                    
     tests/integration/endpoints/test_conversations_v1_integration.py::test_delete_conversation_non_existent_returns_success PASSED [ 36%]                        
     tests/integration/endpoints/test_conversations_v1_integration.py::test_update_conversation_updates_topic_summary PASSED [ 39%]                               
     tests/integration/endpoints/test_conversations_v1_integration.py::test_update_conversation_invalid_id_format_returns_400 PASSED [ 42%]                       
     tests/integration/endpoints/test_conversations_v1_integration.py::test_update_conversation_not_found_returns_404 PASSED [ 45%]                               
     tests/integration/endpoints/test_conversations_v1_integration.py::test_update_conversation_handles_connection_error PASSED [ 48%]                            
     tests/integration/endpoints/test_conversations_v1_integration.py::test_update_conversation_handles_api_status_error PASSED [ 51%]                            
     tests/integration/endpoints/test_conversations_v2_integration.py::test_list_conversations_filters_by_user_id PASSED [ 54%]                                   
     tests/integration/endpoints/test_conversations_v2_integration.py::test_list_conversations_handles_cache_unavailable PASSED [ 57%]                            
     tests/integration/endpoints/test_conversations_v2_integration.py::test_get_conversation_returns_chat_history PASSED [ 60%]                                   
     tests/integration/endpoints/test_conversations_v2_integration.py::test_get_conversation_invalid_id_format_returns_400 PASSED [ 63%]                          
     tests/integration/endpoints/test_conversations_v2_integration.py::test_get_conversation_not_found_returns_404 PASSED [ 66%]                                  
     tests/integration/endpoints/test_conversations_v2_integration.py::test_get_conversation_handles_cache_unavailable PASSED [ 69%]                              
     tests/integration/endpoints/test_conversations_v2_integration.py::test_get_conversation_with_tool_calls PASSED [ 72%]                                        
     tests/integration/endpoints/test_conversations_v2_integration.py::test_delete_conversation_removes_from_cache PASSED [ 75%]                                  
     tests/integration/endpoints/test_conversations_v2_integration.py::test_delete_conversation_invalid_id_format_returns_400 PASSED [ 78%]                       
     tests/integration/endpoints/test_conversations_v2_integration.py::test_delete_conversation_non_existent_returns_success PASSED [ 81%]                        
     tests/integration/endpoints/test_conversations_v2_integration.py::test_delete_conversation_handles_cache_unavailable PASSED [ 84%]                           
     tests/integration/endpoints/test_conversations_v2_integration.py::test_update_conversation_updates_topic_summary PASSED [ 87%]                               
     tests/integration/endpoints/test_conversations_v2_integration.py::test_update_conversation_invalid_id_format_returns_400 PASSED [ 90%]                       
     tests/integration/endpoints/test_conversations_v2_integration.py::test_update_conversation_not_found_returns_404 PASSED [ 93%]                               
     tests/integration/endpoints/test_conversations_v2_integration.py::test_update_conversation_handles_cache_unavailable PASSED [ 96%]                           
     tests/integration/endpoints/test_conversations_v2_integration.py::test_update_conversation_with_multiple_turns PASSED [100%]                                 
                                                                                                                                                                  
     ============================== 33 passed in 0.62s ==============================

Summary by CodeRabbit

  • Tests
    • Added comprehensive integration tests for Conversations API v1 and v2 covering list, get, update, and delete flows.
    • v1 tests validate DB-backed conversations and remote model interactions; v2 tests exercise an in-memory conversation cache with multi-turn history, tool calls/results, referenced documents, and topic summaries.
    • Tests cover success paths, error mappings (400/404/500/503), idempotent deletes, and a non-admin request fixture limiting conversation permissions.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 19, 2026

Walkthrough

Adds three new integration test modules: v1 and v2 conversation endpoint suites and a test fixture. v1 tests mock an AsyncLlamaStack client and override DB engine/session; v2 tests exercise an in-memory SQLite conversation cache. Tests cover list/get/delete/update and error paths.

Changes

Cohort / File(s) Summary
Conversations v1 integration tests
tests/integration/endpoints/test_conversations_v1_integration.py
New integration tests for /v1/conversations that seed UserConversation/UserTurn rows, mock AsyncLlamaStackClientHolder, override app.database.engine/session_local, and cover list/get/delete/update behaviors including chat_history assembly and Llama Stack error mappings (400/404/500/503) and idempotent delete.
Conversations v2 integration tests (SQLite cache)
tests/integration/endpoints/test_conversations_v2_integration.py
New integration tests for /v2/conversations using an autouse setup_conversation_cache fixture that creates an in-memory SQLiteCache. Adds create_test_cache_entry helper and tests list/get/delete/update flows, multi-turn/chat history, tool calls/results, referenced documents, and cache-unavailable/error cases (400/404/500) including idempotency.
Test fixtures
tests/integration/conftest.py
Adds non_admin_test_request fixture which yields test_request while patching authorization.resolvers.NoopAccessResolver.get_actions to return a restricted set of actions for tests (LIST/GET/DELETE/UPDATE_CONVERSATION).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: adding integration tests for conversation management endpoints, with explicit reference to the ticket number.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
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 (2)
tests/integration/endpoints/test_conversations_v1_integration.py (1)

548-624: Assert the turn metadata this test seeds.

Right now this only proves chat_history is present. If provider, model, or the turn timestamps stopped being returned, the test would still pass.

Suggested assertions
     # Verify response includes turn metadata
     assert response.conversation_id == TEST_CONVERSATION_ID
     assert response.chat_history is not None
+    assert len(response.chat_history) == 1
+
+    turn = response.chat_history[0]
+    assert turn.provider == "test-provider"
+    assert turn.model == "test-model"
+    assert turn.started_at is not None
+    assert turn.completed_at is not None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/endpoints/test_conversations_v1_integration.py` around
lines 548 - 624, The test_get_conversation_with_turns_metadata currently only
checks response.conversation_id and that response.chat_history exists; update it
to assert the seeded turn metadata is returned by
get_conversation_endpoint_handler by verifying response.chat_history contains a
turn for TEST_CONVERSATION_ID with the expected provider ("test-provider"),
model ("test-model"), and the timestamps (started_at and completed_at) match or
are present on the returned turn objects; ensure you reference the response
object from get_conversation_endpoint_handler and its chat_history items when
adding these assertions so the test will fail if provider, model, or turn
timestamps stop being returned.
tests/integration/endpoints/test_conversations_v2_integration.py (1)

90-127: Keep CacheEntry fixtures aligned with the model contract.

models/cache_entry.py only defines the query/response/provider/model/timestamp/tool fields. Passing conversation_id, user_id, and topic_summary into CacheEntry(...) makes these fixtures depend on data the cache entry model does not own, which is easy to misread as “topic summary is being persisted here” when it is not.

Also applies to: 516-550

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/endpoints/test_conversations_v2_integration.py` around
lines 90 - 127, The test fixture create_test_cache_entry is constructing a
CacheEntry with fields (conversation_id, user_id, topic_summary) that are not
part of the CacheEntry contract in models/cache_entry.py; update
create_test_cache_entry to only pass the model-owned fields (query, response,
provider, model, referenced_documents, tool_calls, tool_results, started_at,
completed_at) when instantiating CacheEntry and remove conversation_id, user_id,
and topic_summary from the CacheEntry(...) call (if tests need those values keep
them as separate local variables or return a tuple/dict alongside the
CacheEntry), ensuring all other usages of create_test_cache_entry are adjusted
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/integration/endpoints/test_conversations_v1_integration.py`:
- Around line 488-544: The test asserts that an APIStatusError from
mock_llama_stack_client.conversations.items.list should map to a 500, but
get_conversation_endpoint_handler (see conversations_v1.py mapping around the
APIStatusError handling) currently maps APIStatusError with
response.status_code=404 to a 404; update the test to expect HTTP 404 instead of
500 and verify the error detail shape accordingly (i.e., assert
exc_info.value.status_code == status.HTTP_404_NOT_FOUND and that
exc_info.value.detail is the expected dict/structure).

---

Nitpick comments:
In `@tests/integration/endpoints/test_conversations_v1_integration.py`:
- Around line 548-624: The test_get_conversation_with_turns_metadata currently
only checks response.conversation_id and that response.chat_history exists;
update it to assert the seeded turn metadata is returned by
get_conversation_endpoint_handler by verifying response.chat_history contains a
turn for TEST_CONVERSATION_ID with the expected provider ("test-provider"),
model ("test-model"), and the timestamps (started_at and completed_at) match or
are present on the returned turn objects; ensure you reference the response
object from get_conversation_endpoint_handler and its chat_history items when
adding these assertions so the test will fail if provider, model, or turn
timestamps stop being returned.

In `@tests/integration/endpoints/test_conversations_v2_integration.py`:
- Around line 90-127: The test fixture create_test_cache_entry is constructing a
CacheEntry with fields (conversation_id, user_id, topic_summary) that are not
part of the CacheEntry contract in models/cache_entry.py; update
create_test_cache_entry to only pass the model-owned fields (query, response,
provider, model, referenced_documents, tool_calls, tool_results, started_at,
completed_at) when instantiating CacheEntry and remove conversation_id, user_id,
and topic_summary from the CacheEntry(...) call (if tests need those values keep
them as separate local variables or return a tuple/dict alongside the
CacheEntry), ensuring all other usages of create_test_cache_entry are adjusted
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 552e05f7-46cf-405c-a40e-eeab62ea6e0b

📥 Commits

Reviewing files that changed from the base of the PR and between 55c4543 and 0eb3eca.

📒 Files selected for processing (2)
  • tests/integration/endpoints/test_conversations_v1_integration.py
  • tests/integration/endpoints/test_conversations_v2_integration.py

Copy link
Copy Markdown
Contributor

@tisnik tisnik left a comment

Choose a reason for hiding this comment

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

Please create 1st PR with refactoring of existing fixtures. Then these tests will be a bit shorter. Then we can look howto make these tests even shorter by refactoring common patterns from them.



@pytest.fixture(name="mock_llama_stack_client")
def mock_llama_stack_client_fixture(
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.

would be nice to refactor it out, as it is used in other tests too. So you can make module with this fixture

yield mock_client


@pytest.fixture(name="patch_db_session", autouse=True)
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.

again, probably can be refactored. In this case, it needs to be decided it autouse can still be used

Copy link
Copy Markdown
Contributor Author

@anik120 anik120 left a comment

Choose a reason for hiding this comment

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

Peer review feedback from @radofuchs


# Verify response indicates success (local deletion succeeded)
assert response.conversation_id == TEST_CONVERSATION_ID
assert response.success is True
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Check for 200 status code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Looks like the handler returning without exception indicates HTTP 200 here, there's no explicit 200 status code to be checked here @radofuchs

When the handler is called directly, response = await delete_conversation_endpoint_handler(...) we get back a ConversationDeleteResponse model object, not an HTTP response. FastAPI only adds the HTTP status code when the endpoint is called via an actual HTTP request.

@anik120 anik120 force-pushed the integration-test-conversation-endpoints branch from 0eb3eca to c757eeb Compare March 27, 2026 18:12
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

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

Inline comments:
In `@tests/integration/conftest.py`:
- Around line 178-195: Replace the banned unittest.mock.patch usage by accepting
the pytest-mock fixture and using mocker.patch as a context manager: add the
pytest fixture parameter (mocker) to the test function/fixture that yields
test_request, remove the import of unittest.mock.patch, and change the with
patch("authorization.resolvers.NoopAccessResolver.get_actions",
return_value=standard_actions): block to use with
mocker.patch("authorization.resolvers.NoopAccessResolver.get_actions",
return_value=standard_actions): so the NoopAccessResolver.get_actions is stubbed
with standard_actions via pytest-mock.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 60fefba2-83b9-409e-b146-cc5b4dc91859

📥 Commits

Reviewing files that changed from the base of the PR and between 0eb3eca and c757eeb.

📒 Files selected for processing (3)
  • tests/integration/conftest.py
  • tests/integration/endpoints/test_conversations_v1_integration.py
  • tests/integration/endpoints/test_conversations_v2_integration.py
✅ Files skipped from review due to trivial changes (1)
  • tests/integration/endpoints/test_conversations_v2_integration.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/integration/endpoints/test_conversations_v1_integration.py

@anik120 anik120 force-pushed the integration-test-conversation-endpoints branch from c757eeb to 3abadd4 Compare March 27, 2026 18:27
This PR introduces comprehensive integration tests for conversation management endpoints in
both v1 (database + Llama Stack) and v2 (cache-based) APIs.

**Test Coverage:**
- List conversations (GET `/v{1,2}/conversations`)
- Get conversation details (GET `/v{1,2}/conversations/{id}`)
- Delete conversation (DELETE `/v{1,2}/conversations/{id}`)
- Update conversation (PUT `/v{1,2}/conversations/{id}`)

**Scenarios Tested:**
- Success paths with real database/cache integration
- Error handling (invalid IDs, not found, connection errors, API errors)
- Edge cases (empty lists, non-existent resources, cache unavailable)
- Special features (tool calls, turn metadata, multi-turn conversations)

**Notes**
- Tests use real database sessions (SQLite in-memory) and cache instances for true integration testing
- Only external Llama Stack client is mocked to avoid external dependencies

Signed-off-by: Anik Bhattacharjee <anbhatta@redhat.com>
@anik120 anik120 force-pushed the integration-test-conversation-endpoints branch from 3abadd4 to 026a639 Compare March 27, 2026 18:37
@anik120
Copy link
Copy Markdown
Contributor Author

anik120 commented Mar 27, 2026

Fyi @radofuchs I had to introduce a new non_admin_test_request fixture in conftest.py.

Since test_request had no permissions set, when it was used with endpoints that have the @authorize decorator, the decorator calls the authorization system to set permissions. Since NoopAuth authentication is used in integration tests, NoopAccessResolver.get_actions() returns ALL actions.

def get_actions(self, user_roles: UserRoles) -> set[Action]:                                                                                                    
      return set(Action) - {Action.ADMIN}  # Returns everything

This includes elevated "OTHERS" permissions like:
- LIST_OTHERS_CONVERSATIONS
- DELETE_OTHERS_CONVERSATIONS
- READ_OTHERS_CONVERSATIONS
- etc.

As a result it was not possible to use that to test authenticated users' permissions.

The new non_admin_test_request fixture patches NoopAccessResolver.get_actions() to return only standard user actions:

 standard_actions = {                                                                                                                                            
    Action.LIST_CONVERSATIONS,
    Action.GET_CONVERSATION,                                                                                                                                    
    Action.DELETE_CONVERSATION,                                                                                                                                 
    Action.UPDATE_CONVERSATION,                                                                                                                                 
}

that excludes all "OTHERS" elevated permissions. That allowed for testing that users can only access their own data.

@tisnik
Copy link
Copy Markdown
Contributor

tisnik commented Mar 29, 2026

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Wrong response field

In the non-existent delete test, the code asserts against response.response even though the handler likely returns a message field. This typo will cause an AttributeError and prevent the test from validating the deletion message.

# Verify response (note: success is always True per implementation)
assert response.conversation_id == NON_EXISTENT_CONVO_ID
assert response.success is True
# Response message indicates deletion status
assert "cannot be deleted" in response.response.lower()
Missing request argument

Several v2 update-conversation tests call update_conversation_endpoint_handler without passing the request parameter. This will result in TypeError due to a missing required positional argument, so the tests cannot execute.

update_request = ConversationUpdateRequest(topic_summary="New topic summary")

response = await update_conversation_endpoint_handler(
    conversation_id=TEST_CONVERSATION_ID,
    update_request=update_request,
    auth=test_auth,
)

# Verify response

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.

2 participants