Skip to content

Commit

Permalink
Add unit tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
jochemvandooren committed Mar 20, 2024
1 parent 66e2c73 commit cb82aa2
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 1 deletion.
148 changes: 147 additions & 1 deletion tests/conftest.py
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
18 changes: 18 additions & 0 deletions tests/test_models.py
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"
46 changes: 46 additions & 0 deletions tests/test_rule.py
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)

0 comments on commit cb82aa2

Please sign in to comment.