Skip to content

Commit f4f6684

Browse files
authored
Merge pull request lightspeed-core#703 from radofuchs/LCORE_712_info_endpoint_ITs
LCORE-712: add integration tests for info endpoint
2 parents bf05cea + 4198dcb commit f4f6684

File tree

3 files changed

+231
-0
lines changed

3 files changed

+231
-0
lines changed

tests/integration/conftest.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""Shared fixtures for integration tests."""
2+
3+
from pathlib import Path
4+
5+
import pytest
6+
from sqlalchemy import create_engine
7+
from sqlalchemy.orm import sessionmaker
8+
9+
from configuration import configuration
10+
from models.database.base import Base
11+
12+
13+
@pytest.fixture(autouse=True)
14+
def reset_configuration_state():
15+
"""Reset configuration state before each integration test.
16+
17+
This autouse fixture ensures test independence by resetting the
18+
singleton configuration state before each test runs. This allows
19+
tests to verify both loaded and unloaded configuration states
20+
regardless of execution order.
21+
"""
22+
# pylint: disable=protected-access
23+
configuration._configuration = None
24+
yield
25+
26+
27+
@pytest.fixture(name="test_config", scope="function")
28+
def test_config_fixture():
29+
"""Load real configuration for integration tests.
30+
31+
This fixture loads the actual configuration file used in testing,
32+
demonstrating integration with the configuration system.
33+
"""
34+
config_path = (
35+
Path(__file__).parent.parent / "configuration" / "lightspeed-stack.yaml"
36+
)
37+
assert config_path.exists(), f"Config file not found: {config_path}"
38+
39+
# Load configuration
40+
configuration.load_configuration(str(config_path))
41+
42+
yield configuration
43+
# Note: Cleanup is handled by the autouse reset_configuration_state fixture
44+
45+
46+
@pytest.fixture(name="test_db_engine", scope="function")
47+
def test_db_engine_fixture():
48+
"""Create an in-memory SQLite database engine for testing.
49+
50+
This provides a real database (not mocked) for integration tests.
51+
Each test gets a fresh database.
52+
"""
53+
# Create in-memory SQLite database
54+
engine = create_engine(
55+
"sqlite:///:memory:",
56+
echo=False, # Set to True to see SQL queries
57+
connect_args={"check_same_thread": False}, # Allow multi-threaded access
58+
)
59+
60+
# Create all tables
61+
Base.metadata.create_all(engine)
62+
63+
yield engine
64+
65+
# Cleanup
66+
Base.metadata.drop_all(engine)
67+
engine.dispose()
68+
69+
70+
@pytest.fixture(name="test_db_session", scope="function")
71+
def test_db_session_fixture(test_db_engine):
72+
"""Create a database session for testing.
73+
74+
Provides a real database session connected to the in-memory test database.
75+
"""
76+
session_local = sessionmaker(autocommit=False, autoflush=False, bind=test_db_engine)
77+
session = session_local()
78+
79+
yield session
80+
81+
session.close()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Integration tests for API endpoints."""
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
"""Integration tests for the /info endpoint."""
2+
3+
import pytest
4+
from fastapi import HTTPException, Request, status
5+
from llama_stack_client import APIConnectionError
6+
from llama_stack_client.types import VersionInfo
7+
8+
from app.endpoints.info import info_endpoint_handler
9+
from authentication.noop import NoopAuthDependency
10+
from version import __version__
11+
12+
13+
@pytest.fixture(name="mock_llama_stack_client")
14+
def mock_llama_stack_client_fixture(mocker):
15+
"""Mock only the external Llama Stack client.
16+
17+
This is the only external dependency we mock for integration tests,
18+
as it represents an external service call.
19+
"""
20+
mock_holder_class = mocker.patch("app.endpoints.info.AsyncLlamaStackClientHolder")
21+
22+
mock_client = mocker.AsyncMock()
23+
# Mock the version endpoint to return a known version
24+
mock_client.inspect.version.return_value = VersionInfo(version="0.2.22")
25+
26+
# Create a mock holder instance
27+
mock_holder_instance = mock_holder_class.return_value
28+
mock_holder_instance.get_client.return_value = mock_client
29+
30+
yield mock_client
31+
32+
33+
@pytest.fixture(name="test_request")
34+
def test_request_fixture():
35+
"""Create a test FastAPI Request object with proper scope."""
36+
return Request(
37+
scope={
38+
"type": "http",
39+
"query_string": b"",
40+
"headers": [],
41+
}
42+
)
43+
44+
45+
@pytest.fixture(name="test_auth")
46+
async def test_auth_fixture(test_request):
47+
"""Create authentication using real noop auth module.
48+
49+
This uses the actual NoopAuthDependency instead of mocking,
50+
making this a true integration test.
51+
"""
52+
noop_auth = NoopAuthDependency()
53+
return await noop_auth(test_request)
54+
55+
56+
@pytest.mark.asyncio
57+
async def test_info_endpoint_returns_service_information(
58+
test_config, mock_llama_stack_client, test_request, test_auth
59+
):
60+
"""Test that info endpoint returns correct service information.
61+
62+
This integration test verifies:
63+
- Endpoint handler integrates with configuration system
64+
- Configuration values are correctly accessed
65+
- Llama Stack client is properly called
66+
- Real noop authentication is used
67+
- Response structure matches expected format
68+
69+
Args:
70+
test_config: Loads real configuration (required for endpoint to access config)
71+
mock_llama_stack_client: Mocked Llama Stack client
72+
test_request: FastAPI request
73+
test_auth: noop authentication tuple
74+
"""
75+
# Fixtures with side effects (needed but not directly used)
76+
_ = test_config
77+
78+
response = await info_endpoint_handler(auth=test_auth, request=test_request)
79+
80+
# Verify values from real configuration
81+
assert response.name == "foo bar baz" # From lightspeed-stack.yaml
82+
assert response.service_version == __version__
83+
assert response.llama_stack_version == "0.2.22"
84+
85+
# Verify the Llama Stack client was called
86+
mock_llama_stack_client.inspect.version.assert_called_once()
87+
88+
89+
@pytest.mark.asyncio
90+
async def test_info_endpoint_handles_connection_error(
91+
test_config, mock_llama_stack_client, test_request, test_auth, mocker
92+
):
93+
"""Test that info endpoint properly handles Llama Stack connection errors.
94+
95+
This integration test verifies:
96+
- Error handling when external service is unavailable
97+
- HTTPException is raised with correct status code
98+
- Error response includes proper error details
99+
100+
Args:
101+
test_config: Loads real configuration (required for endpoint to access config)
102+
mock_llama_stack_client: Mocked Llama Stack client
103+
test_request: FastAPI request
104+
test_auth: noop authentication tuple
105+
mocker: pytest-mock fixture for creating mocks
106+
"""
107+
# test_config fixture loads configuration, which is required for the endpoint
108+
_ = test_config
109+
# Configure mock to raise connection error
110+
mock_llama_stack_client.inspect.version.side_effect = APIConnectionError(
111+
request=mocker.Mock()
112+
)
113+
114+
# Verify that HTTPException is raised
115+
with pytest.raises(HTTPException) as exc_info:
116+
await info_endpoint_handler(auth=test_auth, request=test_request)
117+
118+
# Verify error details
119+
assert exc_info.value.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
120+
assert isinstance(exc_info.value.detail, dict)
121+
assert exc_info.value.detail["response"] == "Unable to connect to Llama Stack"
122+
assert "cause" in exc_info.value.detail
123+
124+
125+
@pytest.mark.asyncio
126+
async def test_info_endpoint_uses_configuration_values(
127+
test_config, mock_llama_stack_client, test_request, test_auth
128+
):
129+
"""Test that info endpoint correctly uses configuration values.
130+
131+
This integration test verifies:
132+
- Configuration is properly loaded and accessible
133+
- Endpoint reads configuration values correctly
134+
- Service name from config appears in response
135+
136+
Args:
137+
test_config: Loads real configuration (required for endpoint to access config)
138+
mock_llama_stack_client: Mocked Llama Stack client
139+
test_request: Real FastAPI request
140+
test_auth: Real noop authentication tuple
141+
"""
142+
# Fixtures with side effects (needed but not directly used)
143+
_ = mock_llama_stack_client
144+
145+
response = await info_endpoint_handler(auth=test_auth, request=test_request)
146+
147+
# Verify service name comes from configuration
148+
assert response.name == test_config.configuration.name
149+
assert response.name == "foo bar baz"

0 commit comments

Comments
 (0)