diff --git a/.github/workflows/github-action-ci.yml b/.github/workflows/github-action-ci.yml index d64f799..dec757f 100644 --- a/.github/workflows/github-action-ci.yml +++ b/.github/workflows/github-action-ci.yml @@ -43,7 +43,7 @@ jobs: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v5.3.0 + uses: actions/setup-python@v5.3.1 with: # Version range or exact version of Python to use, using SemVer's version range syntax. Reads from .python-version if unset. python-version: '3.10' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6d92fd9..068be94 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v5.0.0 hooks: - id: check-json - id: check-yaml diff --git a/README.md b/README.md index 396652d..38afbac 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ It requires the `az bicep` toolchain installed, and uses [`az bicep`](https://gi ## Demo Example usage of `pre-commit run --all-files` and -`git commit` after hook innstall in git repository `pre-commit install` +`git commit` after hook install in git repository `pre-commit install` ![alt text](https://raw.githubusercontent.com/Azure4DevOps/check-azure-bicep.example/master/example.gif) diff --git a/setup.py b/setup.py index 2f174af..76af402 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,11 @@ import sys def get_project_requirements() -> str: - with open(f"requirements.txt", "r") as f: + with open("requirements.txt", "r") as f: + return f.read() + +def get_long_description() -> str: + with open("README.md", "r", encoding="utf-8") as f: return f.read() def az_bicep_build(): @@ -45,7 +49,8 @@ def az_bicep_format(): setuptools.setup( name="check-azure-bicep-python", - description="check-azure-bicep-python", + description="Pre-commit hooks for Azure Bicep validation with built-in support for GitHub Workflows and Azure Pipelines", + long_description=get_long_description(), long_description_content_type="text/markdown", url="https://github.com/Azure4DevOps/check-azure-bicep", python_requires=">=3.10", diff --git a/tests/test_az_bicep_build.py b/tests/test_az_bicep_build.py new file mode 100644 index 0000000..973ce6c --- /dev/null +++ b/tests/test_az_bicep_build.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +"""Unit tests for az_bicep_build function.""" +import unittest +import sys +from unittest.mock import patch, MagicMock +from checkazurebiceppython import az_bicep_build + + +class TestAzBicepBuild(unittest.TestCase): + """Test cases for az_bicep_build function.""" + + @patch('checkazurebiceppython.subprocess.run') + @patch('checkazurebiceppython.glob.glob') + def test_bicep_build_no_files(self, mock_glob, mock_run): + """Test bicep build when no bicep files are found.""" + mock_glob.return_value = [] + mock_run.return_value = MagicMock(stdout="Bicep CLI version 0.4.1008", stderr=b"") + + # Should not raise any exception when no files found + az_bicep_build() + + # Verify version check was called + mock_run.assert_any_call( + ["az", "bicep", "version"], + stdout=unittest.mock.ANY, + text=True, + shell=True + ) + + @patch('checkazurebiceppython.subprocess.run') + @patch('checkazurebiceppython.glob.glob') + def test_bicep_build_successful(self, mock_glob, mock_run): + """Test bicep build with successful validation.""" + mock_glob.return_value = ['./test.bicep'] + + # Mock version and build calls (no upgrade because -noupgrade is hardcoded) + mock_run.side_effect = [ + MagicMock(stdout="Bicep CLI version 0.4.1008", stderr=b""), + MagicMock(stdout=b"Build successful", stderr=b"") + ] + + # Should complete without error + az_bicep_build() + + # Verify build was called for the file + self.assertEqual(mock_run.call_count, 2) + + @patch('checkazurebiceppython.subprocess.run') + @patch('checkazurebiceppython.glob.glob') + @patch('builtins.print') + def test_bicep_build_with_errors(self, mock_print, mock_glob, mock_run): + """Test bicep build with validation errors.""" + mock_glob.return_value = ['./test.bicep'] + + # Mock version and failed build (no upgrade because -noupgrade is hardcoded) + mock_run.side_effect = [ + MagicMock(stdout="Bicep CLI version 0.4.1008", stderr=b""), + MagicMock(stdout=b"", stderr=b"Error: Invalid syntax") + ] + + # Should exit with code 25 + with self.assertRaises(SystemExit) as cm: + az_bicep_build() + + self.assertEqual(cm.exception.code, 25) + + @patch('checkazurebiceppython.subprocess.run') + @patch('checkazurebiceppython.glob.glob') + def test_bicep_build_multiple_files(self, mock_glob, mock_run): + """Test bicep build with multiple files.""" + mock_glob.return_value = ['./test1.bicep', './test2.bicep'] + + # Mock version and two successful builds (no upgrade because -noupgrade is hardcoded) + mock_run.side_effect = [ + MagicMock(stdout="Bicep CLI version 0.4.1008", stderr=b""), + MagicMock(stdout=b"Build successful", stderr=b""), + MagicMock(stdout=b"Build successful", stderr=b"") + ] + + # Should complete without error + az_bicep_build() + + # Verify build was called for both files + self.assertEqual(mock_run.call_count, 3) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_az_bicep_format.py b/tests/test_az_bicep_format.py new file mode 100644 index 0000000..a1b390d --- /dev/null +++ b/tests/test_az_bicep_format.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +"""Unit tests for az_bicep_format function.""" +import unittest +import sys +from unittest.mock import patch, MagicMock +from checkazurebiceppython import az_bicep_format + + +class TestAzBicepFormat(unittest.TestCase): + """Test cases for az_bicep_format function.""" + + @patch('checkazurebiceppython.subprocess.run') + @patch('checkazurebiceppython.glob.glob') + def test_bicep_format_no_files(self, mock_glob, mock_run): + """Test bicep format when no bicep files are found.""" + mock_glob.return_value = [] + mock_run.return_value = MagicMock(stdout="Bicep CLI version 0.4.1008", stderr=b"") + + # Should not raise any exception when no files found + az_bicep_format() + + # Verify version check was called + mock_run.assert_any_call( + ["az", "bicep", "version"], + stdout=unittest.mock.ANY, + text=True, + shell=True + ) + + @patch('checkazurebiceppython.subprocess.run') + @patch('checkazurebiceppython.glob.glob') + def test_bicep_format_successful(self, mock_glob, mock_run): + """Test bicep format with successful formatting.""" + mock_glob.return_value = ['./test.bicep'] + + # Mock version and format calls (no upgrade because --noupgrade is hardcoded) + mock_run.side_effect = [ + MagicMock(stdout="Bicep CLI version 0.4.1008", stderr=b""), + MagicMock(stdout=b"Format successful", stderr=b"") + ] + + # Should complete without error + az_bicep_format() + + # Verify format was called for the file + self.assertEqual(mock_run.call_count, 2) + + @patch('checkazurebiceppython.subprocess.run') + @patch('checkazurebiceppython.glob.glob') + @patch('builtins.print') + def test_bicep_format_with_errors(self, mock_print, mock_glob, mock_run): + """Test bicep format with errors.""" + mock_glob.return_value = ['./test.bicep'] + + # Mock version and failed format (no upgrade because --noupgrade is hardcoded) + mock_run.side_effect = [ + MagicMock(stdout="Bicep CLI version 0.4.1008", stderr=b""), + MagicMock(stdout=b"", stderr=b"Error: Invalid file") + ] + + # Should exit with code 25 + with self.assertRaises(SystemExit) as cm: + az_bicep_format() + + self.assertEqual(cm.exception.code, 25) + + @patch('checkazurebiceppython.subprocess.run') + @patch('checkazurebiceppython.glob.glob') + def test_bicep_format_multiple_files(self, mock_glob, mock_run): + """Test bicep format with multiple files.""" + mock_glob.return_value = ['./test1.bicep', './test2.bicep'] + + # Mock version and two successful formats (no upgrade because --noupgrade is hardcoded) + mock_run.side_effect = [ + MagicMock(stdout="Bicep CLI version 0.4.1008", stderr=b""), + MagicMock(stdout=b"Format successful", stderr=b""), + MagicMock(stdout=b"Format successful", stderr=b"") + ] + + # Should complete without error + az_bicep_format() + + # Verify format was called for both files + self.assertEqual(mock_run.call_count, 3) + + +if __name__ == '__main__': + unittest.main()