Skip to content

Commit 7ac82f6

Browse files
authored
test: assetType for source endpoint (#1187)
1 parent b8e4bfe commit 7ac82f6

File tree

5 files changed

+116
-92
lines changed

5 files changed

+116
-92
lines changed

tests/model_registry/conftest.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import Any
55

66
import pytest
7+
import yaml
78
from kubernetes.dynamic import DynamicClient
89
from ocp_resources.config_map import ConfigMap
910
from ocp_resources.data_science_cluster import DataScienceCluster
@@ -25,18 +26,26 @@
2526
from tests.model_registry.constants import (
2627
DB_BASE_RESOURCES_NAME,
2728
DB_RESOURCE_NAME,
29+
DEFAULT_CUSTOM_MODEL_CATALOG,
2830
KUBERBACPROXY_STR,
2931
MR_INSTANCE_BASE_NAME,
3032
MR_INSTANCE_NAME,
3133
MR_OPERATOR_NAME,
3234
)
35+
from tests.model_registry.mcp_servers.constants import (
36+
MCP_CATALOG_API_PATH,
37+
MCP_CATALOG_SOURCE,
38+
MCP_SERVERS_YAML,
39+
)
3340
from tests.model_registry.utils import (
3441
generate_namespace_name,
3542
get_byoidc_user_credentials,
3643
get_model_registry_metadata_resources,
3744
get_model_registry_objects,
3845
get_rest_headers,
3946
wait_for_default_resource_cleanedup,
47+
wait_for_mcp_catalog_api,
48+
wait_for_model_catalog_pod_ready_after_deletion,
4049
)
4150
from utilities.constants import DscComponents, Labels
4251
from utilities.general import (
@@ -466,3 +475,54 @@ def model_catalog_routes(admin_client: DynamicClient, model_registry_namespace:
466475
return list(
467476
Route.get(namespace=model_registry_namespace, label_selector="component=model-catalog", client=admin_client)
468477
)
478+
479+
480+
@pytest.fixture(scope="class")
481+
def mcp_catalog_rest_urls(model_registry_namespace: str, model_catalog_routes: list[Route]) -> list[str]:
482+
"""Build MCP catalog REST URL from existing model catalog routes."""
483+
assert model_catalog_routes, f"Model catalog routes do not exist in {model_registry_namespace}"
484+
return [f"https://{route.instance.spec.host}:443{MCP_CATALOG_API_PATH}" for route in model_catalog_routes]
485+
486+
487+
@pytest.fixture(scope="class")
488+
def mcp_servers_configmap_patch(
489+
admin_client: DynamicClient,
490+
model_registry_namespace: str,
491+
mcp_catalog_rest_urls: list[str],
492+
model_registry_rest_headers: dict[str, str],
493+
) -> Generator[None]:
494+
"""
495+
Class-scoped fixture that patches the model-catalog-sources ConfigMap
496+
497+
Sets two keys in the ConfigMap data:
498+
- sources.yaml: catalog source definition pointing to the MCP servers YAML
499+
- mcp-servers.yaml: the actual MCP server definitions
500+
"""
501+
catalog_config_map = ConfigMap(
502+
name=DEFAULT_CUSTOM_MODEL_CATALOG,
503+
client=admin_client,
504+
namespace=model_registry_namespace,
505+
)
506+
507+
current_data = yaml.safe_load(catalog_config_map.instance.data.get("sources.yaml", "{}") or "{}")
508+
if "mcp_catalogs" not in current_data:
509+
current_data["mcp_catalogs"] = []
510+
current_data["mcp_catalogs"].append(MCP_CATALOG_SOURCE)
511+
512+
patches = {
513+
"data": {
514+
"sources.yaml": yaml.dump(current_data, default_flow_style=False),
515+
"mcp-servers.yaml": MCP_SERVERS_YAML,
516+
}
517+
}
518+
519+
with ResourceEditor(patches={catalog_config_map: patches}):
520+
wait_for_model_catalog_pod_ready_after_deletion(
521+
client=admin_client, model_registry_namespace=model_registry_namespace
522+
)
523+
wait_for_mcp_catalog_api(url=mcp_catalog_rest_urls[0], headers=model_registry_rest_headers)
524+
yield
525+
526+
wait_for_model_catalog_pod_ready_after_deletion(
527+
client=admin_client, model_registry_namespace=model_registry_namespace
528+
)

tests/model_registry/mcp_servers/conftest.py

Lines changed: 1 addition & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,29 @@
11
from collections.abc import Generator
22

33
import pytest
4-
import requests
54
import yaml
65
from kubernetes.dynamic import DynamicClient
7-
from kubernetes.dynamic.exceptions import ResourceNotFoundError
86
from ocp_resources.config_map import ConfigMap
97
from ocp_resources.resource import ResourceEditor
10-
from ocp_resources.route import Route
118
from simple_logger.logger import get_logger
12-
from timeout_sampler import retry
139

1410
from tests.model_registry.constants import DEFAULT_CUSTOM_MODEL_CATALOG
1511
from tests.model_registry.mcp_servers.constants import (
16-
MCP_CATALOG_API_PATH,
1712
MCP_CATALOG_INVALID_SOURCE,
1813
MCP_CATALOG_SOURCE,
1914
MCP_CATALOG_SOURCE2,
2015
MCP_SERVERS_YAML,
2116
MCP_SERVERS_YAML2,
2217
)
2318
from tests.model_registry.utils import (
24-
TransientUnauthorizedError,
25-
execute_get_call,
2619
execute_get_command,
20+
wait_for_mcp_catalog_api,
2721
wait_for_model_catalog_pod_ready_after_deletion,
2822
)
2923

3024
LOGGER = get_logger(name=__name__)
3125

3226

33-
@pytest.fixture(scope="class")
34-
def mcp_catalog_rest_urls(model_registry_namespace: str, model_catalog_routes: list[Route]) -> list[str]:
35-
"""Build MCP catalog REST URL from existing model catalog routes."""
36-
assert model_catalog_routes, f"Model catalog routes do not exist in {model_registry_namespace}"
37-
return [f"https://{route.instance.spec.host}:443{MCP_CATALOG_API_PATH}" for route in model_catalog_routes]
38-
39-
40-
@retry(
41-
wait_timeout=90,
42-
sleep=5,
43-
exceptions_dict={ResourceNotFoundError: [], TransientUnauthorizedError: []},
44-
)
45-
def wait_for_mcp_catalog_api(url: str, headers: dict[str, str]) -> requests.Response:
46-
"""Wait for MCP catalog API to be ready and returning MCP server data."""
47-
LOGGER.info(f"Waiting for MCP catalog API at {url}mcp_servers")
48-
response = execute_get_call(url=f"{url}mcp_servers", headers=headers)
49-
data = response.json()
50-
if not data.get("items"):
51-
raise ResourceNotFoundError("MCP catalog API returned empty items, catalog data not yet loaded")
52-
return response
53-
54-
5527
@pytest.fixture(scope="class")
5628
def mcp_servers_response(
5729
mcp_catalog_rest_urls: list[str],
@@ -64,50 +36,6 @@ def mcp_servers_response(
6436
)
6537

6638

67-
@pytest.fixture(scope="class")
68-
def mcp_servers_configmap_patch(
69-
admin_client: DynamicClient,
70-
model_registry_namespace: str,
71-
mcp_catalog_rest_urls: list[str],
72-
model_registry_rest_headers: dict[str, str],
73-
) -> Generator[None]:
74-
"""
75-
Class-scoped fixture that patches the model-catalog-sources ConfigMap
76-
77-
Sets two keys in the ConfigMap data:
78-
- sources.yaml: catalog source definition pointing to the MCP servers YAML
79-
- mcp-servers.yaml: the actual MCP server definitions
80-
"""
81-
catalog_config_map = ConfigMap(
82-
name=DEFAULT_CUSTOM_MODEL_CATALOG,
83-
client=admin_client,
84-
namespace=model_registry_namespace,
85-
)
86-
87-
current_data = yaml.safe_load(catalog_config_map.instance.data.get("sources.yaml", "{}") or "{}")
88-
if "mcp_catalogs" not in current_data:
89-
current_data["mcp_catalogs"] = []
90-
current_data["mcp_catalogs"].append(MCP_CATALOG_SOURCE)
91-
92-
patches = {
93-
"data": {
94-
"sources.yaml": yaml.dump(current_data, default_flow_style=False),
95-
"mcp-servers.yaml": MCP_SERVERS_YAML,
96-
}
97-
}
98-
99-
with ResourceEditor(patches={catalog_config_map: patches}):
100-
wait_for_model_catalog_pod_ready_after_deletion(
101-
client=admin_client, model_registry_namespace=model_registry_namespace
102-
)
103-
wait_for_mcp_catalog_api(url=mcp_catalog_rest_urls[0], headers=model_registry_rest_headers)
104-
yield
105-
106-
wait_for_model_catalog_pod_ready_after_deletion(
107-
client=admin_client, model_registry_namespace=model_registry_namespace
108-
)
109-
110-
11139
@pytest.fixture(scope="class")
11240
def mcp_multi_source_configmap_patch(
11341
admin_client: DynamicClient,

tests/model_registry/mcp_servers/test_data_integrity.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from tests.model_registry.mcp_servers.constants import (
77
EXPECTED_MCP_SERVER_NAMES,
88
EXPECTED_MCP_SERVER_TIMESTAMPS,
9-
EXPECTED_MCP_SERVER_TOOL_COUNTS,
109
EXPECTED_MCP_SERVER_TOOLS,
1110
)
1211
from tests.model_registry.utils import execute_get_command
@@ -30,9 +29,6 @@ def test_mcp_servers_loaded(
3029
assert server["createTimeSinceEpoch"] == expected["createTimeSinceEpoch"]
3130
assert server["lastUpdateTimeSinceEpoch"] == expected["lastUpdateTimeSinceEpoch"]
3231

33-
@pytest.mark.xfail(
34-
reason="RHOAIENG-51765: Tool name returned as {server}@{version}:{tool} instead of YAML-defined name"
35-
)
3632
def test_mcp_server_tools_loaded(
3733
self: Self,
3834
mcp_catalog_rest_urls: list[str],
@@ -50,17 +46,3 @@ def test_mcp_server_tools_loaded(
5046
assert server["toolCount"] == len(expected_tool_names)
5147
actual_tool_names = [tool["name"] for tool in server["tools"]]
5248
assert sorted(actual_tool_names) == sorted(expected_tool_names)
53-
54-
@pytest.mark.xfail(reason="RHOAIENG-51764: toolCount is 0 when not passing includeTools=true")
55-
def test_mcp_server_tool_count_without_include(
56-
self: Self,
57-
mcp_servers_response: dict[str, Any],
58-
):
59-
"""Verify that toolCount reflects actual tools even when tools are not included."""
60-
for server in mcp_servers_response.get("items", []):
61-
name = server["name"]
62-
expected_count = EXPECTED_MCP_SERVER_TOOL_COUNTS[name]
63-
actual_count = server.get("toolCount", 0)
64-
assert actual_count == expected_count, (
65-
f"Server '{name}': expected toolCount {expected_count}, got {actual_count}"
66-
)

tests/model_registry/model_catalog/metadata/test_sources_endpoint.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
from typing import Any, Self
2+
13
import pytest
24
from simple_logger.logger import get_logger
35

4-
from tests.model_registry.model_catalog.constants import REDHAT_AI_CATALOG_ID
6+
from tests.model_registry.mcp_servers.constants import MCP_CATALOG_SOURCE_ID
7+
from tests.model_registry.model_catalog.constants import REDHAT_AI_CATALOG_ID, VALIDATED_CATALOG_ID
58
from tests.model_registry.utils import execute_get_command
69

710
pytestmark = [pytest.mark.usefixtures("updated_dsc_component_state_scope_session", "model_registry_namespace")]
@@ -46,3 +49,39 @@ def test_sources_endpoint_returns_all_sources_regardless_of_enabled_field(
4649
f"Sources endpoint returned {len(items)} total sources: "
4750
f"{len(enabled_sources)} enabled, {len(disabled_sources)} disabled"
4851
)
52+
53+
54+
@pytest.mark.usefixtures("mcp_servers_configmap_patch")
55+
class TestAssetTypeFilter:
56+
"""RHOAIENG-51583: Tests for /sources endpoint assetType query parameter filtering."""
57+
58+
@pytest.mark.parametrize(
59+
"asset_type,expected_ids",
60+
[
61+
(None, {REDHAT_AI_CATALOG_ID, VALIDATED_CATALOG_ID}),
62+
("models", {REDHAT_AI_CATALOG_ID, VALIDATED_CATALOG_ID}),
63+
("mcp_servers", {MCP_CATALOG_SOURCE_ID}),
64+
("invalid_value", set()),
65+
],
66+
ids=["default-models", "explicit-models", "mcp-servers", "invalid-empty"],
67+
)
68+
def test_asset_type_filters_sources(
69+
self: Self,
70+
asset_type: str | None,
71+
expected_ids: set[str],
72+
model_catalog_rest_url: list[str],
73+
model_registry_rest_headers: dict[str, str],
74+
) -> None:
75+
"""Test that the /sources endpoint filters by assetType, returning only catalog sources
76+
for None/models, only MCP sources for mcp_servers, and an empty list for invalid values.
77+
"""
78+
sources_url = f"{model_catalog_rest_url[0]}sources"
79+
params: dict[str, Any] = {}
80+
if asset_type is not None:
81+
params["assetType"] = asset_type
82+
83+
response = execute_get_command(url=sources_url, headers=model_registry_rest_headers, params=params or None)
84+
source_ids = {item["id"] for item in response["items"]}
85+
86+
assert source_ids == expected_ids
87+
LOGGER.info(f"assetType={asset_type} returned sources: {source_ids}")

tests/model_registry/utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,3 +959,18 @@ def wait_for_model_catalog_pod_created(client: DynamicClient, model_registry_nam
959959
if pods:
960960
return True
961961
raise PodNotFound("Model catalog pod not found")
962+
963+
964+
@retry(
965+
wait_timeout=90,
966+
sleep=5,
967+
exceptions_dict={ResourceNotFoundError: [], TransientUnauthorizedError: []},
968+
)
969+
def wait_for_mcp_catalog_api(url: str, headers: dict[str, str]) -> requests.Response:
970+
"""Wait for MCP catalog API to be ready and returning MCP server data."""
971+
LOGGER.info(f"Waiting for MCP catalog API at {url}mcp_servers")
972+
response = execute_get_call(url=f"{url}mcp_servers", headers=headers)
973+
data = response.json()
974+
if not data.get("items"):
975+
raise ResourceNotFoundError("MCP catalog API returned empty items, catalog data not yet loaded")
976+
return response

0 commit comments

Comments
 (0)