|
| 1 | +""" |
| 2 | +Simplified unit tests for CRD format validation. |
| 3 | +
|
| 4 | +This module contains essential tests to validate the basic format and structure |
| 5 | +of the CRD YAML files used by the inference operator, focusing on core |
| 6 | +Kubernetes CRD requirements. |
| 7 | +""" |
| 8 | + |
| 9 | +import unittest |
| 10 | +import yaml |
| 11 | +from pathlib import Path |
| 12 | + |
| 13 | + |
| 14 | +class TestCRDFormat(unittest.TestCase): |
| 15 | + """Test class for validating essential CRD format requirements.""" |
| 16 | + |
| 17 | + def setUp(self): |
| 18 | + """Set up test class with file paths.""" |
| 19 | + self.base_path = Path(__file__).parent.parent.parent.parent |
| 20 | + self.crd_path = self.base_path / "helm_chart" / "HyperPodHelmChart" / "charts" / "inference-operator" / "config" / "crd" |
| 21 | + |
| 22 | + self.crd_files = [ |
| 23 | + self.crd_path / "inference.sagemaker.aws.amazon.com_inferenceendpointconfigs.yaml", |
| 24 | + self.crd_path / "inference.sagemaker.aws.amazon.com_jumpstartmodels.yaml", |
| 25 | + self.crd_path / "inference.sagemaker.aws.amazon.com_sagemakerendpointregistrations.yaml" |
| 26 | + ] |
| 27 | + |
| 28 | + def test_crd_files_exist_and_valid_yaml(self): |
| 29 | + """Test that all CRD files exist and have valid YAML syntax.""" |
| 30 | + for file_path in self.crd_files: |
| 31 | + with self.subTest(file=file_path.name): |
| 32 | + # Check file exists |
| 33 | + self.assertTrue(file_path.exists(), f"CRD file does not exist: {file_path}") |
| 34 | + |
| 35 | + # Check for tab characters (not allowed in YAML) |
| 36 | + with open(file_path, 'r', encoding='utf-8') as f: |
| 37 | + content_text = f.read() |
| 38 | + if '\t' in content_text: |
| 39 | + self.fail(f"File {file_path.name} contains tab characters. YAML should use spaces for indentation.") |
| 40 | + |
| 41 | + # Check valid YAML |
| 42 | + with open(file_path, 'r', encoding='utf-8') as f: |
| 43 | + try: |
| 44 | + content = yaml.safe_load(f) |
| 45 | + self.assertIsNotNone(content, f"YAML content is empty in {file_path.name}") |
| 46 | + except yaml.YAMLError as e: |
| 47 | + self.fail(f"Invalid YAML syntax in {file_path.name}: {e}") |
| 48 | + |
| 49 | + def test_required_crd_structure(self): |
| 50 | + """Test that all CRD files have the required Kubernetes CRD structure.""" |
| 51 | + for file_path in self.crd_files: |
| 52 | + with self.subTest(file=file_path.name): |
| 53 | + with open(file_path, 'r', encoding='utf-8') as f: |
| 54 | + content = yaml.safe_load(f) |
| 55 | + |
| 56 | + # Check required top-level fields |
| 57 | + required_fields = ['apiVersion', 'kind', 'metadata', 'spec'] |
| 58 | + for field in required_fields: |
| 59 | + self.assertIn(field, content, f"Missing required field '{field}' in {file_path.name}") |
| 60 | + |
| 61 | + # Verify this is a CustomResourceDefinition |
| 62 | + self.assertEqual(content['apiVersion'], "apiextensions.k8s.io/v1", |
| 63 | + f"Expected apiVersion 'apiextensions.k8s.io/v1' in {file_path.name}") |
| 64 | + self.assertEqual(content['kind'], "CustomResourceDefinition", |
| 65 | + f"Expected kind 'CustomResourceDefinition' in {file_path.name}") |
| 66 | + |
| 67 | + def test_crd_spec_structure(self): |
| 68 | + """Test that CRD spec has required fields and basic structure.""" |
| 69 | + for file_path in self.crd_files: |
| 70 | + with self.subTest(file=file_path.name): |
| 71 | + with open(file_path, 'r', encoding='utf-8') as f: |
| 72 | + content = yaml.safe_load(f) |
| 73 | + |
| 74 | + spec = content.get('spec', {}) |
| 75 | + |
| 76 | + # Check required spec fields |
| 77 | + required_spec_fields = ['group', 'names', 'scope', 'versions'] |
| 78 | + for field in required_spec_fields: |
| 79 | + self.assertIn(field, spec, f"Missing required spec field '{field}' in {file_path.name}") |
| 80 | + |
| 81 | + # Validate spec.group |
| 82 | + self.assertEqual(spec['group'], "inference.sagemaker.aws.amazon.com", |
| 83 | + f"Expected group 'inference.sagemaker.aws.amazon.com' in {file_path.name}") |
| 84 | + |
| 85 | + # Validate spec.scope |
| 86 | + self.assertEqual(spec['scope'], "Namespaced", |
| 87 | + f"Expected scope 'Namespaced' in {file_path.name}") |
| 88 | + |
| 89 | + def test_crd_names_structure(self): |
| 90 | + """Test that CRD names section has required fields.""" |
| 91 | + for file_path in self.crd_files: |
| 92 | + with self.subTest(file=file_path.name): |
| 93 | + with open(file_path, 'r', encoding='utf-8') as f: |
| 94 | + content = yaml.safe_load(f) |
| 95 | + |
| 96 | + names = content.get('spec', {}).get('names', {}) |
| 97 | + |
| 98 | + # Check required names fields |
| 99 | + required_names_fields = ['kind', 'listKind', 'plural', 'singular'] |
| 100 | + for field in required_names_fields: |
| 101 | + self.assertIn(field, names, f"Missing required names field '{field}' in {file_path.name}") |
| 102 | + self.assertTrue(names[field], f"Empty value for names.{field} in {file_path.name}") |
| 103 | + |
| 104 | + def test_crd_versions_structure(self): |
| 105 | + """Test that CRD versions are properly structured with required fields.""" |
| 106 | + for file_path in self.crd_files: |
| 107 | + with self.subTest(file=file_path.name): |
| 108 | + with open(file_path, 'r', encoding='utf-8') as f: |
| 109 | + content = yaml.safe_load(f) |
| 110 | + |
| 111 | + versions = content.get('spec', {}).get('versions', []) |
| 112 | + |
| 113 | + # Validate versions is a non-empty list |
| 114 | + self.assertIsInstance(versions, list, f"spec.versions should be a list in {file_path.name}") |
| 115 | + self.assertGreater(len(versions), 0, f"spec.versions should not be empty in {file_path.name}") |
| 116 | + |
| 117 | + # Check each version has required fields |
| 118 | + for i, version in enumerate(versions): |
| 119 | + required_version_fields = ['name', 'served', 'storage', 'schema'] |
| 120 | + for field in required_version_fields: |
| 121 | + self.assertIn(field, version, |
| 122 | + f"Missing required field '{field}' in version {i} of {file_path.name}") |
| 123 | + |
| 124 | + # Validate schema has openAPIV3Schema |
| 125 | + schema = version.get('schema', {}) |
| 126 | + self.assertIn('openAPIV3Schema', schema, |
| 127 | + f"Missing 'openAPIV3Schema' in version {i} schema of {file_path.name}") |
| 128 | + |
| 129 | + openapi_schema = schema.get('openAPIV3Schema', {}) |
| 130 | + self.assertIn('type', openapi_schema, |
| 131 | + f"Missing 'type' in openAPIV3Schema for version {i} of {file_path.name}") |
| 132 | + self.assertEqual(openapi_schema['type'], 'object', |
| 133 | + f"Expected 'type: object' in openAPIV3Schema for version {i} of {file_path.name}") |
| 134 | + |
| 135 | + def test_metadata_name_format(self): |
| 136 | + """Test that metadata.name follows the expected CRD naming convention.""" |
| 137 | + expected_names = { |
| 138 | + 'inferenceendpointconfigs': 'inferenceendpointconfigs.inference.sagemaker.aws.amazon.com', |
| 139 | + 'jumpstartmodels': 'jumpstartmodels.inference.sagemaker.aws.amazon.com', |
| 140 | + 'sagemakerendpointregistrations': 'sagemakerendpointregistrations.inference.sagemaker.aws.amazon.com' |
| 141 | + } |
| 142 | + |
| 143 | + for file_path in self.crd_files: |
| 144 | + with self.subTest(file=file_path.name): |
| 145 | + with open(file_path, 'r', encoding='utf-8') as f: |
| 146 | + content = yaml.safe_load(f) |
| 147 | + |
| 148 | + name = content.get('metadata', {}).get('name', '') |
| 149 | + |
| 150 | + # Find expected name based on filename |
| 151 | + expected_name = None |
| 152 | + for key, value in expected_names.items(): |
| 153 | + if key in file_path.name: |
| 154 | + expected_name = value |
| 155 | + break |
| 156 | + |
| 157 | + self.assertIsNotNone(expected_name, f"Could not determine expected name for {file_path.name}") |
| 158 | + self.assertEqual(name, expected_name, |
| 159 | + f"Expected metadata.name '{expected_name}' in {file_path.name}, got '{name}'") |
| 160 | + |
| 161 | + |
| 162 | +if __name__ == '__main__': |
| 163 | + unittest.main() |
0 commit comments