Skip to content

Commit d447014

Browse files
authored
Merge pull request #1912 from Jdubrick/vector-store-test-update
RHIDP-14000: update tests for vector stores
2 parents 6116ef7 + 0b2aa08 commit d447014

4 files changed

Lines changed: 211 additions & 1 deletion

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
@e2e_group_3 @VectorStores
2+
Feature: vector stores API endpoint tests
3+
4+
5+
Background:
6+
Given The service is started locally
7+
And The system is in default state
8+
And I set the Authorization header to Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
9+
And REST API service prefix is /v1
10+
And the Lightspeed stack configuration directory is "tests/e2e/configuration"
11+
And The service uses the lightspeed-stack-auth-noop-token.yaml configuration
12+
And The service is restarted
13+
14+
Scenario: List vector stores returns 200
15+
When I access REST API endpoint "vector-stores" using HTTP GET method
16+
Then The status code of the response is 200
17+
And Content type of response is set to "application/json"
18+
19+
Scenario: Create vector store with empty body returns 422
20+
When I access REST API endpoint "vector-stores" using HTTP POST method
21+
"""
22+
{}
23+
"""
24+
Then The status code of the response is 422
25+
26+
Scenario: Create vector store with extra fields returns 422
27+
When I access REST API endpoint "vector-stores" using HTTP POST method
28+
"""
29+
{"name": "test-store", "unknown_field": "value"}
30+
"""
31+
Then The status code of the response is 422
32+
33+
Scenario: Update vector store with empty body returns 422
34+
When I access REST API endpoint "vector-stores/nonexistent-id" using HTTP PUT method
35+
"""
36+
{}
37+
"""
38+
Then The status code of the response is 422
39+
40+
Scenario: Add file to vector store with empty body returns 422
41+
When I access REST API endpoint "vector-stores/nonexistent-id/files" using HTTP POST method
42+
"""
43+
{}
44+
"""
45+
Then The status code of the response is 422

tests/e2e/test_list.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ features/mcp_servers_api_no_config.feature
2929
features/proxy.feature
3030
features/tls.feature
3131
features/opentelemetry.feature
32+
features/vector_stores.feature

tests/unit/app/endpoints/test_vector_stores.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,3 +1181,103 @@ async def test_delete_vector_store_file_not_found(mocker: MockerFixture) -> None
11811181
)
11821182
assert response.deleted is False
11831183
assert response.file_id == "file_999"
1184+
1185+
1186+
@pytest.mark.asyncio
1187+
async def test_get_vector_store_connection_error(mocker: MockerFixture) -> None:
1188+
"""Test get vector store with connection error."""
1189+
mock_authorization_resolvers(mocker)
1190+
1191+
config_dict = get_test_config()
1192+
cfg = AppConfig()
1193+
cfg.init_from_dict(config_dict)
1194+
1195+
mock_client = mocker.AsyncMock()
1196+
mock_client.vector_stores.retrieve.side_effect = APIConnectionError(
1197+
request=None # type: ignore
1198+
)
1199+
mock_lsc = mocker.patch(
1200+
"app.endpoints.vector_stores.AsyncLlamaStackClientHolder.get_client"
1201+
)
1202+
mock_lsc.return_value = mock_client
1203+
mocker.patch("app.endpoints.vector_stores.configuration", cfg)
1204+
1205+
request = get_test_request()
1206+
auth = get_test_auth()
1207+
1208+
with pytest.raises(HTTPException) as e:
1209+
await get_vector_store(request=request, vector_store_id="vs_123", auth=auth)
1210+
assert e.value.status_code == status.HTTP_503_SERVICE_UNAVAILABLE
1211+
1212+
1213+
@pytest.mark.asyncio
1214+
async def test_create_file_adds_txt_extension_when_missing(
1215+
mocker: MockerFixture,
1216+
) -> None:
1217+
"""Test that create_file appends .txt when filename has no extension."""
1218+
mock_authorization_resolvers(mocker)
1219+
1220+
config_dict = get_test_config()
1221+
cfg = AppConfig()
1222+
cfg.init_from_dict(config_dict)
1223+
1224+
mock_client = mocker.AsyncMock()
1225+
mock_client.files.create.return_value = File("file_123", "uploaded_file.txt", 12)
1226+
mock_lsc = mocker.patch(
1227+
"app.endpoints.vector_stores.AsyncLlamaStackClientHolder.get_client"
1228+
)
1229+
mock_lsc.return_value = mock_client
1230+
mocker.patch("app.endpoints.vector_stores.configuration", cfg)
1231+
1232+
request = get_test_request()
1233+
auth = get_test_auth()
1234+
1235+
mock_file = mocker.AsyncMock()
1236+
mock_file.filename = "uploaded_file"
1237+
mock_file.size = 12
1238+
mock_file.read.return_value = b"test content"
1239+
1240+
response = await create_file(request=request, auth=auth, file=mock_file)
1241+
assert response is not None
1242+
assert response.filename == "uploaded_file.txt"
1243+
1244+
file_arg = mock_client.files.create.call_args.kwargs["file"]
1245+
assert file_arg.name == "uploaded_file.txt"
1246+
1247+
1248+
@pytest.mark.asyncio
1249+
async def test_create_file_non_size_bad_request_returns_400(
1250+
mocker: MockerFixture,
1251+
) -> None:
1252+
"""Test create file with non-size BadRequestError returns 400."""
1253+
mock_authorization_resolvers(mocker)
1254+
1255+
config_dict = get_test_config()
1256+
cfg = AppConfig()
1257+
cfg.init_from_dict(config_dict)
1258+
1259+
mock_client = mocker.AsyncMock()
1260+
mock_response = mocker.Mock()
1261+
mock_response.request = mocker.Mock()
1262+
mock_client.files.create.side_effect = BadRequestError(
1263+
message="Invalid file format", response=mock_response, body=None
1264+
)
1265+
mock_lsc = mocker.patch(
1266+
"app.endpoints.vector_stores.AsyncLlamaStackClientHolder.get_client"
1267+
)
1268+
mock_lsc.return_value = mock_client
1269+
mocker.patch("app.endpoints.vector_stores.configuration", cfg)
1270+
1271+
request = get_test_request()
1272+
auth = get_test_auth()
1273+
1274+
mock_file = mocker.AsyncMock()
1275+
mock_file.filename = "test.txt"
1276+
mock_file.size = 12
1277+
mock_file.read.return_value = b"test content"
1278+
1279+
with pytest.raises(HTTPException) as e:
1280+
await create_file(request=request, auth=auth, file=mock_file)
1281+
1282+
assert e.value.status_code == status.HTTP_400_BAD_REQUEST
1283+
assert e.value.detail["response"] == "Invalid file upload" # type: ignore

tests/unit/models/requests/test_vector_store_requests.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,71 @@
33
import pytest
44
from pydantic import ValidationError
55

6-
from models.api.requests import VectorStoreFileCreateRequest, VectorStoreUpdateRequest
6+
from models.api.requests import (
7+
VectorStoreCreateRequest,
8+
VectorStoreFileCreateRequest,
9+
VectorStoreUpdateRequest,
10+
)
11+
12+
13+
class TestVectorStoreCreateRequest:
14+
"""Test cases for the VectorStoreCreateRequest model."""
15+
16+
def test_valid_create_with_name_only(self) -> None:
17+
"""Test valid create request with only required name field."""
18+
request = VectorStoreCreateRequest(name="test_store")
19+
assert request.name == "test_store"
20+
assert request.embedding_model is None
21+
assert request.embedding_dimension is None
22+
assert request.provider_id is None
23+
24+
def test_valid_create_with_all_fields(self) -> None:
25+
"""Test valid create request with all optional fields."""
26+
request = VectorStoreCreateRequest(
27+
name="test_store",
28+
embedding_model="text-embedding-ada-002",
29+
embedding_dimension=1536,
30+
provider_id="rhdh-docs",
31+
metadata={"user_id": "user123"},
32+
)
33+
assert request.name == "test_store"
34+
assert request.embedding_model == "text-embedding-ada-002"
35+
assert request.embedding_dimension == 1536
36+
assert request.provider_id == "rhdh-docs"
37+
assert request.metadata == {"user_id": "user123"}
38+
39+
def test_name_required(self) -> None:
40+
"""Test that name field is required."""
41+
with pytest.raises(ValidationError):
42+
VectorStoreCreateRequest() # pyright: ignore[reportCallIssue]
43+
44+
def test_name_cannot_be_empty(self) -> None:
45+
"""Test that name cannot be an empty string."""
46+
with pytest.raises(ValidationError, match="at least 1 character"):
47+
VectorStoreCreateRequest(name="")
48+
49+
def test_name_max_length_256(self) -> None:
50+
"""Test that name cannot exceed 256 characters."""
51+
with pytest.raises(ValidationError, match="at most 256 characters"):
52+
VectorStoreCreateRequest(name="a" * 257)
53+
54+
def test_name_at_max_length(self) -> None:
55+
"""Test that name at exactly 256 characters is accepted."""
56+
request = VectorStoreCreateRequest(name="a" * 256)
57+
assert len(request.name) == 256
58+
59+
def test_embedding_dimension_must_be_positive(self) -> None:
60+
"""Test that embedding_dimension must be greater than 0."""
61+
with pytest.raises(ValidationError, match="greater than 0"):
62+
VectorStoreCreateRequest(name="test_store", embedding_dimension=0)
63+
64+
def test_extra_fields_forbidden(self) -> None:
65+
"""Test that extra fields are rejected."""
66+
with pytest.raises(ValidationError, match="Extra inputs are not permitted"):
67+
VectorStoreCreateRequest(
68+
name="test_store",
69+
unknown_field="value", # pyright: ignore[reportCallIssue]
70+
)
771

872

973
class TestVectorStoreUpdateRequest:

0 commit comments

Comments
 (0)