diff --git a/.changes/unreleased/Fixes-20241014-212135.yaml b/.changes/unreleased/Fixes-20241014-212135.yaml new file mode 100644 index 00000000000..8cc700095ba --- /dev/null +++ b/.changes/unreleased/Fixes-20241014-212135.yaml @@ -0,0 +1,6 @@ +kind: Fixes +body: Allow instances of generic data tests to be documented +time: 2024-10-14T21:21:35.767115+01:00 +custom: + Author: aranke + Issue: "2578" diff --git a/core/dbt/parser/generic_test_builders.py b/core/dbt/parser/generic_test_builders.py index 6bca8300dae..1c9478734bf 100644 --- a/core/dbt/parser/generic_test_builders.py +++ b/core/dbt/parser/generic_test_builders.py @@ -136,6 +136,17 @@ def __init__( if self.namespace is not None: self.package_name = self.namespace + # If the user has provided a description for this generic test, use it + # Then delete the "description" argument to: + # 1. Avoid passing it into the test macro + # 2. Avoid passing it into the test name synthesis + # Otherwise, use an empty string + self.description: str = "" + + if "description" in self.args: + self.description = self.args["description"] + del self.args["description"] + # If the user has provided a custom name for this generic test, use it # Then delete the "name" argument to avoid passing it into the test macro # Otherwise, use an auto-generated name synthesized from test inputs diff --git a/core/dbt/parser/schema_generic_tests.py b/core/dbt/parser/schema_generic_tests.py index 9f2538f06f0..58be6dc94be 100644 --- a/core/dbt/parser/schema_generic_tests.py +++ b/core/dbt/parser/schema_generic_tests.py @@ -96,6 +96,7 @@ def create_test_node( test_metadata: Dict[str, Any], file_key_name: str, column_name: Optional[str], + description: str, ) -> GenericTestNode: HASH_LENGTH = 10 @@ -134,6 +135,7 @@ def get_hashable_md(data: Union[str, int, float, List, Dict]) -> Union[str, List "column_name": column_name, "checksum": FileHash.empty().to_dict(omit_none=True), "file_key_name": file_key_name, + "description": description, } try: GenericTestNode.validate(dct) @@ -229,6 +231,7 @@ def parse_generic_test( column_name=column_name, test_metadata=metadata, file_key_name=file_key_name, + description=builder.description, ) self.render_test_update(node, config, builder, schema_file_id) diff --git a/tests/functional/generic_test_description/fixtures.py b/tests/functional/generic_test_description/fixtures.py new file mode 100644 index 00000000000..2be2f2313ba --- /dev/null +++ b/tests/functional/generic_test_description/fixtures.py @@ -0,0 +1,34 @@ +models__my_model_sql = """ +with my_cte as ( + select 1 as id, 'blue' as color + union all + select 2 as id, 'green' as red + union all + select 3 as id, 'red' as red +) +select * from my_cte +""" + +models__schema_yml = """ +models: + - name: my_model + columns: + - name: id + tests: + - unique: + description: "id must be unique" + - not_null + - name: color + tests: + - accepted_values: + values: ['blue', 'green', 'red'] + description: "{{ doc('color_accepted_values') }}" +""" + +models__doc_block_md = """ +{% docs color_accepted_values %} + +The `color` column must be one of 'blue', 'green', or 'red'. + +{% enddocs %} +""" diff --git a/tests/functional/generic_test_description/test_generic_test_description.py b/tests/functional/generic_test_description/test_generic_test_description.py new file mode 100644 index 00000000000..cf68ff1c33c --- /dev/null +++ b/tests/functional/generic_test_description/test_generic_test_description.py @@ -0,0 +1,32 @@ +import pytest + +from dbt.tests.util import get_artifact, run_dbt +from tests.functional.generic_test_description.fixtures import ( + models__doc_block_md, + models__my_model_sql, + models__schema_yml, +) + + +class TestBuiltinGenericTestDescription: + @pytest.fixture(scope="class") + def models(self): + return { + "my_model.sql": models__my_model_sql, + "schema.yml": models__schema_yml, + "doc_block.md": models__doc_block_md, + } + + def test_compile(self, project): + run_dbt(["compile"]) + manifest = get_artifact(project.project_root, "target", "manifest.json") + assert len(manifest["nodes"]) == 4 + + nodes = {node["alias"]: node for node in manifest["nodes"].values()} + + assert nodes["unique_my_model_id"]["description"] == "id must be unique" + assert nodes["not_null_my_model_id"]["description"] == "" + assert ( + nodes["accepted_values_my_model_color__blue__green__red"]["description"] + == "The `color` column must be one of 'blue', 'green', or 'red'." + ) diff --git a/tests/unit/parser/test_parser.py b/tests/unit/parser/test_parser.py index b9b414ebac2..d5f809252b3 100644 --- a/tests/unit/parser/test_parser.py +++ b/tests/unit/parser/test_parser.py @@ -279,6 +279,7 @@ def assertEqualNodes(node_one, node_two): - not_null: severity: WARN - accepted_values: + description: Only primary colors are allowed in here values: ['red', 'blue', 'green'] - foreign_package.test_case: arg: 100 @@ -631,6 +632,7 @@ def test__parse_basic_model_tests(self): self.assertEqual(tests[0].tags, []) self.assertEqual(tests[0].refs, [RefArgs(name="my_model")]) self.assertEqual(tests[0].column_name, "color") + self.assertEqual(tests[0].description, "Only primary colors are allowed in here") self.assertEqual(tests[0].package_name, "snowplow") self.assertTrue(tests[0].name.startswith("accepted_values_")) self.assertEqual(tests[0].fqn, ["snowplow", tests[0].name]) @@ -654,7 +656,7 @@ def test__parse_basic_model_tests(self): self.assertEqual(tests[1].tags, []) self.assertEqual(tests[1].refs, [RefArgs(name="my_model")]) self.assertEqual(tests[1].column_name, "color") - self.assertEqual(tests[1].column_name, "color") + self.assertEqual(tests[1].description, "") self.assertEqual(tests[1].fqn, ["snowplow", tests[1].name]) self.assertTrue(tests[1].name.startswith("foreign_package_test_case_")) self.assertEqual(tests[1].package_name, "snowplow")