-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
66e2c73
commit cb82aa2
Showing
3 changed files
with
211 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,155 @@ | ||
"""Test configuration.""" | ||
from typing import Any, Type | ||
|
||
from pytest import ExitCode, Session | ||
from dbt_score.models import Model | ||
from dbt_score.rule import Rule, RuleViolation, rule | ||
from pytest import ExitCode, Session, fixture | ||
|
||
|
||
def pytest_sessionfinish(session: Session, exitstatus: int): | ||
"""Avoid ci failure if no tests are found.""" | ||
if exitstatus == ExitCode.NO_TESTS_COLLECTED: | ||
session.exitstatus = ExitCode.OK | ||
|
||
|
||
@fixture | ||
def raw_manifest() -> dict[str, Any]: | ||
"""Mock the raw manifest.""" | ||
return { | ||
"nodes": { | ||
"analysis.package.analysis1": {"resource_type": "analysis"}, | ||
"model.package.model1": { | ||
"resource_type": "model", | ||
"unique_id": "model.package.model1", | ||
"name": "model1", | ||
"relation_name": "database.schema.model1", | ||
"description": "Description1.", | ||
"original_file_path": "/path/to/model1.sql", | ||
"config": {}, | ||
"meta": {}, | ||
"columns": { | ||
"a": { | ||
"name": "column_a", | ||
"description": "Column A.", | ||
"data_type": "string", | ||
"meta": {}, | ||
"constraints": [], | ||
"tags": [], | ||
} | ||
}, | ||
"package_name": "package", | ||
"database": "db", | ||
"schema": "schema", | ||
"raw_code": "SELECT x FROM y", | ||
"alias": "model1_alias", | ||
"patch_path": "/path/to/model1.yml", | ||
"tags": [], | ||
"depends_on": {}, | ||
}, | ||
"model.package.model2": { | ||
"resource_type": "model", | ||
"unique_id": "model.package.model2", | ||
"name": "model2", | ||
"relation_name": "database.schema.model2", | ||
"description": "Description2.", | ||
"original_file_path": "/path/to/model2.sql", | ||
"config": {}, | ||
"meta": {}, | ||
"columns": { | ||
"a": { | ||
"name": "column_a", | ||
"description": "Column A.", | ||
"data_type": "string", | ||
"meta": {}, | ||
"constraints": [], | ||
"tags": [], | ||
} | ||
}, | ||
"package_name": "package", | ||
"database": "db", | ||
"schema": "schema", | ||
"raw_code": "SELECT x FROM y", | ||
"alias": "model2_alias", | ||
"patch_path": "/path/to/model2.yml", | ||
"tags": [], | ||
"depends_on": {}, | ||
}, | ||
"test.package.test1": { | ||
"resource_type": "test", | ||
"attached_node": "model.package.model1", | ||
"name": "test1", | ||
"test_metadata": {"name": "type", "kwargs": {"column_name": "a"}}, | ||
"tags": [], | ||
}, | ||
"test.package.test2": { | ||
"resource_type": "test", | ||
"attached_node": "model.package.model1", | ||
"name": "test2", | ||
"test_metadata": {"name": "type", "kwargs": {}}, | ||
"tags": [], | ||
}, | ||
"test.package.test3": {"resource_type": "test"}, | ||
} | ||
} | ||
|
||
|
||
@fixture | ||
def model1(raw_manifest) -> Model: | ||
"""Model 1.""" | ||
return Model.from_node(raw_manifest["nodes"]["model.package.model1"], []) | ||
|
||
|
||
@fixture | ||
def model2(raw_manifest) -> Model: | ||
"""Model 2.""" | ||
return Model.from_node(raw_manifest["nodes"]["model.package.model2"], []) | ||
|
||
|
||
@fixture | ||
def decorator_rule() -> Type[Rule]: | ||
"""An example rule created with the rule decorator.""" | ||
|
||
@rule() | ||
def example_rule(model: Model) -> RuleViolation | None: | ||
"""Description of the rule.""" | ||
if model.name == "model1": | ||
return RuleViolation(message="Model1 is a violation.") | ||
return None | ||
|
||
return example_rule | ||
|
||
|
||
@fixture | ||
def class_rule() -> Type[Rule]: | ||
"""An example rule created with a class.""" | ||
|
||
class ExampleRule(Rule): | ||
"""Example rule.""" | ||
|
||
description = "Description of the rule." | ||
|
||
def evaluate(self, model: Model) -> RuleViolation | None: | ||
"""Evaluate model.""" | ||
if model.name == "model1": | ||
return RuleViolation(message="Model1 is a violation.") | ||
return None | ||
|
||
return ExampleRule | ||
|
||
|
||
@fixture | ||
def invalid_class_rule() -> Type[Rule]: | ||
"""An example rule created with a class.""" | ||
|
||
class ExampleRule(Rule): | ||
"""Example rule.""" | ||
|
||
description = "Description of the rule." | ||
|
||
def evaluate(self, model: Model) -> RuleViolation | None: | ||
"""Evaluate model.""" | ||
if model.name == "model1": | ||
return RuleViolation(message="Model1 is a violation.") | ||
return None | ||
|
||
return ExampleRule |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
"""Test models.""" | ||
|
||
from pathlib import Path | ||
from unittest.mock import patch | ||
|
||
from dbt_score.models import ManifestLoader | ||
|
||
|
||
@patch("dbt_score.models.Path.read_text") | ||
def test_manifest(read_text, raw_manifest): | ||
"""Test loading a manifest.""" | ||
with patch("dbt_score.models.json.loads", return_value=raw_manifest): | ||
loader = ManifestLoader(Path("manifest.json")) | ||
assert len(loader.models) == len([node for node in | ||
raw_manifest["nodes"].values() | ||
if node["resource_type"] == "model"]) | ||
assert loader.models[0].tests[0].name == "test2" | ||
assert loader.models[0].columns[0].tests[0].name == "test1" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
"""Test rule.""" | ||
|
||
import pytest | ||
from dbt_score.models import Model | ||
from dbt_score.rule import Rule, RuleViolation, Severity | ||
|
||
|
||
def test_rule_decorator(decorator_rule, class_rule, model1, model2): | ||
"""Test rule creation with the rule decorator and class.""" | ||
decorator_rule_instance = decorator_rule() | ||
class_rule_instance = class_rule() | ||
|
||
def assertions(rule_instance): | ||
assert isinstance(rule_instance, Rule) | ||
assert rule_instance.severity == Severity.MEDIUM | ||
assert rule_instance.description == "Description of the rule." | ||
assert rule_instance.evaluate(model1) == RuleViolation( | ||
message="Model1 is a violation.") | ||
assert rule_instance.evaluate(model2) is None | ||
|
||
assertions(decorator_rule_instance) | ||
assertions(class_rule_instance) | ||
|
||
|
||
def test_missing_description_rule_class(class_rule): | ||
"""Test missing description in rule class.""" | ||
with pytest.raises(TypeError): | ||
class BadRule(Rule): | ||
"""Bad example rule.""" | ||
|
||
def evaluate(self, model: Model) -> RuleViolation | None: | ||
"""Evaluate model.""" | ||
return None | ||
|
||
|
||
def test_missing_evaluate_rule_class(class_rule, model1): | ||
"""Test missing evaluate implementation in rule class.""" | ||
class BadRule(Rule): | ||
"""Bad example rule.""" | ||
description = "Description of the rule." | ||
|
||
rule = BadRule() | ||
|
||
with pytest.raises(NotImplementedError): | ||
rule.evaluate(model1) | ||
|