Skip to content

Commit 0b196d5

Browse files
committed
test: add artifact type filtering validation
Add comprehensive test suite for model catalog artifact filtering by type: - Validate artifact filtering by model-artifact and metrics-artifact types - Test error handling for invalid artifact types - Test multiple artifact type filtering (currently failing - RHOAIENG-36938) - Refactor randomly_picked_model fixture to support header type selection - Add dynamic pagination helper for artifact fetching - Rename get_models_from_api to get_models_from_catalog_api for clarity
1 parent 21cfe43 commit 0b196d5

File tree

5 files changed

+278
-38
lines changed

5 files changed

+278
-38
lines changed

tests/model_registry/model_catalog/conftest.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -119,22 +119,43 @@ def user_token_for_api_calls(
119119
raise RuntimeError(f"Unknown user type: {user}")
120120

121121

122-
@pytest.fixture(scope="class")
123-
def randomly_picked_model(
124-
model_catalog_rest_url: list[str], user_token_for_api_calls: str, request: pytest.FixtureRequest
122+
@pytest.fixture(scope="function")
123+
def randomly_picked_model_from_catalog_api_by_source(
124+
model_catalog_rest_url: list[str],
125+
user_token_for_api_calls: str,
126+
model_registry_rest_headers: dict[str, str],
127+
request: pytest.FixtureRequest,
125128
) -> dict[Any, Any]:
126-
"""Pick a random model"""
129+
"""Pick a random model from a specific catalog (function-scoped for test isolation)
130+
131+
Supports parameterized headers via 'header_type':
132+
- 'user_token': Uses user_token_for_api_calls (default for user-specific tests)
133+
- 'registry': Uses model_registry_rest_headers (for catalog/registry tests)
134+
135+
Accepts 'catalog_id' or 'source' (alias) to specify the catalog.
136+
"""
127137
param = getattr(request, "param", {})
128-
source = param.get("source", REDHAT_AI_CATALOG_ID)
129-
LOGGER.info(f"Picking random model from {source}")
130-
url = f"{model_catalog_rest_url[0]}models?source={source}&pageSize=100"
131-
result = execute_get_command(
132-
url=url,
133-
headers=get_rest_headers(token=user_token_for_api_calls),
134-
)["items"]
135-
assert result, f"Expected Default models to be present. Actual: {result}"
136-
LOGGER.info(f"{len(result)} models found")
137-
return random.choice(seq=result)
138+
# Support both 'catalog_id' and 'source' for backward compatibility
139+
catalog_id = param.get("catalog_id") or param.get("source", REDHAT_AI_CATALOG_ID)
140+
header_type = param.get("header_type", "user_token")
141+
142+
# Select headers based on header_type
143+
if header_type == "registry":
144+
headers = model_registry_rest_headers
145+
else:
146+
headers = get_rest_headers(token=user_token_for_api_calls)
147+
148+
LOGGER.info(f"Picking random model from catalog: {catalog_id} with header_type: {header_type}")
149+
150+
models_response = execute_get_command(
151+
url=f"{model_catalog_rest_url[0]}models?source={catalog_id}&pageSize=100",
152+
headers=headers,
153+
)
154+
models = models_response.get("items", [])
155+
assert models, f"No models found for catalog: {catalog_id}"
156+
LOGGER.info(f"{len(models)} models found in catalog {catalog_id}")
157+
158+
return random.choice(seq=models)
138159

139160

140161
@pytest.fixture(scope="class")

tests/model_registry/model_catalog/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,5 @@
3636

3737
REDHAT_AI_FILTER: str = "Red+Hat+AI"
3838
REDHAT_AI_VALIDATED_FILTER = "Red+Hat+AI+Validated"
39+
MODEL_ARTIFACT_TYPE: str = "model-artifact"
40+
METRICS_ARTIFACT_TYPE: str = "metrics-artifact"

tests/model_registry/model_catalog/test_default_model_catalog.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -188,41 +188,41 @@ def test_model_catalog_default_catalog_sources(
188188
def test_model_default_catalog_get_models_by_source(
189189
self: Self,
190190
model_catalog_rest_url: list[str],
191-
randomly_picked_model: dict[Any, Any],
191+
randomly_picked_model_from_catalog_api_by_source: dict[Any, Any],
192192
):
193193
"""
194194
Validate a specific user can access models api for model catalog associated with a default source
195195
"""
196-
LOGGER.info(f"picked model: {randomly_picked_model}")
197-
assert randomly_picked_model
196+
LOGGER.info(f"picked model: {randomly_picked_model_from_catalog_api_by_source}")
197+
assert randomly_picked_model_from_catalog_api_by_source
198198

199199
def test_model_default_catalog_get_model_by_name(
200200
self: Self,
201201
model_catalog_rest_url: list[str],
202202
user_token_for_api_calls: str,
203-
randomly_picked_model: dict[Any, Any],
203+
randomly_picked_model_from_catalog_api_by_source: dict[Any, Any],
204204
):
205205
"""
206206
Validate a specific user can access get Model by name associated with a default source
207207
"""
208-
model_name = randomly_picked_model["name"]
208+
model_name = randomly_picked_model_from_catalog_api_by_source["name"]
209209
result = execute_get_command(
210210
url=f"{model_catalog_rest_url[0]}sources/{REDHAT_AI_CATALOG_ID}/models/{model_name}",
211211
headers=get_rest_headers(token=user_token_for_api_calls),
212212
)
213-
differences = list(diff(randomly_picked_model, result))
213+
differences = list(diff(randomly_picked_model_from_catalog_api_by_source, result))
214214
assert not differences, f"Expected no differences in model information for {model_name}: {differences}"
215215

216216
def test_model_default_catalog_get_model_artifact(
217217
self: Self,
218218
model_catalog_rest_url: list[str],
219219
user_token_for_api_calls: str,
220-
randomly_picked_model: dict[Any, Any],
220+
randomly_picked_model_from_catalog_api_by_source: dict[Any, Any],
221221
):
222222
"""
223223
Validate a specific user can access get Model artifacts for model associated with default source
224224
"""
225-
model_name = randomly_picked_model["name"]
225+
model_name = randomly_picked_model_from_catalog_api_by_source["name"]
226226
result = execute_get_command(
227227
url=f"{model_catalog_rest_url[0]}sources/{REDHAT_AI_CATALOG_ID}/models/{model_name}/artifacts",
228228
headers=get_rest_headers(token=user_token_for_api_calls),

tests/model_registry/model_catalog/test_model_search.py

Lines changed: 197 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@
88
REDHAT_AI_VALIDATED_FILTER,
99
REDHAT_AI_CATALOG_ID,
1010
VALIDATED_CATALOG_ID,
11+
MODEL_ARTIFACT_TYPE,
12+
METRICS_ARTIFACT_TYPE,
1113
)
1214
from tests.model_registry.model_catalog.utils import (
13-
get_models_from_api,
15+
get_models_from_catalog_api,
16+
fetch_all_artifacts_with_dynamic_paging,
1417
)
18+
from kubernetes.dynamic.exceptions import ResourceNotFoundError
1519

1620
LOGGER = get_logger(name=__name__)
1721
pytestmark = [
@@ -28,20 +32,20 @@ def test_search_model_catalog_source_label(
2832
RHOAIENG-33656: Validate search model catalog by source label
2933
"""
3034

31-
redhat_ai_filter_moldels_size = get_models_from_api(
35+
redhat_ai_filter_moldels_size = get_models_from_catalog_api(
3236
model_catalog_rest_url=model_catalog_rest_url,
3337
model_registry_rest_headers=model_registry_rest_headers,
3438
source_label=REDHAT_AI_FILTER,
3539
)["size"]
36-
redhat_ai_validated_filter_models_size = get_models_from_api(
40+
redhat_ai_validated_filter_models_size = get_models_from_catalog_api(
3741
model_catalog_rest_url=model_catalog_rest_url,
3842
model_registry_rest_headers=model_registry_rest_headers,
3943
source_label=REDHAT_AI_VALIDATED_FILTER,
4044
)["size"]
41-
no_filtered_models_size = get_models_from_api(
45+
no_filtered_models_size = get_models_from_catalog_api(
4246
model_catalog_rest_url=model_catalog_rest_url, model_registry_rest_headers=model_registry_rest_headers
4347
)["size"]
44-
both_filtered_models_size = get_models_from_api(
48+
both_filtered_models_size = get_models_from_catalog_api(
4549
model_catalog_rest_url=model_catalog_rest_url,
4650
model_registry_rest_headers=model_registry_rest_headers,
4751
source_label=f"{REDHAT_AI_VALIDATED_FILTER},{REDHAT_AI_FILTER}",
@@ -58,13 +62,13 @@ def test_search_model_catalog_invalid_source_label(
5862
Validate search model catalog by invalid source label
5963
"""
6064

61-
null_size = get_models_from_api(
65+
null_size = get_models_from_catalog_api(
6266
model_catalog_rest_url=model_catalog_rest_url,
6367
model_registry_rest_headers=model_registry_rest_headers,
6468
source_label="null",
6569
)["size"]
6670

67-
invalid_size = get_models_from_api(
71+
invalid_size = get_models_from_catalog_api(
6872
model_catalog_rest_url=model_catalog_rest_url,
6973
model_registry_rest_headers=model_registry_rest_headers,
7074
source_label="invalid",
@@ -75,33 +79,35 @@ def test_search_model_catalog_invalid_source_label(
7579
)
7680

7781
@pytest.mark.parametrize(
78-
"randomly_picked_model,source_filter",
82+
"randomly_picked_model_from_catalog_api_by_source,source_filter",
7983
[
8084
pytest.param(
81-
{"source": VALIDATED_CATALOG_ID},
85+
{"source": VALIDATED_CATALOG_ID, "header_type": "registry"},
8286
REDHAT_AI_VALIDATED_FILTER,
8387
id="test_search_model_catalog_redhat_ai_validated",
8488
),
8589
pytest.param(
86-
{"source": REDHAT_AI_CATALOG_ID}, REDHAT_AI_FILTER, id="test_search_model_catalog_redhat_ai_default"
90+
{"source": REDHAT_AI_CATALOG_ID, "header_type": "registry"},
91+
REDHAT_AI_FILTER,
92+
id="test_search_model_catalog_redhat_ai_default",
8793
),
8894
],
89-
indirect=["randomly_picked_model"],
95+
indirect=["randomly_picked_model_from_catalog_api_by_source"],
9096
)
9197
def test_search_model_catalog_match(
9298
self: Self,
9399
model_catalog_rest_url: list[str],
94100
model_registry_rest_headers: dict[str, str],
95-
randomly_picked_model: dict[Any, Any],
101+
randomly_picked_model_from_catalog_api_by_source: dict[Any, Any],
96102
source_filter: str,
97103
):
98104
"""
99105
RHOAIENG-33656: Validate search model catalog by match
100106
"""
101-
random_model = randomly_picked_model
107+
random_model = randomly_picked_model_from_catalog_api_by_source
102108
random_model_name = random_model["name"]
103109
LOGGER.info(f"random_model_name: {random_model_name}")
104-
result = get_models_from_api(
110+
result = get_models_from_catalog_api(
105111
model_catalog_rest_url=model_catalog_rest_url,
106112
model_registry_rest_headers=model_registry_rest_headers,
107113
source_label=source_filter,
@@ -113,3 +119,180 @@ def test_search_model_catalog_match(
113119
differences = list(diff(random_model, result["items"][0]))
114120
assert not differences, f"Expected no differences in model information for {random_model_name}: {differences}"
115121
LOGGER.info("Model information matches")
122+
123+
124+
# All the tests in this class are failing for RHOAIENG-36938, there are two problems:
125+
# 1. The filter parameter is setup to use artifact_type instead of artifactType
126+
# 2. The filter with multiple artifact types is not working as expected
127+
class TestSearchModelArtifact:
128+
@pytest.mark.parametrize(
129+
"randomly_picked_model_from_catalog_api_by_source, artifact_type",
130+
[
131+
pytest.param(
132+
{"catalog_id": REDHAT_AI_CATALOG_ID, "header_type": "registry"},
133+
MODEL_ARTIFACT_TYPE,
134+
id="redhat_ai_model_artifact",
135+
),
136+
pytest.param(
137+
{"catalog_id": REDHAT_AI_CATALOG_ID, "header_type": "registry"},
138+
METRICS_ARTIFACT_TYPE,
139+
id="redhat_ai_metrics_artifact",
140+
),
141+
pytest.param(
142+
{"catalog_id": VALIDATED_CATALOG_ID, "header_type": "registry"},
143+
MODEL_ARTIFACT_TYPE,
144+
id="validated_model_artifact",
145+
),
146+
pytest.param(
147+
{"catalog_id": VALIDATED_CATALOG_ID, "header_type": "registry"},
148+
METRICS_ARTIFACT_TYPE,
149+
id="validated_metrics_artifact",
150+
),
151+
],
152+
indirect=["randomly_picked_model_from_catalog_api_by_source"],
153+
)
154+
def test_validate_model_artifacts_by_artifact_type(
155+
self: Self,
156+
model_catalog_rest_url: list[str],
157+
model_registry_rest_headers: dict[str, str],
158+
randomly_picked_model_from_catalog_api_by_source: dict[Any, Any],
159+
artifact_type: str,
160+
):
161+
"""
162+
RHOAIENG-33659: Validates that the model artifacts returned by the artifactType filter
163+
match the complete set of artifacts for a random model.
164+
"""
165+
model_name = randomly_picked_model_from_catalog_api_by_source.get("name")
166+
catalog_id = randomly_picked_model_from_catalog_api_by_source.get("source_id")
167+
assert model_name and catalog_id, "Model name or catalog ID not found in random model"
168+
LOGGER.info(f"Testing model '{model_name}' from catalog '{catalog_id}' for artifact type '{artifact_type}'")
169+
170+
# Fetch all artifacts with dynamic page size adjustment
171+
all_model_artifacts = fetch_all_artifacts_with_dynamic_paging(
172+
url_with_pagesize=f"{model_catalog_rest_url[0]}sources/{catalog_id}/models/{model_name}/artifacts?pageSize",
173+
headers=model_registry_rest_headers,
174+
page_size=100,
175+
)["items"]
176+
177+
# Fetch filtered artifacts by type with dynamic page size adjustment
178+
artifact_type_artifacts = fetch_all_artifacts_with_dynamic_paging(
179+
url_with_pagesize=(
180+
f"{model_catalog_rest_url[0]}sources/{catalog_id}/models/{model_name}/artifacts?"
181+
f"artifactType={artifact_type}&pageSize"
182+
),
183+
headers=model_registry_rest_headers,
184+
page_size=50,
185+
)["items"]
186+
187+
# Create lookup for validation
188+
all_artifacts_by_id = {artifact["id"]: artifact for artifact in all_model_artifacts}
189+
190+
# Verify all filtered artifacts exist
191+
for artifact in artifact_type_artifacts:
192+
artifact_id = artifact["id"]
193+
assert artifact_id in all_artifacts_by_id, (
194+
f"Filtered artifact {artifact_id} not found in complete artifact list for {model_name}"
195+
)
196+
197+
differences = list(diff(artifact, all_artifacts_by_id[artifact_id]))
198+
assert not differences, f"Artifact {artifact_id} mismatch for {model_name}: {differences}"
199+
200+
# Verify the filter didn't miss any artifacts of the type
201+
artifacts_of_type_in_all = [
202+
artifact for artifact in all_model_artifacts if artifact.get("artifactType") == artifact_type
203+
]
204+
assert len(artifact_type_artifacts) == len(artifacts_of_type_in_all), (
205+
f"Filter returned {len(artifact_type_artifacts)} {artifact_type} artifacts, "
206+
f"but found {len(artifacts_of_type_in_all)} in complete list for {model_name}"
207+
)
208+
209+
LOGGER.info(f"Validated {len(artifact_type_artifacts)} {artifact_type} artifacts for {model_name}")
210+
211+
@pytest.mark.parametrize(
212+
"randomly_picked_model_from_catalog_api_by_source",
213+
[
214+
pytest.param(
215+
{"header_type": "registry"},
216+
id="invalid_artifact_type",
217+
),
218+
],
219+
indirect=["randomly_picked_model_from_catalog_api_by_source"],
220+
)
221+
def test_error_handled_for_invalid_artifact_type(
222+
self: Self,
223+
model_catalog_rest_url: list[str],
224+
model_registry_rest_headers: dict[str, str],
225+
randomly_picked_model_from_catalog_api_by_source: dict[Any, Any],
226+
):
227+
"""
228+
RHOAIENG-33659: Validates that the API returns the correct error when an invalid artifactType
229+
is provided regardless of catalog or model.
230+
"""
231+
model_name = randomly_picked_model_from_catalog_api_by_source.get("name")
232+
catalog_id = randomly_picked_model_from_catalog_api_by_source.get("source_id")
233+
assert model_name and catalog_id, "Model name or catalog ID not found in random model"
234+
235+
invalid_artifact_type = "invalid"
236+
237+
with pytest.raises(ResourceNotFoundError, match=f"unsupported catalog artifact type: {invalid_artifact_type}"):
238+
fetch_all_artifacts_with_dynamic_paging(
239+
url_with_pagesize=(
240+
f"{model_catalog_rest_url[0]}sources/{catalog_id}/models/{model_name}/artifacts?"
241+
f"artifactType={invalid_artifact_type}&pageSize"
242+
),
243+
headers=model_registry_rest_headers,
244+
page_size=1,
245+
)
246+
247+
LOGGER.info(f"Successfully validated that invalid artifact type '{invalid_artifact_type}' raises an error")
248+
249+
@pytest.mark.parametrize(
250+
"randomly_picked_model_from_catalog_api_by_source",
251+
[
252+
pytest.param(
253+
{"catalog_id": REDHAT_AI_CATALOG_ID, "header_type": "registry"},
254+
id="redhat_ai_catalog",
255+
),
256+
pytest.param(
257+
{"catalog_id": VALIDATED_CATALOG_ID, "header_type": "registry"},
258+
id="validated_catalog",
259+
),
260+
],
261+
indirect=["randomly_picked_model_from_catalog_api_by_source"],
262+
)
263+
def test_multiple_artifact_type_filtering(
264+
self: Self,
265+
model_catalog_rest_url: list[str],
266+
model_registry_rest_headers: dict[str, str],
267+
randomly_picked_model_from_catalog_api_by_source: dict[Any, Any],
268+
):
269+
"""
270+
RHOAIENG-33659: Validates that the API returns all artifacts of a random model
271+
when filtering by multiple artifact types.
272+
"""
273+
model_name = randomly_picked_model_from_catalog_api_by_source.get("name")
274+
catalog_id = randomly_picked_model_from_catalog_api_by_source.get("source_id")
275+
assert model_name and catalog_id, "Model name or catalog ID not found in random model"
276+
LOGGER.info(f"Testing model '{model_name}' from catalog '{catalog_id}' for multiple artifact types")
277+
artifact_types = f"{METRICS_ARTIFACT_TYPE},{MODEL_ARTIFACT_TYPE}"
278+
# Fetch all artifacts with dynamic page size adjustment
279+
all_model_artifacts = fetch_all_artifacts_with_dynamic_paging(
280+
url_with_pagesize=f"{model_catalog_rest_url[0]}sources/{catalog_id}/models/{model_name}/artifacts?pageSize",
281+
headers=model_registry_rest_headers,
282+
page_size=100,
283+
)["items"]
284+
285+
# Fetch filtered artifacts by type with dynamic page size adjustment
286+
artifact_type_artifacts = fetch_all_artifacts_with_dynamic_paging(
287+
url_with_pagesize=(
288+
f"{model_catalog_rest_url[0]}sources/{catalog_id}/models/{model_name}/artifacts?"
289+
f"artifactType={artifact_types}&pageSize"
290+
),
291+
headers=model_registry_rest_headers,
292+
page_size=100,
293+
)["items"]
294+
295+
assert len(artifact_type_artifacts) == len(all_model_artifacts), (
296+
f"Filter returned {len(artifact_type_artifacts)} artifacts, "
297+
f"but found {len(all_model_artifacts)} in complete list for {model_name}"
298+
)

0 commit comments

Comments
 (0)