diff --git a/docs/devel_doc/openapi.json b/docs/devel_doc/openapi.json
index 55c47daca..c731dbb7f 100644
--- a/docs/devel_doc/openapi.json
+++ b/docs/devel_doc/openapi.json
@@ -172,7 +172,7 @@
"info"
],
"summary": "Info Endpoint Handler",
- "description": "Handle request to the /info endpoint.\n\nProcess GET requests to the /info endpoint, returning the\nservice name, version and OGX version.\n\n### Parameters:\n- request: The incoming HTTP request (used by middleware).\n- auth: Authentication tuple from the auth dependency (used by middleware).\n\n### Raises:\n- HTTPException: with status 401 for unauthorized access.\n- HTTPException: with status 403 if permission is denied.\n- HTTPException: with status 503 and a detail object containing `response`\n and `cause` when unable to connect to OGX.\n\n### Returns:\n- InfoResponse: An object containing the service's name and version.",
+ "description": "Handle request to the /info endpoint.\n\nProcess GET requests to the /info endpoint, returning the\nservice name and version. The answer does not depend on OGX.\n\n### Parameters:\n- request: The incoming HTTP request (used by middleware).\n- auth: Authentication tuple from the auth dependency (used by middleware).\n\n### Raises:\n- HTTPException: with status 401 for unauthorized access.\n- HTTPException: with status 403 if permission is denied.\n- HTTPException: with status 503 and a detail object containing `response`\n and `cause` when the Kubernetes authentication backend is unreachable.\n\n### Returns:\n- InfoResponse: An object containing the service's name and version.",
"operationId": "info_endpoint_handler_v1_info_get",
"responses": {
"200": {
@@ -184,7 +184,6 @@
},
"example": {
"name": "Lightspeed Stack",
- "ogx_version": "1.0.0",
"service_version": "1.0.0"
}
}
@@ -294,14 +293,6 @@
"$ref": "#/components/schemas/ServiceUnavailableResponse"
},
"examples": {
- "OGX": {
- "value": {
- "detail": {
- "cause": "Connection error while trying to reach backend service.",
- "response": "Unable to connect to OGX"
- }
- }
- },
"kubernetes api": {
"value": {
"detail": {
@@ -15369,32 +15360,18 @@
"0.2.0",
"1.0.0"
]
- },
- "ogx_version": {
- "type": "string",
- "title": "Ogx Version",
- "description": "OGX version",
- "examples": [
- "0.2.1",
- "0.2.2",
- "0.2.18",
- "0.2.21",
- "0.2.22"
- ]
}
},
"type": "object",
"required": [
"name",
- "service_version",
- "ogx_version"
+ "service_version"
],
"title": "InfoResponse",
- "description": "Model representing a response to an info request.\n\nAttributes:\n name: Service name.\n service_version: Service version.\n ogx_version: OGX version.",
+ "description": "Model representing a response to an info request.\n\nAttributes:\n name: Service name.\n service_version: Service version.",
"examples": [
{
"name": "Lightspeed Stack",
- "ogx_version": "1.0.0",
"service_version": "1.0.0"
}
]
diff --git a/docs/devel_doc/openapi.md b/docs/devel_doc/openapi.md
index fdd09fb0d..78b506fbb 100644
--- a/docs/devel_doc/openapi.md
+++ b/docs/devel_doc/openapi.md
@@ -408,7 +408,7 @@ Lightspeed Core Stack (LCS) service API specification.
| Method | Path | Description |
|--------|-------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| GET | `/` | Returns the static HTML index page |
-| GET | `/v1/info` | Returns the service name, version and OGX version |
+| GET | `/v1/info` | Returns the service name and version |
| GET | `/v1/models` | List of available models |
| GET | `/v1/tools` | Consolidated list of available tools from all configured MCP servers |
| GET | `/v1/mcp-auth/client-options` | List of MCP servers configured to accept client-provided authorization tokens, along with the header names where clients should provide these tokens |
@@ -625,7 +625,7 @@ Examples
Handle request to the /info endpoint.
Process GET requests to the /info endpoint, returning the
-service name, version and OGX version.
+service name and version. The answer does not depend on OGX.
### Parameters:
- request: The incoming HTTP request (used by middleware).
@@ -635,7 +635,7 @@ service name, version and OGX version.
- HTTPException: with status 401 for unauthorized access.
- HTTPException: with status 403 if permission is denied.
- HTTPException: with status 503 and a detail object containing `response`
- and `cause` when unable to connect to OGX.
+ and `cause` when the Kubernetes authentication backend is unreachable.
### Returns:
- InfoResponse: An object containing the service's name and version.
@@ -761,18 +761,6 @@ Examples
}
```
-```json
-{
- "detail": {
- "cause": "Connection error while trying to reach backend service.",
- "response": "Unable to connect to OGX"
- }
-}
-```
-
-
-
-
```json
{
"detail": {
@@ -6600,14 +6588,12 @@ Model representing a response to an info request.
Attributes:
name: Service name.
service_version: Service version.
- ogx_version: OGX version.
| Field | Type | Description |
|-------|------|-------------|
| name | string | Service name |
| service_version | string | Service version |
-| ogx_version | string | OGX version |
## InputToolMCP
diff --git a/docs/migrations/index.md b/docs/migrations/index.md
index 26d90dea9..39a8618ce 100644
--- a/docs/migrations/index.md
+++ b/docs/migrations/index.md
@@ -6,4 +6,4 @@ release that includes breaking or notable configuration changes.
| Version | Description |
|---------|-------------|
| [v0.7.0](v0.7.0.md) | RAG configuration restructured; `ogx` naming; `/v1/vector-stores` and `/v1/conversations` deprecated |
-| [v0.8.0](v0.8.0.md) | `inference.context_windows` deprecated in favour of per-model `metadata.context_window` |
+| [v0.8.0](v0.8.0.md) | `inference.context_windows` deprecated in favour of per-model `metadata.context_window`; `ogx_version` removed from `/v1/info` |
diff --git a/docs/migrations/v0.8.0.md b/docs/migrations/v0.8.0.md
index 3edcba581..1607f14bf 100644
--- a/docs/migrations/v0.8.0.md
+++ b/docs/migrations/v0.8.0.md
@@ -3,6 +3,7 @@
## Table of Contents
* [Context windows for conversation compaction](#context-windows-for-conversation-compaction)
+* [`/v1/info` API](#v1info-api)
---
@@ -72,3 +73,18 @@ at `threshold_ratio` of the window found for it instead of never. Set
128,000 tokens. See the
[conversation compaction guide](../user_doc/conversation_compaction.md#context-window)
for the full resolution order.
+
+---
+
+## `/v1/info` API
+
+`GET /v1/info` now describes the Lightspeed Core Stack service only and no
+longer contacts OGX. The `ogx_version` field is removed with no replacement:
+
+| v0.7.x | v0.8.0 |
+|---|---|
+| `name`, `service_version`, `ogx_version` | `name`, `service_version` |
+| 503 `Unable to connect to OGX` while OGX is unreachable | 200 with the service name and version |
+
+Update clients that read `ogx_version`, and do not use `/v1/info` to detect
+whether OGX is available: `GET /readiness` reports that.
diff --git a/docs/models/responses.puml b/docs/models/responses.puml
index 1bd9471d8..741549b73 100644
--- a/docs/models/responses.puml
+++ b/docs/models/responses.puml
@@ -100,7 +100,6 @@ class "ForbiddenResponse" as src.models.api.responses.error.forbidden.ForbiddenR
class "InfoResponse" as src.models.api.responses.successful.probes.InfoResponse {
model_config : dict
name : str
- ogx_version : str
service_version : str
}
class "InternalServerErrorResponse" as src.models.api.responses.error.internal.InternalServerErrorResponse {
diff --git a/docs/models/responses.svg b/docs/models/responses.svg
index 1ab1be00b..069d1d66b 100644
--- a/docs/models/responses.svg
+++ b/docs/models/responses.svg
@@ -227,16 +227,15 @@
-
+
InfoResponse
model_config : dict
name : str
- ogx_version : str
- service_version : str
-
+ service_version : str
+
diff --git a/docs/models/successful_responses.json b/docs/models/successful_responses.json
index 14cd641c0..a8761b7fa 100644
--- a/docs/models/successful_responses.json
+++ b/docs/models/successful_responses.json
@@ -2236,11 +2236,10 @@
"type": "object"
},
"InfoResponse": {
- "description": "Model representing a response to an info request.\n\nAttributes:\n name: Service name.\n service_version: Service version.\n ogx_version: OGX version.",
+ "description": "Model representing a response to an info request.\n\nAttributes:\n name: Service name.\n service_version: Service version.",
"examples": [
{
"name": "Lightspeed Stack",
- "ogx_version": "1.0.0",
"service_version": "1.0.0"
}
],
@@ -2262,24 +2261,11 @@
],
"title": "Service Version",
"type": "string"
- },
- "ogx_version": {
- "description": "OGX version",
- "examples": [
- "0.2.1",
- "0.2.2",
- "0.2.18",
- "0.2.21",
- "0.2.22"
- ],
- "title": "Ogx Version",
- "type": "string"
}
},
"required": [
"name",
- "service_version",
- "ogx_version"
+ "service_version"
],
"title": "InfoResponse",
"type": "object"
diff --git a/docs/models/successful_responses.md b/docs/models/successful_responses.md
index 8681400fd..4d81dad9d 100644
--- a/docs/models/successful_responses.md
+++ b/docs/models/successful_responses.md
@@ -873,14 +873,12 @@ Model representing a response to an info request.
Attributes:
name: Service name.
service_version: Service version.
- ogx_version: OGX version.
| Field | Type | Description |
|-------|------|-------------|
| name | string | Service name |
| service_version | string | Service version |
-| ogx_version | string | OGX version |
## JsonPathOperator
diff --git a/docs/testing/e2e_scenarios.md b/docs/testing/e2e_scenarios.md
index fd3eb7e30..089550a0b 100644
--- a/docs/testing/e2e_scenarios.md
+++ b/docs/testing/e2e_scenarios.md
@@ -101,7 +101,7 @@
* Check if the OpenAPI endpoint works as expected
* Check if info endpoint is working
-* Check if info endpoint reports error when OGX connection is not working
+* Check if info endpoint works when OGX connection is not working
* Check if shields endpoint is working (lists LCORE-configured shields)
* Check if tools endpoint is working
* Check if tools endpoint reports error when OGX is unreachable
diff --git a/docs/testing/integration_vs_e2e.md b/docs/testing/integration_vs_e2e.md
index 2b46368d8..0efb44a1a 100644
--- a/docs/testing/integration_vs_e2e.md
+++ b/docs/testing/integration_vs_e2e.md
@@ -60,7 +60,7 @@ These tests check something true. They sit in the wrong layer, or they assert so
| `test_health_liveness` in `tests/integration/endpoints/test_health_integration.py` | Same shape: unused config fixture, assertion is `response.alive is True`. Liveness does not consult OGX. The other tests in that file, which map stubbed provider health into the readiness payload, are real integration tests. |
| `tests/integration/test_version.py` | Compares `version.__version__` with `pdm show --version`. No config, database, or handler. The repo runs tests with `uv`, not `pdm`. |
| `tests/integration/container_lifecycle/test_container_lifecycle.py` | Builds an image, polls `http://localhost:8321/v1/health`, and `exec`s into the container. That is a deployed stack, collected by `make test-integration`, and one function does build, start, probe, mounts, and cleanup. The behave suite never runs it. |
-| `tests/unit/app/endpoints/test_info.py` and `tests/integration/endpoints/test_info_integration.py` | Both call `info_endpoint_handler` with a mocked OGX version. The integration test earns its place: it reads `name` (`"foo bar baz"`) from `tests/configuration/lightspeed-stack.yaml` and uses the real noop auth fixture. The unit happy path only asserts `response.name is not None`. |
+| `tests/unit/app/endpoints/test_info.py` and `tests/integration/endpoints/test_info_integration.py` | Both call `info_endpoint_handler` and check it works without OGX. The integration test earns its place: it reads `name` (`"foo bar baz"`) from `tests/configuration/lightspeed-stack.yaml` and uses the real noop auth fixture. The unit happy path asserts the exact response body. |
| `@flaky` scenarios in `tests/e2e/features/query.feature` (also `streaming_query.feature`, `mcp.feature`, parts of `responses.feature`) | The reply must contain `"ask"`, `"checkout"`, or `"image"`. The contract is status 200 and token counters. The extra word depends on the model. |
`tests/e2e/features/conversation-compaction.feature` also matches response fragments, and that use holds. The scenario plants names (`north-quarry`, `blue-lagoon`) and later asks for them. The fragment is a value the test wrote. Stable end-to-end assertions are status, schema, `context_status`, metrics, and planted facts.
diff --git a/src/app/endpoints/info.py b/src/app/endpoints/info.py
index 3a80a0209..072ef77f1 100644
--- a/src/app/endpoints/info.py
+++ b/src/app/endpoints/info.py
@@ -2,14 +2,12 @@
from typing import Annotated
-from fastapi import APIRouter, Depends, HTTPException, Request
-from ogx_client import ApiException
+from fastapi import APIRouter, Depends, Request
from opentelemetry import trace
from authentication import get_auth_dependency
from authentication.interface import AuthTuple
from authorization.middleware import authorize
-from client.ogx import AsyncOgxClientHolder
from configuration import configuration
from log import get_logger
from models.api.responses.constants import UNAUTHORIZED_OPENAPI_EXAMPLES
@@ -33,9 +31,7 @@
200: InfoResponse.openapi_response(),
401: UnauthorizedResponse.openapi_response(examples=UNAUTHORIZED_OPENAPI_EXAMPLES),
403: ForbiddenResponse.openapi_response(examples=["endpoint"]),
- 503: ServiceUnavailableResponse.openapi_response(
- examples=["OGX", "kubernetes api"]
- ),
+ 503: ServiceUnavailableResponse.openapi_response(examples=["kubernetes api"]),
}
@@ -49,7 +45,7 @@ async def info_endpoint_handler(
Handle request to the /info endpoint.
Process GET requests to the /info endpoint, returning the
- service name, version and OGX version.
+ service name and version. The answer does not depend on OGX.
### Parameters:
- request: The incoming HTTP request (used by middleware).
@@ -59,7 +55,7 @@ async def info_endpoint_handler(
- HTTPException: with status 401 for unauthorized access.
- HTTPException: with status 403 if permission is denied.
- HTTPException: with status 503 and a detail object containing `response`
- and `cause` when unable to connect to OGX.
+ and `cause` when the Kubernetes authentication backend is unreachable.
### Returns:
- InfoResponse: An object containing the service's name and version.
@@ -73,29 +69,16 @@ async def info_endpoint_handler(
with tracer.start_as_current_span("info.handle_request") as span:
logger.info("Response to /v1/info endpoint")
- try:
- # try to get OGX client
- client = AsyncOgxClientHolder().get_client()
- # retrieve version
- ogx_version_object = await client.inspect.version()
- ogx_version = ogx_version_object.version
- logger.debug("Service name: %s", configuration.configuration.name)
- logger.debug("Service version: %s", __version__)
- logger.debug("OGX version: %s", ogx_version)
- set_span_attributes(
- span,
- {
- SpanAttributes.SERVICE_NAME: configuration.configuration.name,
- SpanAttributes.SERVICE_VERSION: __version__,
- },
- )
- return InfoResponse(
- name=configuration.configuration.name,
- service_version=__version__,
- ogx_version=ogx_version,
- )
- # connection to OGX server
- except ApiException as e:
- logger.error("Unable to connect to OGX: %s", e)
- response = ServiceUnavailableResponse(backend_name="OGX")
- raise HTTPException(**response.model_dump()) from e
+ logger.debug("Service name: %s", configuration.configuration.name)
+ logger.debug("Service version: %s", __version__)
+ set_span_attributes(
+ span,
+ {
+ SpanAttributes.SERVICE_NAME: configuration.configuration.name,
+ SpanAttributes.SERVICE_VERSION: __version__,
+ },
+ )
+ return InfoResponse(
+ name=configuration.configuration.name,
+ service_version=__version__,
+ )
diff --git a/src/models/api/responses/successful/probes.py b/src/models/api/responses/successful/probes.py
index 544e993f5..aee10d7de 100644
--- a/src/models/api/responses/successful/probes.py
+++ b/src/models/api/responses/successful/probes.py
@@ -17,7 +17,6 @@ class InfoResponse(AbstractSuccessfulResponse):
Attributes:
name: Service name.
service_version: Service version.
- ogx_version: OGX version.
"""
@@ -31,11 +30,6 @@ class InfoResponse(AbstractSuccessfulResponse):
examples=["0.1.0", "0.2.0", "1.0.0"],
)
- ogx_version: str = Field(
- description="OGX version",
- examples=["0.2.1", "0.2.2", "0.2.18", "0.2.21", "0.2.22"],
- )
-
# provides examples for /docs endpoint
model_config = {
"json_schema_extra": {
@@ -43,7 +37,6 @@ class InfoResponse(AbstractSuccessfulResponse):
{
"name": "Lightspeed Stack",
"service_version": "1.0.0",
- "ogx_version": "1.0.0",
}
]
}
diff --git a/tests/e2e/features/info.feature b/tests/e2e/features/info.feature
index 1add2b41e..9f9456c53 100644
--- a/tests/e2e/features/info.feature
+++ b/tests/e2e/features/info.feature
@@ -19,7 +19,7 @@ Feature: Info tests
When I access REST API endpoint "info" using HTTP GET method
Then The status code of the response is 200
And The body of the response has proper name Lightspeed Core Service (LCS) and version 0.8.0rc1
- And The body of the response has ogx version 1.2.5
+ And The body of the response does not contain ogx_version
Scenario: Check if tools endpoint is working
diff --git a/tests/e2e/features/ogx_disrupted.feature b/tests/e2e/features/ogx_disrupted.feature
index 73fa4da85..515566137 100644
--- a/tests/e2e/features/ogx_disrupted.feature
+++ b/tests/e2e/features/ogx_disrupted.feature
@@ -60,16 +60,13 @@ Feature: OGX connection disrupted
@cfg_default
- Scenario: Check if info endpoint reports error when ogx connection is not working
+ Scenario: Check if info endpoint works when ogx connection is not working
Given The service uses the lightspeed-stack-default.yaml configuration
And The service is restarted
And The ogx connection is disrupted
When I access REST API endpoint "info" using HTTP GET method
- Then The status code of the response is 503
- And The body of the response is the following
- """
- {"detail": {"response": "Unable to connect to OGX", "cause": "Connection error while trying to reach backend service."}}
- """
+ Then The status code of the response is 200
+ And The body of the response contains service_version
# --- @cfg_default (noop auth; tools list needs no bearer) ---
diff --git a/tests/e2e/features/steps/info.py b/tests/e2e/features/steps/info.py
index c5206c5d4..759e11e09 100644
--- a/tests/e2e/features/steps/info.py
+++ b/tests/e2e/features/steps/info.py
@@ -1,7 +1,6 @@
"""Implementation of common test steps."""
import json
-import re
from behave import then # pyright: ignore[reportAttributeAccessIssue]
from behave.runner import Context
@@ -19,23 +18,6 @@ def check_name_version(context: Context, service_name: str, version: str) -> Non
), f"version is {response_json["service_version"]}"
-@then("The body of the response has ogx version {ogx_version}")
-def check_ogx_version(context: Context, ogx_version: str) -> None:
- """Check proper OGX version number."""
- response_json = context.response.json()
- assert response_json is not None, "Response is not valid JSON"
-
- version_pattern = r"\d+\.\d+\.\d+"
- response_ogx_version = response_json["ogx_version"]
- match = re.search(version_pattern, response_ogx_version)
- assert match is not None, f"Could not extract version from {response_ogx_version}"
- extracted_version = match.group(0)
-
- assert (
- extracted_version == ogx_version
- ), f"ogx version is {extracted_version}, expected {ogx_version}"
-
-
@then("The response contains {count:d} tools listed for provider {provider_name}")
def check_tool_count(context: Context, count: int, provider_name: str) -> None:
"""Check that the number of tools for defined provider is correct."""
diff --git a/tests/integration/endpoints/test_info_integration.py b/tests/integration/endpoints/test_info_integration.py
index e4c591464..6032aea43 100644
--- a/tests/integration/endpoints/test_info_integration.py
+++ b/tests/integration/endpoints/test_info_integration.py
@@ -4,9 +4,8 @@
from typing import Any
import pytest
-from fastapi import HTTPException, Request, status
+from fastapi import Request
from ogx_client import ApiException
-from ogx_client.models.version_info import VersionInfo
from pytest_mock import AsyncMockType, MockerFixture
from app.endpoints.info import info_endpoint_handler
@@ -15,14 +14,15 @@
from version import __version__
-@pytest.fixture(name="mock_ogx_client")
-def mock_ogx_client_fixture(
+@pytest.fixture(name="mock_get_ogx_client")
+def mock_get_ogx_client_fixture(
mocker: MockerFixture,
) -> Generator[Any, None, None]:
- """Mock only the external OGX client.
+ """Mock the accessor of the external OGX client.
This is the only external dependency we mock for integration tests,
- as it represents an external service call.
+ as it represents an external service call. The /info endpoint must
+ not use it.
Parameters:
----------
@@ -30,26 +30,16 @@ def mock_ogx_client_fixture(
Yields:
------
- AsyncMock: A mocked OGX client configured for tests.
+ Mock: The patched AsyncOgxClientHolder.get_client.
"""
- mock_holder_class = mocker.patch("app.endpoints.info.AsyncOgxClientHolder")
-
- mock_client = mocker.AsyncMock()
- # Mock the version endpoint to return a known version
- mock_client.inspect.version.return_value = VersionInfo(version="0.2.22")
-
- # Create a mock holder instance
- mock_holder_instance = mock_holder_class.return_value
- mock_holder_instance.get_client.return_value = mock_client
-
- yield mock_client
+ yield mocker.patch("client.ogx.AsyncOgxClientHolder.get_client")
@pytest.mark.asyncio
async def test_info_endpoint_returns_service_information(
test_config: AppConfig,
- mock_ogx_client: AsyncMockType,
+ mock_get_ogx_client: AsyncMockType,
test_request: Request,
test_auth: AuthTuple,
) -> None:
@@ -58,14 +48,14 @@ async def test_info_endpoint_returns_service_information(
This integration test verifies:
- Endpoint handler integrates with configuration system
- Configuration values are correctly accessed
- - OGX client is properly called
+ - OGX client is not called
- Real noop authentication is used
- Response structure matches expected format
Parameters:
----------
test_config: Loads real configuration (required for endpoint to access config)
- mock_ogx_client: Mocked OGX client
+ mock_get_ogx_client: Mocked OGX client accessor
test_request: FastAPI request
test_auth: noop authentication tuple
@@ -82,55 +72,47 @@ async def test_info_endpoint_returns_service_information(
# Verify values from real configuration
assert response.name == "foo bar baz" # From lightspeed-stack.yaml
assert response.service_version == __version__
- assert response.ogx_version == "0.2.22"
- # Verify the OGX client was called
- mock_ogx_client.inspect.version.assert_called_once()
+ # Verify the response has nothing else in it
+ assert set(response.model_dump()) == {"name", "service_version"}
+
+ # Verify the OGX client was not used
+ mock_get_ogx_client.assert_not_called()
@pytest.mark.asyncio
-async def test_info_endpoint_handles_connection_error(
+async def test_info_endpoint_does_not_depend_on_ogx(
test_config: AppConfig,
- mock_ogx_client: AsyncMockType,
+ mock_get_ogx_client: AsyncMockType,
test_request: Request,
test_auth: AuthTuple,
) -> None:
- """Test that info endpoint properly handles OGX connection errors.
+ """Test that info endpoint answers while OGX is unavailable.
This integration test verifies:
- - Error handling when external service is unavailable
- - HTTPException is raised with correct status code
- - Error response includes proper error details
+ - A failing OGX connection does not reach the endpoint
+ - The service identity is still returned
Parameters:
----------
test_config: Loads real configuration (required for endpoint to access config)
- mock_ogx_client: Mocked OGX client
+ mock_get_ogx_client: Mocked OGX client accessor
test_request: FastAPI request
test_auth: noop authentication tuple
"""
- # test_config fixture loads configuration, which is required for the endpoint
- _ = test_config
# Configure mock to raise connection error
- mock_ogx_client.inspect.version.side_effect = ApiException(status=None)
+ mock_get_ogx_client.side_effect = ApiException(status=None)
- # Verify that HTTPException is raised
- with pytest.raises(HTTPException) as exc_info:
- await info_endpoint_handler(auth=test_auth, request=test_request)
+ response = await info_endpoint_handler(auth=test_auth, request=test_request)
- # Verify error details
- assert exc_info.value.status_code == status.HTTP_503_SERVICE_UNAVAILABLE
- assert isinstance(exc_info.value.detail, dict)
- expected = "Unable to connect to OGX"
- assert exc_info.value.detail["response"] == expected # type: ignore[reportArgumentType]
- assert "cause" in exc_info.value.detail
+ assert response.name == test_config.configuration.name
+ assert response.service_version == __version__
@pytest.mark.asyncio
async def test_info_endpoint_uses_configuration_values(
test_config: AppConfig,
- mock_ogx_client: AsyncMockType,
test_request: Request,
test_auth: AuthTuple,
) -> None:
@@ -144,14 +126,10 @@ async def test_info_endpoint_uses_configuration_values(
Parameters:
----------
test_config: Loads real configuration (required for endpoint to access config)
- mock_ogx_client: Mocked OGX client
test_request: Real FastAPI request
test_auth: Real noop authentication tuple
"""
- # Fixtures with side effects (needed but not directly used)
- _ = mock_ogx_client
-
response = await info_endpoint_handler(auth=test_auth, request=test_request)
# Verify service name comes from configuration
diff --git a/tests/unit/app/endpoints/test_info.py b/tests/unit/app/endpoints/test_info.py
index 654f65c27..36388baf3 100644
--- a/tests/unit/app/endpoints/test_info.py
+++ b/tests/unit/app/endpoints/test_info.py
@@ -3,18 +3,17 @@
from typing import TYPE_CHECKING, Any
import pytest
-from fastapi import HTTPException, Request, status
+from fastapi import Request
from ogx_client import ApiException
-from ogx_client.models.version_info import VersionInfo
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
InMemorySpanExporter,
)
-from opentelemetry.trace import StatusCode
from pytest_mock import MockerFixture
from app.endpoints.info import info_endpoint_handler
from configuration import AppConfig
from tests.unit.utils.auth_helpers import mock_authorization_resolvers
+from version import __version__
if TYPE_CHECKING:
from authentication.interface import AuthTuple
@@ -51,11 +50,8 @@ async def test_info_endpoint(mocker: MockerFixture) -> None:
cfg = AppConfig()
cfg.init_from_dict(config_dict)
- # Mock the OGX client
- mock_client = mocker.AsyncMock()
- mock_client.inspect.version.return_value = VersionInfo(version="0.1.2")
+ # The handler must not touch the OGX client
mock_lsc = mocker.patch("client.ogx.AsyncOgxClientHolder.get_client")
- mock_lsc.return_value = mock_client
mock_config = mocker.Mock()
mocker.patch("app.endpoints.models.configuration", mock_config)
@@ -75,179 +71,88 @@ async def test_info_endpoint(mocker: MockerFixture) -> None:
auth: AuthTuple = ("test_user_id", "test_user", True, "test_token")
response = await info_endpoint_handler(auth=auth, request=request)
- assert response is not None
- assert response.name is not None
- assert response.service_version is not None
- assert response.ogx_version == "0.1.2"
+ assert response.model_dump() == {"name": "foo", "service_version": __version__}
+ mock_lsc.assert_not_called()
@pytest.mark.asyncio
-async def test_info_endpoint_connection_error(mocker: MockerFixture) -> None:
- """Test the info endpoint handler.
-
- Verify that info_endpoint_handler raises an HTTPException with
- status 503 when the OGX client cannot connect.
-
- Sets up application configuration and patches the OGX
- client so that calling its version inspection raises an
- ApiException, then asserts the raised HTTPException has
- status code 503 and a detail payload containing a "response" of
- "Service unavailable" and a "cause" that includes "Unable to
- connect to OGX".
+async def test_info_endpoint_does_not_depend_on_ogx(mocker: MockerFixture) -> None:
+ """Test that the info endpoint answers while OGX is unreachable.
+
+ Makes any use of the OGX client fail and asserts the handler still
+ returns the service identity, because /info does not need OGX.
"""
mock_authorization_resolvers(mocker)
- # configuration for tests
- config_dict: dict[Any, Any] = {
- "name": "foo",
- "service": {
- "host": "localhost",
- "port": 8080,
- "auth_enabled": False,
- "workers": 1,
- "color_log": True,
- "access_log": True,
- },
- "ogx": {
- "api_key": "xyzzy",
- "url": "http://x.y.com:1234",
- "use_as_library_client": False,
- },
- "user_data_collection": {
- "feedback_enabled": False,
- },
- "customization": None,
- "authorization": {"access_rules": []},
- "authentication": {"module": "noop"},
- }
cfg = AppConfig()
- cfg.init_from_dict(config_dict)
+ cfg.init_from_dict(
+ {
+ "name": "foo",
+ "service": {"host": "localhost", "port": 8080},
+ "ogx": {
+ "api_key": "xyzzy",
+ "url": "http://x.y.com:1234",
+ "use_as_library_client": False,
+ },
+ "user_data_collection": {"feedback_enabled": False},
+ "authorization": {"access_rules": []},
+ "authentication": {"module": "noop"},
+ }
+ )
+ mocker.patch("configuration.configuration", cfg)
- # Mock the OGX client
- mock_client = mocker.AsyncMock()
- mock_client.inspect.version.side_effect = ApiException(status=None) # type: ignore
- mock_lsc = mocker.patch("client.ogx.AsyncOgxClientHolder.get_client")
- mock_lsc.return_value = mock_client
- mock_config = mocker.Mock()
- mocker.patch("app.endpoints.models.configuration", mock_config)
+ # Any attempt to reach OGX fails
+ mocker.patch(
+ "client.ogx.AsyncOgxClientHolder.get_client",
+ side_effect=ApiException(status=None), # type: ignore
+ )
- # Mock configuration
- mocker.patch("configuration.configuration", cfg)
+ # HTTP request mock required by URL endpoint handler
+ request = Request(scope={"type": "http"})
+
+ # Authorization tuple required by URL endpoint handler
+ auth: AuthTuple = ("test_user_id", "test_user", True, "test_token")
+ response = await info_endpoint_handler(auth=auth, request=request)
+ assert response.model_dump() == {"name": "foo", "service_version": __version__}
+
+
+@pytest.mark.asyncio
+async def test_info_endpoint_emits_span_on_success(
+ mocker: MockerFixture,
+ otel: tuple[Any, InMemorySpanExporter],
+) -> None:
+ """Test that a successful /info request emits a span with service metadata."""
+ tracer, exporter = otel
+ mocker.patch("app.endpoints.info.tracer", tracer)
mock_authorization_resolvers(mocker)
- # HTTP request mock required by URL endpoint handler
- request = Request(
- scope={
- "type": "http",
+ cfg = AppConfig()
+ cfg.init_from_dict(
+ {
+ "name": "test-service",
+ "service": {"host": "localhost", "port": 8080},
+ "ogx": {
+ "api_key": "k",
+ "url": "http://x:1234",
+ "use_as_library_client": False,
+ },
+ "user_data_collection": {},
+ "authorization": {"access_rules": []},
+ "authentication": {"module": "noop"},
}
)
+ mocker.patch("configuration.configuration", cfg)
- # Authorization tuple required by URL endpoint handler
- auth: AuthTuple = ("test_user_id", "test_user", True, "test_token")
+ request = Request(scope={"type": "http"})
+ auth: AuthTuple = ("uid", "uname", True, "tok")
+
+ await info_endpoint_handler(auth=auth, request=request)
- with pytest.raises(HTTPException) as e:
- await info_endpoint_handler(auth=auth, request=request)
- assert e.value.status_code == status.HTTP_503_SERVICE_UNAVAILABLE
- assert e.value.detail["response"] == "Service unavailable" # type: ignore
- assert (
- "Connection error while trying to reach backend service."
- in e.value.detail["cause"]
- ) # type: ignore
-
-
-class TestInfoEndpointOtel:
- """OTEL instrumentation tests for the /info endpoint."""
-
- @pytest.mark.asyncio
- async def test_emits_span_on_success(
- self,
- mocker: MockerFixture,
- otel: tuple[Any, InMemorySpanExporter],
- ) -> None:
- """Test that a successful /info request emits a span with service metadata."""
- tracer, exporter = otel
- mocker.patch("app.endpoints.info.tracer", tracer)
- mock_authorization_resolvers(mocker)
-
- cfg = AppConfig()
- cfg.init_from_dict(
- {
- "name": "test-service",
- "service": {"host": "localhost", "port": 8080},
- "ogx": {
- "api_key": "k",
- "url": "http://x:1234",
- "use_as_library_client": False,
- },
- "user_data_collection": {},
- "authorization": {"access_rules": []},
- "authentication": {"module": "noop"},
- }
- )
- mocker.patch("configuration.configuration", cfg)
-
- mock_client = mocker.AsyncMock()
- mock_client.inspect.version.return_value = VersionInfo(version="0.1.2")
- mocker.patch(
- "client.ogx.AsyncOgxClientHolder.get_client", return_value=mock_client
- )
-
- request = Request(scope={"type": "http"})
- auth: AuthTuple = ("uid", "uname", True, "tok")
-
- await info_endpoint_handler(auth=auth, request=request)
-
- spans = exporter.get_finished_spans()
- assert len(spans) == 1
- span = spans[0]
- assert span.name == "info.handle_request"
- assert span.attributes is not None
- assert span.attributes["service.name"] == "test-service"
- assert span.attributes["service.version"] is not None
-
- @pytest.mark.asyncio
- async def test_span_records_error_on_connection_failure(
- self,
- mocker: MockerFixture,
- otel: tuple[Any, InMemorySpanExporter],
- ) -> None:
- """Test that the span records an error when OGX is unreachable."""
- tracer, exporter = otel
- mocker.patch("app.endpoints.info.tracer", tracer)
- mock_authorization_resolvers(mocker)
-
- cfg = AppConfig()
- cfg.init_from_dict(
- {
- "name": "test-service",
- "service": {"host": "localhost", "port": 8080},
- "ogx": {
- "api_key": "k",
- "url": "http://x:1234",
- "use_as_library_client": False,
- },
- "user_data_collection": {},
- "authorization": {"access_rules": []},
- "authentication": {"module": "noop"},
- }
- )
- mocker.patch("configuration.configuration", cfg)
-
- mock_client = mocker.AsyncMock()
- mock_client.inspect.version.side_effect = ApiException(status=None)
- mocker.patch(
- "client.ogx.AsyncOgxClientHolder.get_client", return_value=mock_client
- )
-
- request = Request(scope={"type": "http"})
- auth: AuthTuple = ("uid", "uname", True, "tok")
-
- with pytest.raises(HTTPException):
- await info_endpoint_handler(auth=auth, request=request)
-
- spans = exporter.get_finished_spans()
- assert len(spans) == 1
- span = spans[0]
- assert span.name == "info.handle_request"
- assert span.status.status_code == StatusCode.ERROR
+ spans = exporter.get_finished_spans()
+ assert len(spans) == 1
+ span = spans[0]
+ assert span.name == "info.handle_request"
+ assert span.attributes is not None
+ assert span.attributes["service.name"] == "test-service"
+ assert span.attributes["service.version"] is not None
diff --git a/tests/unit/models/responses/test_successful_responses.py b/tests/unit/models/responses/test_successful_responses.py
index d409bd8b2..08a164f69 100644
--- a/tests/unit/models/responses/test_successful_responses.py
+++ b/tests/unit/models/responses/test_successful_responses.py
@@ -403,12 +403,10 @@ def test_constructor(self) -> None:
response = InfoResponse(
name="Lightspeed Stack",
service_version="1.0.0",
- ogx_version="1.0.0",
)
assert isinstance(response, AbstractSuccessfulResponse)
assert response.name == "Lightspeed Stack"
assert response.service_version == "1.0.0"
- assert response.ogx_version == "1.0.0"
def test_missing_required_parameters(self) -> None:
"""Test InfoResponse raises ValidationError when required fields are missing."""
@@ -417,6 +415,12 @@ def test_missing_required_parameters(self) -> None:
with pytest.raises(ValidationError):
InfoResponse(name="Test") # pyright: ignore[reportCallIssue]
+ def test_exposes_only_lcore_identity(self) -> None:
+ """Test InfoResponse fields and example carry only the LCORE identity."""
+ expected_fields = {"name", "service_version"}
+ assert set(InfoResponse.model_fields) == expected_fields
+ assert set(InfoResponse.model_json_schema()["examples"][0]) == expected_fields
+
def test_openapi_response(self) -> None:
"""Test InfoResponse.openapi_response() method."""
schema = InfoResponse.model_json_schema()
diff --git a/tests/unit/utils/dumpers/test_models_dumper.py b/tests/unit/utils/dumpers/test_models_dumper.py
index 8ab5d7814..0c812f27d 100644
--- a/tests/unit/utils/dumpers/test_models_dumper.py
+++ b/tests/unit/utils/dumpers/test_models_dumper.py
@@ -2504,10 +2504,9 @@ def test_dump_models(tmpdir: Path) -> None:
"type": "object"
},
"InfoResponse": {
- "description": "Model representing a response to an info request.\n\nAttributes:\n name: Service name.\n service_version: Service version.\n ogx_version: OGX version.",
+ "description": "Model representing a response to an info request.\n\nAttributes:\n name: Service name.\n service_version: Service version.",
"examples": [
{
- "ogx_version": "1.0.0",
"name": "Lightspeed Stack",
"service_version": "1.0.0"
}
@@ -2530,24 +2529,11 @@ def test_dump_models(tmpdir: Path) -> None:
],
"title": "Service Version",
"type": "string"
- },
- "ogx_version": {
- "description": "OGX version",
- "examples": [
- "0.2.1",
- "0.2.2",
- "0.2.18",
- "0.2.21",
- "0.2.22"
- ],
- "title": "OGX Version",
- "type": "string"
}
},
"required": [
"name",
- "service_version",
- "ogx_version"
+ "service_version"
],
"title": "InfoResponse",
"type": "object"