Repository: dbt-checkpoint/dbt-checkpoint (language: python)

Issue:
Add integration tests
**Describe the feature you'd like**
I would like to add integration tests to test `pre-commit-dbt` if the new version of `dbt` is released.



Discussion / hints:

Fix patch:
diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml
index 5452e353..eb9a7360 100644
--- a/.pre-commit-hooks.yaml
+++ b/.pre-commit-hooks.yaml
@@ -11,6 +11,13 @@
     language: python
     types_or: [yaml, sql]
     require_serial: true # because we need to process yaml and sql
+-   id: check-macro-arguments-have-desc
+    name: Check the macro arguments have description
+    description: Ensures that the macro has arguments with descriptions in properties file.
+    entry: check-macro-arguments-have-desc
+    language: python
+    types_or: [yaml, sql]
+    require_serial: true # because we need to process yaml and sql
 -   id: check-model-columns-have-desc
     name: Check the model columns have description
     description: Ensures that the model has columns with descriptions in properties file.
diff --git a/HOOKS.md b/HOOKS.md
index f5aa1922..a8bc04f3 100644
--- a/HOOKS.md
+++ b/HOOKS.md
@@ -38,6 +38,7 @@
 
 **Macro checks:**
  * [`check-macro-has-description`](https://github.com/offbi/pre-commit-dbt/blob/main/HOOKS.md#check-macro-has-description): Check the macro has description.
+ * [`check-macro-arguments-have-desc`](https://github.com/offbi/pre-commit-dbt/blob/main/HOOKS.md#check-macro-arguments-have-desc): Check the macro arguments have description.
 
 **Modifiers:**
  * [`generate-missing-sources`](https://github.com/offbi/pre-commit-dbt/blob/main/HOOKS.md#generate-missing-sources): If any source is missing this hook tries to create it.
@@ -1221,7 +1222,50 @@ You want to make sure that all macros have a description.
 
 #### Known limitations
 
-If you `run` your model and then you delete the description from a properties file, the hook success since the description is still present in `manifest.json`.
+If you `run` and then you delete the description from a properties file, the hook success since the description is still present in `manifest.json`.
+
+-----
+### `check-macro-arguments-have-desc`
+
+Ensures that the macro has arguments with descriptions in the properties file (usually `schema.yml`).
+
+#### Arguments
+
+`--manifest`: location of `manifest.json` file. Usually `target/manifest.json`. This file contains a full representation of dbt project. **Default: `target/manifest.json`**
+
+#### Example
+```
+repos:
+- repo: https://github.com/offbi/pre-commit-dbt
+ rev: v0.1.1
+ hooks:
+ - id: check-macro-arguments-have-desc
+```
+
+#### When to use it
+
+You want to make sure that all specified arguments in the properties files (usually `schema.yml`) have some description. **This hook does not validate if all macro arguments are also present in a properties file.**
+#### Requirements
+
+| Macro exists in `manifest.json` <sup id="a1">[1](#f1)</sup> | Macro exists in `catalog.json` <sup id="a2">[2](#f2)</sup> |
+| :----: | :----------: |
+| :x: Not needed since it also validates properties files | :x: Not needed |
+
+<sup id="f1">1</sup> It means that you need to run `dbt run`, `dbt compile` before run this hook.<br/>
+<sup id="f2">2</sup> It means that you need to run `dbt docs generate` before run this hook.
+
+#### How it works
+
+- Hook takes all changed `yml` and `SQL` files.
+- The macro name is obtained from the `SQL` file name.
+- The manifest is scanned for a macro.
+- Modified `yml` files are scanned for a macro.
+- If any argument in the found macro does not contain a description, the hook fails.
+- The description must be in either the yml file **or** the manifest.
+
+#### Known limitations
+
+If you `run` and then you delete argument description from a properties file, the hook success since the description is still present in `manifest.json`.
 
 -----
 
diff --git a/README.md b/README.md
index cb41f3e8..36f7473a 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,7 @@ If this is the case, `pre-commit-dbt` is here to help you!
 
 **Macro checks:**
  * [`check-macro-has-description`](https://github.com/offbi/pre-commit-dbt/blob/main/HOOKS.md#check-macro-has-description): Check the macro has description.
+ * [`check-macro-arguments-have-desc`](https://github.com/offbi/pre-commit-dbt/blob/main/HOOKS.md#check-macro-arguments-have-desc): Check the macro arguments have description.
 
 **Modifiers:**
  * [`generate-missing-sources`](https://github.com/offbi/pre-commit-dbt/blob/main/HOOKS.md#generate-missing-sources): If any source is missing this hook tries to create it.
diff --git a/pre_commit_dbt/check_macro_arguments_have_desc.py b/pre_commit_dbt/check_macro_arguments_have_desc.py
new file mode 100644
index 00000000..c4ff5234
--- /dev/null
+++ b/pre_commit_dbt/check_macro_arguments_have_desc.py
@@ -0,0 +1,93 @@
+import argparse
+import itertools
+from typing import Any
+from typing import Dict
+from typing import Optional
+from typing import Sequence
+from typing import Set
+from typing import Tuple
+
+from pre_commit_dbt.utils import add_filenames_args
+from pre_commit_dbt.utils import add_manifest_args
+from pre_commit_dbt.utils import get_filenames
+from pre_commit_dbt.utils import get_json
+from pre_commit_dbt.utils import get_macro_schemas
+from pre_commit_dbt.utils import get_macro_sqls
+from pre_commit_dbt.utils import get_macros
+from pre_commit_dbt.utils import JsonOpenError
+from pre_commit_dbt.utils import Macro
+from pre_commit_dbt.utils import MacroSchema
+
+
+def check_argument_desc(
+    paths: Sequence[str], manifest: Dict[str, Any]
+) -> Tuple[int, Dict[str, Any]]:
+    status_code = 0
+    ymls = get_filenames(paths, [".yml", ".yaml"])
+    sqls = get_macro_sqls(paths, manifest)
+    filenames = set(sqls.keys())
+
+    # get manifest macros that pre-commit found as changed
+    macros = get_macros(manifest, filenames)
+    # if user added schema but did not rerun the macro
+    schemas = get_macro_schemas(list(ymls.values()), filenames)
+    missing: Dict[str, Set[str]] = {}
+
+    for item in itertools.chain(macros, schemas):
+        missing_args = set()  # pragma: no mutate
+        if isinstance(item, MacroSchema):
+            macro_name = item.macro_name
+            missing_args = {
+                key.get("name")
+                for key in item.schema.get("arguments", [])
+                if not key.get("description")
+            }
+        # Macro
+        elif isinstance(item, Macro):
+            macro_name = item.filename
+            missing_args = {
+                key
+                for key, value in item.macro.get("arguments", {}).items()
+                if not value.get("description")
+            }
+        else:
+            continue  # pragma: no cover, no mutate
+        seen = missing.get(macro_name)
+        if seen:
+            if not missing_args:
+                missing[macro_name] = set()  # pragma: no mutate
+            else:
+                missing[macro_name] = seen.union(missing_args)
+        elif missing_args:
+            missing[macro_name] = missing_args
+
+    for macro, arguments in missing.items():
+        if arguments:
+            status_code = 1
+            result = "\n- ".join(list(arguments))  # pragma: no mutate
+            print(
+                f"{sqls.get(macro)}: "
+                f"following arguments are missing description:\n- {result}",
+            )
+    return status_code, missing
+
+
+def main(argv: Optional[Sequence[str]] = None) -> int:
+    parser = argparse.ArgumentParser()
+    add_filenames_args(parser)
+    add_manifest_args(parser)
+
+    args = parser.parse_args(argv)
+
+    try:
+        manifest = get_json(args.manifest)
+    except JsonOpenError as e:
+        print(f"Unable to load manifest file ({e})")
+        return 1
+
+    status_code, _ = check_argument_desc(paths=args.filenames, manifest=manifest)
+    return status_code
+
+
+if __name__ == "__main__":
+    exit(main())
diff --git a/setup.cfg b/setup.cfg
index 118c1137..39aed167 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -28,6 +28,7 @@ python_requires = >=3.7.1
 console_scripts =
     check-column-desc-are-same = pre_commit_dbt.check_column_desc_are_same:main
     check-macro-has-description = pre_commit_dbt.check_macro_has_description:main
+    check-macro-arguments-have-desc = pre_commit_dbt.check_macro_arguments_have_desc:main
     check-model-columns-have-desc = pre_commit_dbt.check_model_columns_have_desc:main
     check-model-has-all-columns = pre_commit_dbt.check_model_has_all_columns:main
     check-model-has-description = pre_commit_dbt.check_model_has_description:main


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 6e72fdda..95d82d60 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -151,6 +151,24 @@
             "description": "test description",
             "path": "macros/aa/with_description.sql",
         },
+        "macro.with_argument_description": {
+            "path": "macros/aa/with_argument_description.sql",
+            "arguments": {
+                "test1": {"name": "test1", "description": "test"},
+                "test2": {"name": "test2", "description": "test"},
+            },
+        },
+        "macro.with_some_argument_description": {
+            "path": "macros/aa/with_some_argument_description.sql",
+            "arguments": {
+                "test1": {"name": "test1", "description": "test"},
+                "test2": {"name": "test2"},
+            },
+        },
+        "macro.without_arguments_description": {
+            "path": "macros/aa/without_arguments_description.sql",
+            "arguments": {"test1": {"name": "test1"}, "test2": {"name": "test2"}},
+        },
     },
     "child_map": {
         "source.test.test1": ["test.test1", "test.test2", "model.with_schema"],
diff --git a/tests/unit/test_check_macro_arguments_have_desc.py b/tests/unit/test_check_macro_arguments_have_desc.py
new file mode 100644
index 00000000..23ded516
--- /dev/null
+++ b/tests/unit/test_check_macro_arguments_have_desc.py
@@ -0,0 +1,126 @@
+import pytest
+
+from pre_commit_dbt.check_macro_arguments_have_desc import check_argument_desc
+from pre_commit_dbt.check_macro_arguments_have_desc import main
+
+
+# Input args, valid manifest, expected return value
+TESTS = (
+    (["macros/aa/with_argument_description.sql"], True, 0),
+    (["macros/aa/with_argument_description.sql"], False, 1),
+    (["macros/aa/without_arguments_description.sql"], True, 1),
+    (["macros/aa/with_some_argument_description.sql"], True, 1),
+)
+
+
+@pytest.mark.parametrize(
+    ("input_args", "valid_manifest", "expected_status_code"), TESTS
+)
+def test_check_macro_arguments_have_desc(
+    input_args, valid_manifest, expected_status_code, manifest_path_str
+):
+    if valid_manifest:
+        input_args.extend(["--manifest", manifest_path_str])
+    status_code = main(input_args)
+    assert status_code == expected_status_code
+
+
+@pytest.mark.parametrize("extension", [("yml"), ("yaml")])
+def test_check_macro_arguments_have_desc_in_schema(
+    extension, tmpdir, manifest_path_str
+):
+    schema_yml = """
+version: 2
+
+macros:
+-   name: in_schema_argument_description
+    arguments:
+    -   name: test
+        description: aaa
+    """
+    yml_file = tmpdir.join(f"schema.{extension}")
+    yml_file.write(schema_yml)
+    result = main(
+        argv=[
+            "in_schema_argument_description.sql",
+            str(yml_file),
+            "--manifest",
+            manifest_path_str,
+        ],
+    )
+    assert result == 0
+
+
+@pytest.mark.parametrize("extension", [("yml"), ("yaml")])
+def test_check_argument_desc(extension, tmpdir, manifest):
+    schema_yml = """
+version: 2
+
+macros:
+-   name: with_some_argument_description
+    arguments:
+    -   name: test1
+        description: aaa
+    -   name: test2
+    """
+    yml_file = tmpdir.join(f"schema.{extension}")
+    yml_file.write(schema_yml)
+    res_stat, missing = check_argument_desc(
+        ["macros/aa/with_some_argument_description.sql", str(yml_file)], manifest
+    )
+    assert res_stat == 1
+    assert missing == {"with_some_argument_description": {"test2"}}
+
+
+@pytest.mark.parametrize("extension", [("yml"), ("yaml")])
+def test_check_macro_arguments_have_desc_both(extension, tmpdir, manifest_path_str):
+    schema_yml = """
+version: 2
+
+macros:
+-   name: with_some_argument_description
+    arguments:
+    -   name: test4
+-   name: without_arguments_description
+    arguments:
+    -   name: test
+-   name: xxx
+    """
+    yml_file = tmpdir.join(f"schema.{extension}")
+    yml_file.write(schema_yml)
+    result = main(
+        argv=[
+            "macros/aa/with_some_argument_description.sql",
+            "macros/aa/without_arguments_description.sql",
+            str(yml_file),
+            "--manifest",
+            manifest_path_str,
+        ],
+    )
+    assert result == 1
+
+
+@pytest.mark.parametrize("extension", [("yml"), ("yaml")])
+def test_check_macro_arguments_have_desc_without(extension, tmpdir, manifest_path_str):
+    schema_yml = """
+version: 2
+
+macros:
+-   name: without_arguments_description
+    arguments:
+    -   name: test1
+        description: aa
+    -   name: test2
+        description: bb
+    """
+    yml_file = tmpdir.join(f"schema.{extension}")
+    yml_file.write(schema_yml)
+    result = main(
+        argv=[
+            "macros/aa/without_arguments_description.sql",
+            str(yml_file),
+            "--manifest",
+            manifest_path_str,
+        ],
+    )
+    assert result == 0