Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Shared fixtures for integration tests."""

import os
from pathlib import Path
from typing import Generator

import pytest
from fastapi import Request, Response
from fastapi.testclient import TestClient

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
Expand Down Expand Up @@ -157,3 +159,33 @@ async def test_auth_fixture(test_request: Request) -> AuthTuple:
"""
noop_auth = NoopAuthDependency()
return await noop_auth(test_request)


@pytest.fixture(name="integration_http_client")
def integration_http_client_fixture(
test_config: object,
) -> Generator[TestClient, None, None]:
"""Provide a TestClient for the app with integration config.

Use for integration tests that need to send real HTTP requests (e.g. empty
body validation). Depends on test_config so configuration is loaded first.
"""
_ = test_config
config_path = (
Path(__file__).resolve().parent.parent
/ "configuration"
/ "lightspeed-stack.yaml"
)
assert config_path.exists(), f"Config file not found: {config_path}"

original = os.environ.get("LIGHTSPEED_STACK_CONFIG_PATH")
os.environ["LIGHTSPEED_STACK_CONFIG_PATH"] = str(config_path)
try:
from app.main import app # pylint: disable=import-outside-toplevel

yield TestClient(app)
finally:
if original is not None:
os.environ["LIGHTSPEED_STACK_CONFIG_PATH"] = original
else:
os.environ.pop("LIGHTSPEED_STACK_CONFIG_PATH", None)
176 changes: 176 additions & 0 deletions tests/integration/endpoints/test_query_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,182 @@ async def test_query_v2_endpoint_with_attachments(
assert response.response is not None


@pytest.mark.asyncio
async def test_query_v2_endpoint_empty_payload(
test_config: AppConfig,
mock_llama_stack_client: AsyncMockType,
test_request: Request,
test_auth: AuthTuple,
) -> None:
"""Test query v2 endpoint with minimal payload (no attachments).

Verifies that a request with only the required query and no attachments
field does not break the handler and returns 200.
"""
_ = test_config
_ = mock_llama_stack_client

query_request = QueryRequest(query="what is kubernetes?")

response = await query_endpoint_handler(
request=test_request,
query_request=query_request,
auth=test_auth,
mcp_headers={},
)

assert getattr(response, "status_code", status.HTTP_200_OK) == status.HTTP_200_OK
assert response.conversation_id is not None
assert response.response is not None
Comment on lines +357 to +359
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Status code assertion is always true (no-op).

query_endpoint_handler returns a QueryResponse object, not a FastAPI Response. The QueryResponse model doesn't have a status_code attribute, so getattr(response, "status_code", status.HTTP_200_OK) will always return the default 200, making this assertion meaningless.

Either remove the status_code assertion (since the handler doesn't raise means success), or verify the response type directly.

🔧 Proposed fix
-    assert getattr(response, "status_code", status.HTTP_200_OK) == status.HTTP_200_OK
+    # Handler returning without exception indicates success (HTTP 200)
     assert response.conversation_id is not None
     assert response.response is not None
📝 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
assert getattr(response, "status_code", status.HTTP_200_OK) == status.HTTP_200_OK
assert response.conversation_id is not None
assert response.response is not None
# Handler returning without exception indicates success (HTTP 200)
assert response.conversation_id is not None
assert response.response 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_query_integration.py` around lines 357 -
359, The status_code assertion is a no-op because query_endpoint_handler returns
a QueryResponse (which lacks status_code); remove the line asserting
getattr(response, "status_code", status.HTTP_200_OK) == status.HTTP_200_OK and
instead assert the response is the expected model and populated, e.g., assert
isinstance(response, QueryResponse) (or check response.__class__.__name__ ==
"QueryResponse") and keep the existing assertions that response.conversation_id
and response.response are not None to validate success.



@pytest.mark.asyncio
async def test_query_v2_endpoint_empty_attachments_list(
test_config: AppConfig,
mock_llama_stack_client: AsyncMockType,
test_request: Request,
test_auth: AuthTuple,
) -> None:
"""Test query v2 endpoint accepts empty attachment list.

Verifies that POST /v1/query with attachments=[] returns 200 and
application/json response.
"""
_ = test_config
_ = mock_llama_stack_client

query_request = QueryRequest(
query="what is kubernetes?",
attachments=[],
)

response = await query_endpoint_handler(
request=test_request,
query_request=query_request,
auth=test_auth,
mcp_headers={},
)

assert getattr(response, "status_code", status.HTTP_200_OK) == status.HTTP_200_OK
assert response.conversation_id is not None
assert response.response is not None
Comment on lines +389 to +391
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Same no-op status code assertion.

Same issue as above - QueryResponse doesn't have status_code, so the assertion is always true.

🔧 Proposed fix
-    assert getattr(response, "status_code", status.HTTP_200_OK) == status.HTTP_200_OK
     assert response.conversation_id is not None
     assert response.response 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_query_integration.py` around lines 389 -
391, The assertion using getattr(response, "status_code", status.HTTP_200_OK) is
a no-op because QueryResponse has no status_code; remove that assertion and
instead validate the real success condition—either assert the actual HTTP
response object’s status_code (e.g., response_http.status_code ==
status.HTTP_200_OK) if you have access to it, or simply assert properties on the
QueryResponse itself (e.g., isinstance(response, QueryResponse) and
response.conversation_id is not None and response.response is not None); update
the test to reference QueryResponse and the correct HTTP response variable (or
remove the status check) rather than using getattr on response.



@pytest.mark.asyncio
async def test_query_v2_endpoint_multiple_attachments(
test_config: AppConfig,
mock_llama_stack_client: AsyncMockType,
test_request: Request,
test_auth: AuthTuple,
) -> None:
"""Test query v2 endpoint with multiple attachments.

Verifies that two attachments (log + configuration) are accepted
and processed.
"""
_ = test_config
_ = mock_llama_stack_client

query_request = QueryRequest(
query="what is kubernetes?",
attachments=[
Attachment(
attachment_type="log",
content_type="text/plain",
content="log content",
),
Attachment(
attachment_type="configuration",
content_type="application/json",
content='{"key": "value"}',
),
],
)

response = await query_endpoint_handler(
request=test_request,
query_request=query_request,
auth=test_auth,
mcp_headers={},
)

assert getattr(response, "status_code", status.HTTP_200_OK) == status.HTTP_200_OK
assert response.conversation_id is not None
assert response.response is not None
Comment on lines +432 to +434
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Same no-op status code assertion.

Same issue as above - remove or fix the meaningless assertion.

🔧 Proposed fix
-    assert getattr(response, "status_code", status.HTTP_200_OK) == status.HTTP_200_OK
     assert response.conversation_id is not None
     assert response.response 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_query_integration.py` around lines 432 -
434, The first assertion is a no-op because getattr(response, "status_code",
status.HTTP_200_OK) will default to status.HTTP_200_OK and always pass; update
the test in tests/integration/endpoints/test_query_integration.py to assert the
real status code instead (e.g., assert response.status_code ==
status.HTTP_200_OK) or explicitly expect None on missing attribute (e.g.,
getattr(response, "status_code", None) == status.HTTP_200_OK), and otherwise
remove the meaningless assertion; refer to the response object and
status.HTTP_200_OK in the change.



@pytest.mark.asyncio
async def test_query_v2_endpoint_attachment_unknown_type_returns_422(
test_config: AppConfig,
mock_llama_stack_client: AsyncMockType,
test_request: Request,
test_auth: AuthTuple,
) -> None:
"""Test query v2 endpoint returns 422 for unknown attachment type."""
_ = test_config
_ = mock_llama_stack_client

query_request = QueryRequest(
query="what is kubernetes?",
attachments=[
Attachment(
attachment_type="unknown_type",
content_type="text/plain",
content="content",
)
],
)

with pytest.raises(HTTPException) as exc_info:
await query_endpoint_handler(
request=test_request,
query_request=query_request,
auth=test_auth,
mcp_headers={},
)

assert exc_info.value.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT
assert isinstance(exc_info.value.detail, dict)
assert "unknown_type" in exc_info.value.detail["cause"]
assert "Invalid" in exc_info.value.detail["response"]


@pytest.mark.asyncio
async def test_query_v2_endpoint_attachment_unknown_content_type_returns_422(
test_config: AppConfig,
mock_llama_stack_client: AsyncMockType,
test_request: Request,
test_auth: AuthTuple,
) -> None:
"""Test query v2 endpoint returns 422 for unknown attachment content type."""
_ = test_config
_ = mock_llama_stack_client

query_request = QueryRequest(
query="what is kubernetes?",
attachments=[
Attachment(
attachment_type="log",
content_type="unknown/type",
content="content",
)
],
)

with pytest.raises(HTTPException) as exc_info:
await query_endpoint_handler(
request=test_request,
query_request=query_request,
auth=test_auth,
mcp_headers={},
)

assert exc_info.value.status_code == status.HTTP_422_UNPROCESSABLE_CONTENT
assert isinstance(exc_info.value.detail, dict)
assert "unknown/type" in exc_info.value.detail["cause"]
assert "Invalid" in exc_info.value.detail["response"]


# ==========================================
# Tool Integration Tests
# ==========================================
Expand Down
Loading
Loading