|
3 | 3 | from __future__ import annotations |
4 | 4 |
|
5 | 5 | import typing as t |
| 6 | +from pathlib import Path |
6 | 7 |
|
| 8 | +from ruamel.yaml import YAML |
7 | 9 | from sqlglot.expressions import Star |
8 | 10 | from sqlglot.helper import subclasses |
9 | 11 |
|
10 | 12 | from sqlmesh.core.constants import EXTERNAL_MODELS_YAML |
11 | 13 | from sqlmesh.core.dialect import normalize_model_name |
| 14 | +from sqlmesh.core.linter.definition import RuleSet |
12 | 15 | from sqlmesh.core.linter.helpers import ( |
13 | 16 | TokenPositionDetails, |
14 | 17 | get_range_of_model_block, |
15 | 18 | read_range_from_string, |
16 | 19 | ) |
17 | 20 | from sqlmesh.core.linter.rule import ( |
| 21 | + CreateFile, |
| 22 | + Fix, |
| 23 | + Position, |
| 24 | + Range, |
18 | 25 | Rule, |
19 | 26 | RuleViolation, |
20 | | - Range, |
21 | | - Fix, |
22 | 27 | TextEdit, |
23 | | - Position, |
24 | | - CreateFile, |
25 | 28 | ) |
26 | | -from sqlmesh.core.linter.definition import RuleSet |
27 | | -from sqlmesh.core.model import Model, SqlModel, ExternalModel |
28 | | -from sqlmesh.utils.lineage import extract_references_from_query, ExternalModelReference |
| 29 | +from sqlmesh.core.model import ExternalModel, Model, SqlModel |
| 30 | +from sqlmesh.utils.lineage import ExternalModelReference, extract_references_from_query |
29 | 31 |
|
30 | 32 |
|
31 | 33 | class NoSelectStar(Rule): |
@@ -129,6 +131,40 @@ def check_model(self, model: Model) -> t.Optional[RuleViolation]: |
129 | 131 | return self.violation() |
130 | 132 |
|
131 | 133 |
|
| 134 | +class NoMissingUnitTest(Rule): |
| 135 | + """All models must have a unit test found in the test/ directory yaml files""" |
| 136 | + |
| 137 | + def check_model(self, model: Model) -> t.Optional[RuleViolation]: |
| 138 | + # External models cannot have unit tests |
| 139 | + if isinstance(model, ExternalModel): |
| 140 | + return None |
| 141 | + |
| 142 | + test_dir = Path("tests") |
| 143 | + found_test = False |
| 144 | + |
| 145 | + yaml_parser = YAML(typ="safe") |
| 146 | + for test_file in test_dir.rglob("*.yaml"): |
| 147 | + try: |
| 148 | + test_data = yaml_parser.load(test_file) or {} |
| 149 | + except Exception: |
| 150 | + # Skip files with Jinja templating or other parse errors |
| 151 | + continue |
| 152 | + |
| 153 | + for _, test_config in test_data.items(): |
| 154 | + print(f"Test_Config: {test_config}") |
| 155 | + if test_config.get("model") == model.name: |
| 156 | + found_test = True |
| 157 | + break |
| 158 | + if found_test: |
| 159 | + break |
| 160 | + |
| 161 | + if not found_test: |
| 162 | + return self.violation( |
| 163 | + violation_msg=f"Model {model.name} is missing unit test(s). Please add in the tests/ directory." |
| 164 | + ) |
| 165 | + return None |
| 166 | + |
| 167 | + |
132 | 168 | class NoMissingExternalModels(Rule): |
133 | 169 | """All external models must be registered in the external_models.yaml file""" |
134 | 170 |
|
|
0 commit comments