Skip to content

Commit 9d5f2a1

Browse files
lugi0jgarciao
andauthored
feat: add tests to validate new customProperties loaded from metadata.json (#956)
Signed-off-by: lugi0 <lgiorgi@redhat.com> Co-authored-by: Jorge <jgarciao@users.noreply.github.com>
1 parent 5d939b6 commit 9d5f2a1

File tree

3 files changed

+1087
-963
lines changed

3 files changed

+1087
-963
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import pytest
2+
from typing import Any
3+
4+
from kubernetes.dynamic import DynamicClient
5+
from simple_logger.logger import get_logger
6+
7+
from tests.model_registry.model_catalog.constants import VALIDATED_CATALOG_ID
8+
from tests.model_registry.model_catalog.utils import (
9+
extract_custom_property_values,
10+
validate_custom_properties_structure,
11+
validate_custom_properties_match_metadata,
12+
get_metadata_from_catalog_pod,
13+
)
14+
from tests.model_registry.utils import get_model_catalog_pod
15+
16+
LOGGER = get_logger(name=__name__)
17+
18+
pytestmark = [
19+
pytest.mark.usefixtures(
20+
"updated_dsc_component_state_scope_session",
21+
"model_registry_namespace",
22+
)
23+
]
24+
25+
26+
@pytest.mark.skip_must_gather
27+
class TestCustomProperties:
28+
"""Test suite for validating custom properties in model catalog API"""
29+
30+
@pytest.mark.parametrize(
31+
"randomly_picked_model_from_catalog_api_by_source", [{"source": VALIDATED_CATALOG_ID}], indirect=True
32+
)
33+
def test_custom_properties_structure_is_valid(
34+
self,
35+
randomly_picked_model_from_catalog_api_by_source: tuple[dict[Any, Any], str, str],
36+
):
37+
"""Test that custom properties follow the expected MetadataStringValue structure."""
38+
model_data, model_name, catalog_id = randomly_picked_model_from_catalog_api_by_source
39+
40+
LOGGER.info(f"Testing custom properties structure for model '{model_name}' from catalog '{catalog_id}'")
41+
42+
custom_props = model_data.get("customProperties", {})
43+
assert validate_custom_properties_structure(custom_props)
44+
45+
@pytest.mark.parametrize(
46+
"randomly_picked_model_from_catalog_api_by_source", [{"source": VALIDATED_CATALOG_ID}], indirect=True
47+
)
48+
def test_custom_properties_match_metadata(
49+
self,
50+
randomly_picked_model_from_catalog_api_by_source: tuple[dict[Any, Any], str, str],
51+
admin_client: DynamicClient,
52+
model_registry_namespace: str,
53+
):
54+
"""Test that custom properties from API match values in metadata.json files."""
55+
model_data, model_name, catalog_id = randomly_picked_model_from_catalog_api_by_source
56+
57+
LOGGER.info(f"Testing custom properties metadata match for model '{model_name}' from catalog '{catalog_id}'")
58+
59+
# Get model catalog pod
60+
model_catalog_pods = get_model_catalog_pod(
61+
client=admin_client, model_registry_namespace=model_registry_namespace
62+
)
63+
assert len(model_catalog_pods) > 0, "No model catalog pods found"
64+
65+
# Extract custom properties and get metadata
66+
custom_props = model_data.get("customProperties", {})
67+
api_props = extract_custom_property_values(custom_properties=custom_props)
68+
metadata = get_metadata_from_catalog_pod(model_catalog_pod=model_catalog_pods[0], model_name=model_name)
69+
70+
assert validate_custom_properties_match_metadata(api_props, metadata)

tests/model_registry/model_catalog/utils.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import Any, Tuple, List, Dict
2+
import json
23
import yaml
34

45
from kubernetes.dynamic import DynamicClient
@@ -1145,3 +1146,129 @@ def get_excluded_model_str(models: list[str]) -> str:
11451146
- {model_name}
11461147
"""
11471148
return excluded_models
1149+
1150+
1151+
def extract_custom_property_values(custom_properties: dict[str, Any]) -> dict[str, str]:
1152+
"""
1153+
Extract string values from MetadataStringValue format for custom properties.
1154+
1155+
Args:
1156+
custom_properties: Dictionary of custom properties from API response
1157+
1158+
Returns:
1159+
Dictionary of extracted string values for size, tensor_type, variant_group_id
1160+
"""
1161+
extracted = {}
1162+
expected_keys = ["size", "tensor_type", "variant_group_id"]
1163+
1164+
for key in expected_keys:
1165+
if key in custom_properties:
1166+
prop_data = custom_properties[key]
1167+
if isinstance(prop_data, dict) and "string_value" in prop_data:
1168+
extracted[key] = prop_data["string_value"]
1169+
else:
1170+
LOGGER.warning(f"Unexpected format for custom property '{key}': {prop_data}")
1171+
1172+
LOGGER.info(f"Extracted {len(extracted)} custom properties: {list(extracted.keys())}")
1173+
return extracted
1174+
1175+
1176+
def validate_custom_properties_structure(custom_properties: dict[str, Any]) -> bool:
1177+
"""
1178+
Validate that custom properties follow the expected MetadataStringValue structure.
1179+
1180+
Args:
1181+
custom_properties: Dictionary of custom properties from API response
1182+
1183+
Returns:
1184+
True if all custom properties have valid structure, False otherwise
1185+
"""
1186+
if not custom_properties:
1187+
LOGGER.info("No custom properties found - structure validation skipped")
1188+
return True
1189+
1190+
expected_keys = ["size", "tensor_type", "variant_group_id"]
1191+
1192+
for key in expected_keys:
1193+
if key in custom_properties:
1194+
prop_data = custom_properties[key]
1195+
1196+
if not isinstance(prop_data, dict):
1197+
LOGGER.error(f"Custom property '{key}' is not a dictionary: {prop_data}")
1198+
return False
1199+
1200+
if "metadataType" not in prop_data:
1201+
LOGGER.error(f"Custom property '{key}' missing 'metadataType' field")
1202+
return False
1203+
1204+
if prop_data.get("metadataType") != "MetadataStringValue":
1205+
LOGGER.error(f"Custom property '{key}' has unexpected metadataType: {prop_data.get('metadataType')}")
1206+
return False
1207+
1208+
if "string_value" not in prop_data:
1209+
LOGGER.error(f"Custom property '{key}' missing 'string_value' field")
1210+
return False
1211+
1212+
if not isinstance(prop_data.get("string_value"), str):
1213+
LOGGER.error(f"Custom property '{key}' string_value is not a string: {prop_data.get('string_value')}")
1214+
return False
1215+
1216+
LOGGER.info(f"Custom property '{key}' has valid structure: '{prop_data.get('string_value')}'")
1217+
1218+
LOGGER.info("All custom properties have valid structure")
1219+
return True
1220+
1221+
1222+
def validate_custom_properties_match_metadata(api_custom_properties: dict[str, str], metadata: dict[str, Any]) -> bool:
1223+
"""
1224+
Compare API custom properties with metadata.json values.
1225+
1226+
Args:
1227+
api_custom_properties: Extracted custom properties from API (string values)
1228+
metadata: Parsed metadata.json content
1229+
1230+
Returns:
1231+
True if all custom properties match metadata values, False otherwise
1232+
"""
1233+
expected_keys = ["size", "tensor_type", "variant_group_id"]
1234+
1235+
for key in expected_keys:
1236+
api_value = api_custom_properties.get(key)
1237+
metadata_value = metadata.get(key)
1238+
1239+
if api_value != metadata_value:
1240+
LOGGER.error(f"Mismatch for custom property '{key}': API='{api_value}' vs metadata='{metadata_value}'")
1241+
return False
1242+
1243+
if api_value is not None: # Only log if the property exists
1244+
LOGGER.info(f"Custom property '{key}' matches: '{api_value}'")
1245+
1246+
LOGGER.info("All custom properties match metadata.json values")
1247+
return True
1248+
1249+
1250+
def get_metadata_from_catalog_pod(model_catalog_pod: Pod, model_name: str) -> dict[str, Any]:
1251+
"""
1252+
Read and parse metadata.json for a model from the catalog pod.
1253+
1254+
Args:
1255+
model_catalog_pod: The catalog pod instance
1256+
model_name: Name of the model
1257+
1258+
Returns:
1259+
Parsed metadata.json content
1260+
1261+
Raises:
1262+
Exception: If metadata.json cannot be read or parsed
1263+
"""
1264+
metadata_path = f"/shared-benchmark-data/{model_name}/metadata.json"
1265+
LOGGER.info(f"Reading metadata from: {metadata_path}")
1266+
1267+
try:
1268+
metadata_json = model_catalog_pod.execute(command=["cat", metadata_path], container=CATALOG_CONTAINER)
1269+
metadata = json.loads(metadata_json)
1270+
LOGGER.info(f"Successfully loaded metadata.json for model '{model_name}'")
1271+
return metadata
1272+
except Exception as e:
1273+
LOGGER.error(f"Failed to read metadata.json for model '{model_name}': {e}")
1274+
raise

0 commit comments

Comments
 (0)