|
| 1 | +"""Tests for Dapr component directory structure refactoring. |
| 2 | +
|
| 3 | +This module verifies that Dapr components can be organized in subdirectories |
| 4 | +by component type (statestore, pubsub, bindings, secretstore, configuration) |
| 5 | +and that Dapr runtime can correctly discover and load them. |
| 6 | +""" |
| 7 | + |
| 8 | +import os |
| 9 | +from pathlib import Path |
| 10 | +from typing import Dict, List |
| 11 | + |
| 12 | +import pytest |
| 13 | +import yaml |
| 14 | + |
| 15 | + |
| 16 | +@pytest.fixture |
| 17 | +def dapr_components_base_path() -> Path: |
| 18 | + """Get the base path for Dapr components.""" |
| 19 | + repo_root = Path(__file__).parent.parent |
| 20 | + return repo_root / ".devcontainer" / "dapr-components" |
| 21 | + |
| 22 | + |
| 23 | +@pytest.fixture |
| 24 | +def expected_component_structure() -> Dict[str, List[str]]: |
| 25 | + """Define the expected directory structure for Dapr components. |
| 26 | +
|
| 27 | + Returns: |
| 28 | + Dictionary mapping subdirectory names to expected YAML files. |
| 29 | + """ |
| 30 | + return { |
| 31 | + "statestore": ["postgres.yaml", "redis.yaml", "default.yaml"], |
| 32 | + "pubsub": ["redis.yaml"], |
| 33 | + "bindings": ["http.yaml"], |
| 34 | + "secretstore": ["env.yaml"], |
| 35 | + "configuration": ["default.yaml"], |
| 36 | + } |
| 37 | + |
| 38 | + |
| 39 | +class TestDaprComponentDirectoryStructure: |
| 40 | + """Test suite for Dapr component directory organization.""" |
| 41 | + |
| 42 | + def test_subdirectories_exist( |
| 43 | + self, dapr_components_base_path: Path, expected_component_structure: Dict[str, List[str]] |
| 44 | + ): |
| 45 | + """Verify all expected subdirectories exist.""" |
| 46 | + for subdir in expected_component_structure.keys(): |
| 47 | + subdir_path = dapr_components_base_path / subdir |
| 48 | + assert subdir_path.exists(), f"Subdirectory {subdir}/ should exist" |
| 49 | + assert subdir_path.is_dir(), f"{subdir}/ should be a directory" |
| 50 | + |
| 51 | + def test_yaml_files_in_correct_locations( |
| 52 | + self, dapr_components_base_path: Path, expected_component_structure: Dict[str, List[str]] |
| 53 | + ): |
| 54 | + """Verify YAML files are in correct subdirectories with correct names.""" |
| 55 | + for subdir, expected_files in expected_component_structure.items(): |
| 56 | + subdir_path = dapr_components_base_path / subdir |
| 57 | + |
| 58 | + for expected_file in expected_files: |
| 59 | + file_path = subdir_path / expected_file |
| 60 | + assert file_path.exists(), f"File {subdir}/{expected_file} should exist" |
| 61 | + assert file_path.is_file(), f"{subdir}/{expected_file} should be a file" |
| 62 | + assert file_path.suffix == ".yaml", f"{subdir}/{expected_file} should be a YAML file" |
| 63 | + |
| 64 | + def test_no_prefixed_files_in_root(self, dapr_components_base_path: Path): |
| 65 | + """Verify old prefixed files no longer exist in root directory.""" |
| 66 | + old_files = [ |
| 67 | + "statestore-postgres.yaml", |
| 68 | + "statestore-redis.yaml", |
| 69 | + "statestore.yaml", |
| 70 | + "pubsub-redis.yaml", |
| 71 | + "secretstore-env.yaml", |
| 72 | + "bindings-http.yaml", |
| 73 | + "configuration.yaml", |
| 74 | + ] |
| 75 | + |
| 76 | + for old_file in old_files: |
| 77 | + old_file_path = dapr_components_base_path / old_file |
| 78 | + assert not old_file_path.exists(), f"Old file {old_file} should not exist in root" |
| 79 | + |
| 80 | + def test_yaml_files_are_valid( |
| 81 | + self, dapr_components_base_path: Path, expected_component_structure: Dict[str, List[str]] |
| 82 | + ): |
| 83 | + """Verify all YAML files have valid syntax and Dapr component structure.""" |
| 84 | + for subdir, expected_files in expected_component_structure.items(): |
| 85 | + subdir_path = dapr_components_base_path / subdir |
| 86 | + |
| 87 | + for expected_file in expected_files: |
| 88 | + file_path = subdir_path / expected_file |
| 89 | + |
| 90 | + with open(file_path, "r") as f: |
| 91 | + component = yaml.safe_load(f) |
| 92 | + |
| 93 | + # Verify required Dapr component fields |
| 94 | + assert "apiVersion" in component, f"{subdir}/{expected_file} missing apiVersion" |
| 95 | + assert "kind" in component, f"{subdir}/{expected_file} missing kind" |
| 96 | + assert "metadata" in component, f"{subdir}/{expected_file} missing metadata" |
| 97 | + assert "spec" in component, f"{subdir}/{expected_file} missing spec" |
| 98 | + |
| 99 | + assert component["apiVersion"] == "dapr.io/v1alpha1", f"{subdir}/{expected_file} wrong apiVersion" |
| 100 | + assert component["kind"] == "Component", f"{subdir}/{expected_file} should be Component kind" |
| 101 | + assert "name" in component["metadata"], f"{subdir}/{expected_file} missing component name" |
| 102 | + |
| 103 | + def test_component_names_are_consistent( |
| 104 | + self, dapr_components_base_path: Path, expected_component_structure: Dict[str, List[str]] |
| 105 | + ): |
| 106 | + """Verify component names follow consistent naming conventions.""" |
| 107 | + for subdir, expected_files in expected_component_structure.items(): |
| 108 | + subdir_path = dapr_components_base_path / subdir |
| 109 | + |
| 110 | + for expected_file in expected_files: |
| 111 | + file_path = subdir_path / expected_file |
| 112 | + |
| 113 | + with open(file_path, "r") as f: |
| 114 | + component = yaml.safe_load(f) |
| 115 | + |
| 116 | + component_name = component["metadata"]["name"] |
| 117 | + file_basename = expected_file.replace(".yaml", "") |
| 118 | + |
| 119 | + # Component names should reflect their type and backend |
| 120 | + if file_basename == "default": |
| 121 | + # Default components use just the type name |
| 122 | + assert component_name in ["statestore", "configuration"], \ |
| 123 | + f"Default component in {subdir}/ has unexpected name: {component_name}" |
| 124 | + else: |
| 125 | + # Named components should include backend identifier |
| 126 | + # e.g., statestore-postgres, statestore-redis, pubsub-redis |
| 127 | + assert subdir in component_name or file_basename in component_name, \ |
| 128 | + f"Component name '{component_name}' should reference {subdir} or {file_basename}" |
| 129 | + |
| 130 | + def test_statestore_components_have_correct_types(self, dapr_components_base_path: Path): |
| 131 | + """Verify statestore components have correct Dapr state store types.""" |
| 132 | + statestore_path = dapr_components_base_path / "statestore" |
| 133 | + |
| 134 | + # postgres.yaml should use state.postgresql |
| 135 | + with open(statestore_path / "postgres.yaml", "r") as f: |
| 136 | + postgres = yaml.safe_load(f) |
| 137 | + assert postgres["spec"]["type"] == "state.postgresql", "postgres.yaml should use state.postgresql" |
| 138 | + |
| 139 | + # redis.yaml and default.yaml should use state.redis |
| 140 | + with open(statestore_path / "redis.yaml", "r") as f: |
| 141 | + redis = yaml.safe_load(f) |
| 142 | + assert redis["spec"]["type"] == "state.redis", "redis.yaml should use state.redis" |
| 143 | + |
| 144 | + with open(statestore_path / "default.yaml", "r") as f: |
| 145 | + default = yaml.safe_load(f) |
| 146 | + assert default["spec"]["type"] == "state.redis", "default.yaml should use state.redis" |
| 147 | + |
| 148 | + def test_readme_exists_in_root(self, dapr_components_base_path: Path): |
| 149 | + """Verify README.md still exists in root directory.""" |
| 150 | + readme_path = dapr_components_base_path / "README.md" |
| 151 | + assert readme_path.exists(), "README.md should exist in dapr-components root" |
| 152 | + assert readme_path.is_file(), "README.md should be a file" |
| 153 | + |
| 154 | + |
| 155 | +class TestDaprComponentDiscovery: |
| 156 | + """Test suite for Dapr runtime component discovery.""" |
| 157 | + |
| 158 | + def test_dapr_scans_subdirectories_recursively(self, dapr_components_base_path: Path): |
| 159 | + """Verify Dapr components-path supports subdirectory scanning. |
| 160 | +
|
| 161 | + Note: This is a structural test. Runtime verification happens in |
| 162 | + test-docker-stack.ps1 by checking Dapr metadata endpoint. |
| 163 | + """ |
| 164 | + # Dapr runtime automatically scans subdirectories for component files |
| 165 | + # We verify the structure allows for this behavior |
| 166 | + |
| 167 | + all_component_files = list(dapr_components_base_path.rglob("*.yaml")) |
| 168 | + |
| 169 | + # Filter out README.md and only include YAML component files |
| 170 | + component_yamls = [ |
| 171 | + f for f in all_component_files |
| 172 | + if f.is_file() and f.parent != dapr_components_base_path |
| 173 | + ] |
| 174 | + |
| 175 | + # Should have at least 7 component files in subdirectories |
| 176 | + assert len(component_yamls) >= 7, f"Expected at least 7 components in subdirectories, found {len(component_yamls)}" |
| 177 | + |
| 178 | + # All component files should be in subdirectories, not root |
| 179 | + for component_file in component_yamls: |
| 180 | + assert component_file.parent != dapr_components_base_path, \ |
| 181 | + f"Component {component_file.name} should be in a subdirectory" |
| 182 | + |
| 183 | + |
| 184 | +if __name__ == "__main__": |
| 185 | + pytest.main([__file__, "-v"]) |
0 commit comments