|
2 | 2 |
|
3 | 3 | from sqlmesh import Context |
4 | 4 | from sqlmesh.core.linter.rule import Position, Range |
| 5 | +from sqlmesh.core.model import load_sql_based_model |
| 6 | +from sqlmesh.core import dialect as d |
5 | 7 |
|
6 | 8 |
|
7 | 9 | def test_no_missing_external_models(tmp_path, copy_to_temp_path) -> None: |
@@ -31,6 +33,7 @@ def test_no_missing_external_models(tmp_path, copy_to_temp_path) -> None: |
31 | 33 | "nomissingaudits", |
32 | 34 | "nomissingowner", |
33 | 35 | "nomissingexternalmodels", |
| 36 | + "cronvalidator", |
34 | 37 | ], |
35 | 38 | ),""" |
36 | 39 | after = """linter=LinterConfig(enabled=True, rules=["nomissingexternalmodels"]),""" |
@@ -84,6 +87,7 @@ def test_no_missing_external_models_with_existing_file_ending_in_newline( |
84 | 87 | "nomissingaudits", |
85 | 88 | "nomissingowner", |
86 | 89 | "nomissingexternalmodels", |
| 90 | + "cronvalidator", |
87 | 91 | ], |
88 | 92 | ),""" |
89 | 93 | after = """linter=LinterConfig(enabled=True, rules=["nomissingexternalmodels"]),""" |
@@ -141,6 +145,7 @@ def test_no_missing_external_models_with_existing_file_not_ending_in_newline( |
141 | 145 | "nomissingaudits", |
142 | 146 | "nomissingowner", |
143 | 147 | "nomissingexternalmodels", |
| 148 | + "cronvalidator", |
144 | 149 | ], |
145 | 150 | ),""" |
146 | 151 | after = """linter=LinterConfig(enabled=True, rules=["nomissingexternalmodels"]),""" |
@@ -172,3 +177,57 @@ def test_no_missing_external_models_with_existing_file_not_ending_in_newline( |
172 | 177 | ) |
173 | 178 | fix_path = sushi_path / "external_models.yaml" |
174 | 179 | assert edit.path == fix_path |
| 180 | + |
| 181 | + |
| 182 | +def test_cron_validator(tmp_path, copy_to_temp_path) -> None: |
| 183 | + sushi_paths = copy_to_temp_path("examples/sushi") |
| 184 | + sushi_path = sushi_paths[0] |
| 185 | + |
| 186 | + # Override the config.py to turn on lint |
| 187 | + with open(sushi_path / "config.py", "r") as f: |
| 188 | + read_file = f.read() |
| 189 | + |
| 190 | + before = """ linter=LinterConfig( |
| 191 | + enabled=False, |
| 192 | + rules=[ |
| 193 | + "ambiguousorinvalidcolumn", |
| 194 | + "invalidselectstarexpansion", |
| 195 | + "noselectstar", |
| 196 | + "nomissingaudits", |
| 197 | + "nomissingowner", |
| 198 | + "nomissingexternalmodels", |
| 199 | + "cronvalidator", |
| 200 | + ], |
| 201 | + ),""" |
| 202 | + after = """linter=LinterConfig(enabled=True, rules=["cronvalidator"]),""" |
| 203 | + read_file = read_file.replace(before, after) |
| 204 | + assert after in read_file |
| 205 | + with open(sushi_path / "config.py", "w") as f: |
| 206 | + f.writelines(read_file) |
| 207 | + |
| 208 | + # Load the context with the temporary sushi path |
| 209 | + context = Context(paths=[sushi_path]) |
| 210 | + |
| 211 | + context.load() |
| 212 | + |
| 213 | + # Create model with shorter cron interval that depends on model with longer interval |
| 214 | + upstream_model = load_sql_based_model( |
| 215 | + d.parse("MODEL (name memory.sushi.step_1, cron '@weekly'); SELECT * FROM (SELECT 1)") |
| 216 | + ) |
| 217 | + |
| 218 | + downstream_model = load_sql_based_model( |
| 219 | + d.parse( |
| 220 | + "MODEL (name memory.sushi.step_2, cron '@daily', depends_on ['memory.sushi.step_1']); SELECT * FROM (SELECT 1)" |
| 221 | + ) |
| 222 | + ) |
| 223 | + |
| 224 | + context.upsert_model(upstream_model) |
| 225 | + context.upsert_model(downstream_model) |
| 226 | + |
| 227 | + lints = context.lint_models(raise_on_error=False) |
| 228 | + assert len(lints) == 1 |
| 229 | + lint = lints[0] |
| 230 | + assert ( |
| 231 | + lint.violation_msg |
| 232 | + == 'Upstream model "memory"."sushi"."step_1" has longer cron interval (@weekly) than this model (@daily)' |
| 233 | + ) |
0 commit comments