From 58cf604f67840e828eb6b588ea9b4ba397dae722 Mon Sep 17 00:00:00 2001 From: Ajesh Sen Thapa <35629644+aj3sh@users.noreply.github.com> Date: Fri, 30 Aug 2024 09:45:38 +0545 Subject: [PATCH] test: add tests for github actions with coverage (#66) Co-authored-by: Ajesh Sen Thapa --- Pipfile | 6 +- github_actions/__main__.py | 4 +- github_actions/action/run.py | 7 +- pytest.ini | 2 +- tests/fixtures/actions_env.py | 26 +++ tests/test_github_actions/test_event.py | 72 ++++++++ .../test_run/test_check_commit_messages.py | 98 ++++++++++ .../test_run/test_get_pr_commit_messages.py | 106 +++++++++++ .../test_run/test_get_push_commit_messages.py | 28 +++ .../test_github_actions/test_run/test_run.py | 172 ++++++++++++++++++ .../test_run/test_run_action.py | 62 +++++++ .../test_run/test_run_commitlint.py | 58 ++++++ .../test_utils/test_get_boolean_input.py | 44 +++++ .../test_utils/test_get_input.py | 11 ++ .../test_utils/test_request_github_api.py | 131 +++++++++++++ .../test_utils/test_write_line_to_file.py | 13 ++ .../test_utils/test_write_output.py | 15 ++ .../test_linter/test_utils/test_is_ingored.py | 3 + 18 files changed, 848 insertions(+), 10 deletions(-) create mode 100644 tests/fixtures/actions_env.py create mode 100644 tests/test_github_actions/test_event.py create mode 100644 tests/test_github_actions/test_run/test_check_commit_messages.py create mode 100644 tests/test_github_actions/test_run/test_get_pr_commit_messages.py create mode 100644 tests/test_github_actions/test_run/test_get_push_commit_messages.py create mode 100644 tests/test_github_actions/test_run/test_run.py create mode 100644 tests/test_github_actions/test_run/test_run_action.py create mode 100644 tests/test_github_actions/test_run/test_run_commitlint.py create mode 100644 tests/test_github_actions/test_utils/test_get_boolean_input.py create mode 100644 tests/test_github_actions/test_utils/test_get_input.py create mode 100644 tests/test_github_actions/test_utils/test_request_github_api.py create mode 100644 tests/test_github_actions/test_utils/test_write_line_to_file.py create mode 100644 tests/test_github_actions/test_utils/test_write_output.py diff --git a/Pipfile b/Pipfile index aec7c83..81ec69d 100644 --- a/Pipfile +++ b/Pipfile @@ -12,7 +12,7 @@ pytest-cov = "*" [scripts] test = "pytest" -coverage = "pytest --cov=src/ --no-cov-on-fail" -coverage-html = "pytest --cov=src/ --cov-report=html --no-cov-on-fail" -coverage-xml = "pytest --cov=src/ --cov-report=xml --no-cov-on-fail" +coverage = "pytest --cov=src --cov=github_actions" +coverage-html = "pytest --cov=src --cov=github_actions --cov-report=html" +coverage-xml = "pytest --cov=src --cov=github_actions --cov-report=xml" install-hooks = "pre-commit install --hook-type pre-commit --hook-type commit-msg" diff --git a/github_actions/__main__.py b/github_actions/__main__.py index 1e06182..a568e81 100644 --- a/github_actions/__main__.py +++ b/github_actions/__main__.py @@ -1,5 +1,5 @@ """Main entry point for the GitHub Actions workflow.""" -from action.run import run_action +from action.run import run_action # pragma: no cover -run_action() +run_action() # pragma: no cover diff --git a/github_actions/action/run.py b/github_actions/action/run.py index e01b44f..116bddb 100644 --- a/github_actions/action/run.py +++ b/github_actions/action/run.py @@ -6,7 +6,6 @@ import os import subprocess import sys -from math import ceil from typing import Iterable, List, Optional, Tuple, cast from .event import GitHubEvent @@ -33,6 +32,7 @@ STATUS_FAILURE = "failure" MAX_PR_COMMITS = 250 +PER_PAGE_COMMITS = 50 def get_push_commit_messages(event: GitHubEvent) -> Iterable[str]: @@ -75,8 +75,7 @@ def get_pr_commit_messages(event: GitHubEvent) -> Iterable[str]: ) # pagination - per_page = 50 - total_page = ceil(total_commits / per_page) + total_page = 1 + total_commits // PER_PAGE_COMMITS commits: List[str] = [] for page in range(1, total_page + 1): @@ -84,7 +83,7 @@ def get_pr_commit_messages(event: GitHubEvent) -> Iterable[str]: method="GET", url=f"/repos/{repo}/pulls/{pr_number}/commits", token=token, - params={"per_page": per_page, "page": page}, + params={"per_page": PER_PAGE_COMMITS, "page": page}, ) if status != 200: diff --git a/pytest.ini b/pytest.ini index 99d5e62..4513937 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] -pythonpath = src +pythonpath = . src python_files = test_*.py addopts = -vvv diff --git a/tests/fixtures/actions_env.py b/tests/fixtures/actions_env.py new file mode 100644 index 0000000..a9bccc2 --- /dev/null +++ b/tests/fixtures/actions_env.py @@ -0,0 +1,26 @@ +# type: ignore +# pylint: disable=all +import os + + +def set_github_env_vars(): + # GitHub Action event env + os.environ["GITHUB_EVENT_NAME"] = "push" + os.environ["GITHUB_SHA"] = "commitlint_sha" + os.environ["GITHUB_REF"] = "refs/heads/main" + os.environ["GITHUB_WORKFLOW"] = "commitlint_ci" + os.environ["GITHUB_ACTION"] = "action" + os.environ["GITHUB_ACTOR"] = "actor" + os.environ["GITHUB_REPOSITORY"] = "opensource-nepal/commitlint" + os.environ["GITHUB_JOB"] = "job" + os.environ["GITHUB_RUN_ATTEMPT"] = "9" + os.environ["GITHUB_RUN_NUMBER"] = "8" + os.environ["GITHUB_RUN_ID"] = "7" + os.environ["GITHUB_EVENT_PATH"] = "/tmp/github_event.json" + os.environ["GITHUB_STEP_SUMMARY"] = "/tmp/github_step_summary" + os.environ["GITHUB_OUTPUT"] = "/tmp/github_output" + + # GitHub Action input env + os.environ["INPUT_TOKEN"] = "token" + os.environ["INPUT_VERBOSE"] = "false" + os.environ["INPUT_FAIL_ON_ERROR"] = "true" diff --git a/tests/test_github_actions/test_event.py b/tests/test_github_actions/test_event.py new file mode 100644 index 0000000..c120f6b --- /dev/null +++ b/tests/test_github_actions/test_event.py @@ -0,0 +1,72 @@ +# type: ignore +# pylint: disable=all + +import json +import os +from unittest.mock import mock_open, patch + +import pytest + +from github_actions.action.event import GitHubEvent +from tests.fixtures.actions_env import set_github_env_vars + +MOCK_PAYLOAD = {"key": "value"} + + +@pytest.fixture(scope="module") +def github_event(): + set_github_env_vars() + with patch("builtins.open", mock_open(read_data=json.dumps(MOCK_PAYLOAD))): + return GitHubEvent() + + +def test__github_event__initialization(github_event): + assert github_event.event_name == "push" + assert github_event.sha == "commitlint_sha" + assert github_event.ref == "refs/heads/main" + assert github_event.workflow == "commitlint_ci" + assert github_event.action == "action" + assert github_event.actor == "actor" + assert github_event.repository == "opensource-nepal/commitlint" + assert github_event.job == "job" + assert github_event.run_attempt == "9" + assert github_event.run_number == "8" + assert github_event.run_id == "7" + assert github_event.event_path == "/tmp/github_event.json" + assert github_event.payload == MOCK_PAYLOAD + + +def test__github_event__to_dict(github_event): + event_dict = github_event.to_dict() + assert event_dict["event_name"] == "push" + assert event_dict["sha"] == "commitlint_sha" + assert event_dict["ref"] == "refs/heads/main" + assert event_dict["workflow"] == "commitlint_ci" + assert event_dict["action"] == "action" + assert event_dict["actor"] == "actor" + assert event_dict["repository"] == "opensource-nepal/commitlint" + assert event_dict["job"] == "job" + assert event_dict["run_attempt"] == "9" + assert event_dict["run_number"] == "8" + assert event_dict["run_id"] == "7" + assert event_dict["event_path"] == "/tmp/github_event.json" + assert event_dict["payload"] == MOCK_PAYLOAD + + +def test__github_event__str(github_event): + event_str = str(github_event) + assert "push" in event_str + assert "commitlint_sha" in event_str + assert "refs/heads/main" in event_str + assert "commitlint_ci" in event_str + assert "action" in event_str + assert "actor" in event_str + assert "opensource-nepal/commitlint" in event_str + assert "job" in event_str + assert "9" in event_str + + +def test__github_event__env_error(): + os.environ.pop("GITHUB_EVENT_NAME") + with pytest.raises(EnvironmentError): + GitHubEvent() diff --git a/tests/test_github_actions/test_run/test_check_commit_messages.py b/tests/test_github_actions/test_run/test_check_commit_messages.py new file mode 100644 index 0000000..41bcf32 --- /dev/null +++ b/tests/test_github_actions/test_run/test_check_commit_messages.py @@ -0,0 +1,98 @@ +# type: ignore +# pylint: disable=all +import os +from unittest.mock import patch + +import pytest + +from github_actions.action.run import check_commit_messages +from tests.fixtures.actions_env import set_github_env_vars + +# Constants +STATUS_SUCCESS = "success" +STATUS_FAILURE = "failure" +INPUT_FAIL_ON_ERROR = "fail_on_error" + + +@pytest.fixture(scope="module", autouse=True) +def setup_env(): + set_github_env_vars() + + +@patch("github_actions.action.run.run_commitlint") +@patch("github_actions.action.run.write_line_to_file") +@patch("github_actions.action.run.write_output") +@patch.dict(os.environ, {**os.environ, "GITHUB_STEP_SUMMARY": "summary_path"}) +def test__check_commit_messages__all_valid_messages( + mock_write_output, + mock_write_line_to_file, + mock_run_commitlint, +): + commit_messages = ["feat: valid commit 1", "fix: valid commit 2"] + mock_run_commitlint.return_value = (True, None) + + check_commit_messages(commit_messages) + + mock_run_commitlint.assert_any_call("feat: valid commit 1") + mock_run_commitlint.assert_any_call("fix: valid commit 2") + mock_write_line_to_file.assert_called_once_with( + "summary_path", "commitlint: All commits passed!" + ) + mock_write_output.assert_any_call("status", STATUS_SUCCESS) + mock_write_output.assert_any_call("exit_code", 0) + + +@patch("github_actions.action.run.run_commitlint") +@patch("github_actions.action.run.write_line_to_file") +@patch("github_actions.action.run.write_output") +@patch.dict(os.environ, {**os.environ, "GITHUB_STEP_SUMMARY": "summary_path"}) +def test__check_commit_messages__partial_invalid_messages( + mock_write_output, + mock_write_line_to_file, + mock_run_commitlint, +): + commit_messages = ["feat: valid commit", "invalid commit message"] + mock_run_commitlint.side_effect = [ + (True, None), + (False, "Error: invalid commit format"), + ] + + with pytest.raises(SystemExit): + check_commit_messages(commit_messages) + + mock_run_commitlint.assert_any_call("feat: valid commit") + mock_run_commitlint.assert_any_call("invalid commit message") + mock_write_line_to_file.assert_called_once_with( + "summary_path", "commitlint: 1 commit(s) failed!" + ) + mock_write_output.assert_any_call("status", STATUS_FAILURE) + mock_write_output.assert_any_call("exit_code", 1) + + +@patch("github_actions.action.run.run_commitlint") +@patch("github_actions.action.run.write_line_to_file") +@patch("github_actions.action.run.write_output") +@patch.dict( + os.environ, + { + **os.environ, + "GITHUB_STEP_SUMMARY": "summary_path", + "INPUT_FAIL_ON_ERROR": "False", + }, +) +def test__check_commit_messages__fail_on_error_false( + mock_write_output, + mock_write_line_to_file, + mock_run_commitlint, +): + commit_messages = ["invalid commit message"] + mock_run_commitlint.return_value = (False, "Invalid commit format") + + check_commit_messages(commit_messages) + + mock_run_commitlint.assert_called_once_with("invalid commit message") + mock_write_line_to_file.assert_called_once_with( + "summary_path", "commitlint: 1 commit(s) failed!" + ) + mock_write_output.assert_any_call("status", STATUS_FAILURE) + mock_write_output.assert_any_call("exit_code", 1) diff --git a/tests/test_github_actions/test_run/test_get_pr_commit_messages.py b/tests/test_github_actions/test_run/test_get_pr_commit_messages.py new file mode 100644 index 0000000..8d44462 --- /dev/null +++ b/tests/test_github_actions/test_run/test_get_pr_commit_messages.py @@ -0,0 +1,106 @@ +# type: ignore +# pylint: disable=all +import json +import os +from unittest.mock import mock_open, patch + +import pytest + +from github_actions.action.event import GitHubEvent +from github_actions.action.run import ( + MAX_PR_COMMITS, + PER_PAGE_COMMITS, + get_pr_commit_messages, +) +from tests.fixtures.actions_env import set_github_env_vars + + +@pytest.fixture(scope="module", autouse=True) +def setup_env(): + set_github_env_vars() + + +@patch("github_actions.action.run.request_github_api") +@patch.dict(os.environ, {**os.environ, "GITHUB_EVENT_NAME": "pull_request"}) +def test__get_pr_commit_messages__single_page( + mock_request_github_api, +): + # mock github api request + mock_request_github_api.return_value = ( + 200, + [{"commit": {"message": "feat: commit message"}}], + ) + + payload = {"number": 10, "pull_request": {"commits": 2}} + with patch("builtins.open", mock_open(read_data=json.dumps(payload))): + event = GitHubEvent() + result = get_pr_commit_messages(event) + assert result == ["feat: commit message"] + + mock_request_github_api.assert_called_once_with( + method="GET", + url="/repos/opensource-nepal/commitlint/pulls/10/commits", + token="token", + params={"per_page": PER_PAGE_COMMITS, "page": 1}, + ) + + +@patch("github_actions.action.run.request_github_api") +@patch.dict(os.environ, {**os.environ, "GITHUB_EVENT_NAME": "pull_request"}) +def test__get_pr_commit_messages__multiple_page( + mock_request_github_api, +): + # mock github api request + mock_request_github_api.side_effect = [ + ( + 200, + [{"commit": {"message": "feat: commit message1"}}], + ), + ( + 200, + [{"commit": {"message": "feat: commit message2"}}], + ), + ] + + payload = {"number": 10, "pull_request": {"commits": 60}} + with patch("builtins.open", mock_open(read_data=json.dumps(payload))): + event = GitHubEvent() + result = get_pr_commit_messages(event) + assert result == ["feat: commit message1", "feat: commit message2"] + + assert mock_request_github_api.call_count == 2 + mock_request_github_api.assert_any_call( + method="GET", + url="/repos/opensource-nepal/commitlint/pulls/10/commits", + token="token", + params={"per_page": PER_PAGE_COMMITS, "page": 1}, + ) + + mock_request_github_api.assert_any_call( + method="GET", + url="/repos/opensource-nepal/commitlint/pulls/10/commits", + token="token", + params={"per_page": PER_PAGE_COMMITS, "page": 2}, + ) + + +@patch("github_actions.action.run.request_github_api") +@patch.dict(os.environ, {**os.environ, "GITHUB_EVENT_NAME": "pull_request"}) +def test__get_pr_commit_messages__api_failure( + mock_request_github_api, +): + mock_request_github_api.return_value = (500, None) + payload = {"number": 10, "pull_request": {"commits": 60}} + with patch("builtins.open", mock_open(read_data=json.dumps(payload))): + with pytest.raises(SystemExit): + event = GitHubEvent() + get_pr_commit_messages(event) + + +@patch.dict(os.environ, {**os.environ, "GITHUB_EVENT_NAME": "pull_request"}) +def test__get_pr_commit_messages__exceed_max_commits(): + payload = {"number": 10, "pull_request": {"commits": MAX_PR_COMMITS + 1}} + with patch("builtins.open", mock_open(read_data=json.dumps(payload))): + with pytest.raises(SystemExit): + event = GitHubEvent() + get_pr_commit_messages(event) diff --git a/tests/test_github_actions/test_run/test_get_push_commit_messages.py b/tests/test_github_actions/test_run/test_get_push_commit_messages.py new file mode 100644 index 0000000..a9210b6 --- /dev/null +++ b/tests/test_github_actions/test_run/test_get_push_commit_messages.py @@ -0,0 +1,28 @@ +# type: ignore +# pylint: disable=all + +import json +from unittest.mock import mock_open, patch + +import pytest + +from github_actions.action.event import GitHubEvent +from github_actions.action.run import get_push_commit_messages +from tests.fixtures.actions_env import set_github_env_vars + + +@pytest.fixture(scope="module", autouse=True) +def setup_env(): + set_github_env_vars() + + +def test__get_push_commit_messages__returns_push_commits(): + payload = { + "commits": [ + {"message": "feat: valid message"}, + {"message": "fix(login): fix login message"}, + ] + } + with patch("builtins.open", mock_open(read_data=json.dumps(payload))): + commits = get_push_commit_messages(GitHubEvent()) + assert list(commits) == ["feat: valid message", "fix(login): fix login message"] diff --git a/tests/test_github_actions/test_run/test_run.py b/tests/test_github_actions/test_run/test_run.py new file mode 100644 index 0000000..7218880 --- /dev/null +++ b/tests/test_github_actions/test_run/test_run.py @@ -0,0 +1,172 @@ +# type: ignore +# pylint: disable=all +""" +Integration test of `run.py` to ensure Github Action full functionality. +""" + +import json +import os +import subprocess +from unittest.mock import call, mock_open, patch + +import pytest + +from github_actions.action.run import run_action +from tests.fixtures.actions_env import set_github_env_vars + + +@pytest.fixture(scope="module", autouse=True) +def setup_env(): + set_github_env_vars() + + +@patch("subprocess.check_output", return_value="success") +@patch.dict(os.environ, {**os.environ, "GITHUB_EVENT_NAME": "push"}) +def test__run_action__push_event_full_integration_test_for_valid_commits( + mock_check_output, +): + payload = { + "commits": [ + {"message": "feat: valid message"}, + {"message": "fix(login): fix login message"}, + ] + } + with patch("builtins.open", mock_open(read_data=json.dumps(payload))): + run_action() + + assert mock_check_output.call_count == 2 + expected_calls = [ + call( + ["commitlint", "feat: valid message", "--hide-input"], + text=True, + stderr=subprocess.PIPE, + ), + call( + ["commitlint", "fix(login): fix login message", "--hide-input"], + text=True, + stderr=subprocess.PIPE, + ), + ] + mock_check_output.assert_has_calls(expected_calls, any_order=False) + + +@patch("subprocess.check_output", return_value="success") +@patch.dict(os.environ, {**os.environ, "GITHUB_EVENT_NAME": "push"}) +def test__run_action__push_event_full_integration_test_for_invalid_commits( + mock_check_output, +): + # mock for commitlint command to return error + mock_check_output.side_effect = subprocess.CalledProcessError( + 1, ["cmd"], "stdout", "stderr" + ) + + payload = { + "commits": [ + {"message": "feat: valid message"}, + {"message": "invalid commit message"}, + ] + } + with patch("builtins.open", mock_open(read_data=json.dumps(payload))): + with pytest.raises(SystemExit): + run_action() + + assert mock_check_output.call_count == 2 + expected_calls = [ + call( + ["commitlint", "feat: valid message", "--hide-input"], + text=True, + stderr=subprocess.PIPE, + ), + call( + ["commitlint", "invalid commit message", "--hide-input"], + text=True, + stderr=subprocess.PIPE, + ), + ] + mock_check_output.assert_has_calls(expected_calls, any_order=False) + + +@patch("github_actions.action.run.request_github_api") +@patch("subprocess.check_output", return_value="success") +@patch.dict(os.environ, {**os.environ, "GITHUB_EVENT_NAME": "pull_request"}) +def test__run_action__pr_event_full_integration_test_for_valid_commits( + mock_check_output, + mock_request_github_api, +): + # mock github api request + mock_request_github_api.return_value = ( + 200, + [ + { + "commit": {"message": "feat: valid message"}, + }, + { + "commit": {"message": "fix(login): fix login message"}, + }, + ], + ) + + payload = {"number": 10, "pull_request": {"commits": 2}} + with patch("builtins.open", mock_open(read_data=json.dumps(payload))): + run_action() + + assert mock_check_output.call_count == 2 + expected_calls = [ + call( + ["commitlint", "feat: valid message", "--hide-input"], + text=True, + stderr=subprocess.PIPE, + ), + call( + ["commitlint", "fix(login): fix login message", "--hide-input"], + text=True, + stderr=subprocess.PIPE, + ), + ] + mock_check_output.assert_has_calls(expected_calls, any_order=False) + + +@patch("github_actions.action.run.request_github_api") +@patch("subprocess.check_output", return_value="success") +@patch.dict(os.environ, {**os.environ, "GITHUB_EVENT_NAME": "pull_request"}) +def test__run_action__pr_event_full_integration_test_for_invalid_commits( + mock_check_output, + mock_request_github_api, +): + # mock for commitlint command to return error + mock_check_output.side_effect = subprocess.CalledProcessError( + 1, ["cmd"], "stdout", "stderr" + ) + + # mock github api request + mock_request_github_api.return_value = ( + 200, + [ + { + "commit": {"message": "feat: valid message"}, + }, + { + "commit": {"message": "invalid commit message"}, + }, + ], + ) + + payload = {"number": 10, "pull_request": {"commits": 2}} + with patch("builtins.open", mock_open(read_data=json.dumps(payload))): + with pytest.raises(SystemExit): + run_action() + + assert mock_check_output.call_count == 2 + expected_calls = [ + call( + ["commitlint", "feat: valid message", "--hide-input"], + text=True, + stderr=subprocess.PIPE, + ), + call( + ["commitlint", "invalid commit message", "--hide-input"], + text=True, + stderr=subprocess.PIPE, + ), + ] + mock_check_output.assert_has_calls(expected_calls, any_order=False) diff --git a/tests/test_github_actions/test_run/test_run_action.py b/tests/test_github_actions/test_run/test_run_action.py new file mode 100644 index 0000000..c5e5d92 --- /dev/null +++ b/tests/test_github_actions/test_run/test_run_action.py @@ -0,0 +1,62 @@ +# type: ignore +# pylint: disable=all + +import json +import os +from unittest.mock import mock_open, patch + +import pytest + +from github_actions.action.event import GitHubEvent +from github_actions.action.run import run_action +from tests.fixtures.actions_env import set_github_env_vars + + +@pytest.fixture(scope="module", autouse=True) +def setup_env(): + set_github_env_vars() + + +@pytest.fixture(autouse=True) +def mock_open_file(): + with patch("builtins.open", mock_open(read_data=json.dumps({}))) as mocked_open: + yield mocked_open + + +@patch("github_actions.action.run._handle_push_event") +@patch.dict(os.environ, {**os.environ, "GITHUB_EVENT_NAME": "push"}) +def test__run_action__calls_handle_push_events(mock_handle_push_event): + run_action() + mock_handle_push_event.assert_called_once() + args, _ = mock_handle_push_event.call_args + assert type(args[0]) == GitHubEvent + assert args[0].event_name == "push" + + +@patch("github_actions.action.run._handle_pr_event") +@patch.dict(os.environ, {**os.environ, "GITHUB_EVENT_NAME": "pull_request"}) +def test__run_action__calls_handle_pr_events(mock_handle_pr_event): + run_action() + mock_handle_pr_event.assert_called_once() + args, _ = mock_handle_pr_event.call_args + assert type(args[0]) == GitHubEvent + assert args[0].event_name == "pull_request" + + +@patch("github_actions.action.run._handle_pr_event") +@patch.dict(os.environ, {**os.environ, "GITHUB_EVENT_NAME": "pull_request_target"}) +def test__run_action__calls_handle_pr_events_for_pull_request_target( + mock_handle_pr_event, +): + run_action() + mock_handle_pr_event.assert_called_once() + args, _ = mock_handle_pr_event.call_args + assert type(args[0]) == GitHubEvent + assert args[0].event_name == "pull_request_target" + + +@patch("sys.stdout.write") +@patch.dict(os.environ, {**os.environ, "GITHUB_EVENT_NAME": "workflow_dispatch"}) +def test__run_action__skips_unknown_event(mock_stdout_write): + run_action() + mock_stdout_write.assert_called_once_with("Skipping for event workflow_dispatch\n") diff --git a/tests/test_github_actions/test_run/test_run_commitlint.py b/tests/test_github_actions/test_run/test_run_commitlint.py new file mode 100644 index 0000000..bfb2c12 --- /dev/null +++ b/tests/test_github_actions/test_run/test_run_commitlint.py @@ -0,0 +1,58 @@ +# type: ignore +# pylint: disable=all +import os +import subprocess +from unittest.mock import patch + +from github_actions.action.run import run_commitlint + + +@patch("subprocess.check_output", return_value="feat: valid commit message") +@patch.dict(os.environ, {**os.environ, "INPUT_VERBOSE": "False"}) +def test__run_commitlint__success(mock_check_output): + commit_message = "feat: add new feature" + + result = run_commitlint(commit_message) + + assert result == (True, None) + mock_check_output.assert_called_once_with( + ["commitlint", commit_message, "--hide-input"], + text=True, + stderr=subprocess.PIPE, + ) + + +@patch("subprocess.check_output") +@patch.dict(os.environ, {**os.environ, "INPUT_VERBOSE": "False"}) +def test__run_commitlint__failure(mock_check_output): + mock_check_output.side_effect = ( + subprocess.CalledProcessError(1, "cmd", output="", stderr="Error"), + ) + + commit_message = "invalid commit message" + result = run_commitlint(commit_message) + + assert result == (False, "Error") + mock_check_output.assert_called_once_with( + ["commitlint", commit_message, "--hide-input"], + text=True, + stderr=subprocess.PIPE, + ) + + +@patch( + "subprocess.check_output", + return_value="feat: valid commit message", +) +@patch.dict(os.environ, {**os.environ, "INPUT_VERBOSE": "True"}) +def test__run_commitlint__verbose(mock_check_output): + commit_message = "feat: add new feature" + + result = run_commitlint(commit_message) + + assert result == (True, None) + mock_check_output.assert_called_once_with( + ["commitlint", commit_message, "--hide-input", "--verbose"], + text=True, + stderr=subprocess.PIPE, + ) diff --git a/tests/test_github_actions/test_utils/test_get_boolean_input.py b/tests/test_github_actions/test_utils/test_get_boolean_input.py new file mode 100644 index 0000000..44e2575 --- /dev/null +++ b/tests/test_github_actions/test_utils/test_get_boolean_input.py @@ -0,0 +1,44 @@ +# type: ignore +# pylint: disable=all +import os +from unittest.mock import patch + +import pytest + +from github_actions.action.utils import get_boolean_input + + +@patch.dict(os.environ, {"INPUT_TEST": "True"}) +def test__get_boolean_input__return_True_for_True(): + assert get_boolean_input("test") is True + + +@patch.dict(os.environ, {"INPUT_TEST": "TRUE"}) +def test__get_boolean_input__return_True_for_TRUE(): + assert get_boolean_input("test") is True + + +@patch.dict(os.environ, {"INPUT_TEST": "true"}) +def test__get_boolean_input__return_True_for_true(): + assert get_boolean_input("test") is True + + +@patch.dict(os.environ, {"INPUT_TEST": "False"}) +def test__get_boolean_input__return_False_for_False(): + assert get_boolean_input("test") is False + + +@patch.dict(os.environ, {"INPUT_TEST": "FALSE"}) +def test__get_boolean_input__return_False_for_FALSE(): + assert get_boolean_input("test") is False + + +@patch.dict(os.environ, {"INPUT_TEST": "false"}) +def test__get_boolean_input__return_False_for_false(): + assert get_boolean_input("test") is False + + +@patch.dict(os.environ, {"INPUT_TEST": "random"}) +def test__get_boolean_input__raises_type_error_for_unknown(): + with pytest.raises(TypeError): + get_boolean_input("test") diff --git a/tests/test_github_actions/test_utils/test_get_input.py b/tests/test_github_actions/test_utils/test_get_input.py new file mode 100644 index 0000000..f797e41 --- /dev/null +++ b/tests/test_github_actions/test_utils/test_get_input.py @@ -0,0 +1,11 @@ +# type: ignore +# pylint: disable=all +import os +from unittest.mock import patch + +from github_actions.action.utils import get_input + + +def test_get_input_variable_set(): + with patch.dict(os.environ, {"INPUT_TEST": "value"}): + assert get_input("test") == "value" diff --git a/tests/test_github_actions/test_utils/test_request_github_api.py b/tests/test_github_actions/test_utils/test_request_github_api.py new file mode 100644 index 0000000..da565ad --- /dev/null +++ b/tests/test_github_actions/test_utils/test_request_github_api.py @@ -0,0 +1,131 @@ +# type: ignore +# pylint: disable=all +import json +from unittest.mock import Mock, patch + +import pytest + +from github_actions.action.utils import request_github_api + + +@pytest.fixture +def mock_https_connection(): + with patch("http.client.HTTPSConnection") as mock: + mock_response = Mock() + mock_response.read.return_value = json.dumps({"success": True}).encode("utf-8") + mock_response.status = 200 + mock.return_value.getresponse.return_value = mock_response + + yield mock + + +def test__request_github_api__get_request(mock_https_connection): + status, data = request_github_api( + method="GET", url="/repos/opensource-nepal/commitlint", token="test_token" + ) + + assert status == 200 + assert data == {"success": True} + + mock_https_connection.return_value.request.assert_called_with( + method="GET", + url="/repos/opensource-nepal/commitlint", + body=None, + headers={ + "Authorization": "Bearer test_token", + "Content-Type": "application/json", + "User-Agent": "commitlint", + }, + ) + + +def test__request_github_api__get_request_with_params(mock_https_connection): + status, data = request_github_api( + method="GET", + url="/repos/opensource-nepal/commitlint", + token="test_token", + params={"key1": "val1", "key2": "val2"}, + ) + + assert status == 200 + assert data == {"success": True} + + mock_https_connection.return_value.request.assert_called_with( + method="GET", + url="/repos/opensource-nepal/commitlint?key1=val1&key2=val2", + body=None, + headers={ + "Authorization": "Bearer test_token", + "Content-Type": "application/json", + "User-Agent": "commitlint", + }, + ) + + +def test__request_github_api__post_request(mock_https_connection): + status, data = request_github_api( + method="POST", + url="/repos/opensource-nepal/commitlint", + token="test_token", + ) + + assert status == 200 + assert data == {"success": True} + + mock_https_connection.return_value.request.assert_called_with( + method="POST", + url="/repos/opensource-nepal/commitlint", + body=None, + headers={ + "Authorization": "Bearer test_token", + "Content-Type": "application/json", + "User-Agent": "commitlint", + }, + ) + + +def test_request_github_api__post_request_with_body(mock_https_connection): + status, data = request_github_api( + method="POST", + url="/repos/opensource-nepal/commitlint", + token="test_token", + body={"data": "test_data"}, + ) + + assert status == 200 + assert data == {"success": True} + + mock_https_connection.return_value.request.assert_called_with( + method="POST", + url="/repos/opensource-nepal/commitlint", + body=json.dumps({"data": "test_data"}), + headers={ + "Authorization": "Bearer test_token", + "Content-Type": "application/json", + "User-Agent": "commitlint", + }, + ) + + +def test_request_github_api__post_request_with_body_and_params(mock_https_connection): + status, data = request_github_api( + method="POST", + url="/repos/opensource-nepal/commitlint", + token="test_token", + body={"data": "test_data"}, + params={"key1": "val1", "key2": "val2"}, + ) + + assert status == 200 + assert data == {"success": True} + + mock_https_connection.return_value.request.assert_called_with( + method="POST", + url="/repos/opensource-nepal/commitlint?key1=val1&key2=val2", + body=json.dumps({"data": "test_data"}), + headers={ + "Authorization": "Bearer test_token", + "Content-Type": "application/json", + "User-Agent": "commitlint", + }, + ) diff --git a/tests/test_github_actions/test_utils/test_write_line_to_file.py b/tests/test_github_actions/test_utils/test_write_line_to_file.py new file mode 100644 index 0000000..50bf02d --- /dev/null +++ b/tests/test_github_actions/test_utils/test_write_line_to_file.py @@ -0,0 +1,13 @@ +# type: ignore +# pylint: disable=all +from unittest.mock import mock_open, patch + +from github_actions.action.utils import write_line_to_file + + +@patch("builtins.open", new_callable=mock_open) +def test__write_line_to_file(mock_open): + write_line_to_file("dummy_path.txt", "Test line") + + mock_open.assert_called_once_with(file="dummy_path.txt", mode="a", encoding="utf-8") + mock_open().write.assert_called_once_with("Test line\n") diff --git a/tests/test_github_actions/test_utils/test_write_output.py b/tests/test_github_actions/test_utils/test_write_output.py new file mode 100644 index 0000000..aabfedf --- /dev/null +++ b/tests/test_github_actions/test_utils/test_write_output.py @@ -0,0 +1,15 @@ +# type: ignore +# pylint: disable=all +import os +from unittest.mock import mock_open, patch + +from github_actions.action.utils import write_output + + +@patch("builtins.open", new_callable=mock_open) +def test__write_output(mock_open): + with patch.dict(os.environ, {"GITHUB_OUTPUT": "output.txt"}): + write_output("key", "value") + + mock_open.assert_called_once_with(file="output.txt", mode="a", encoding="utf-8") + mock_open().write.assert_called_once_with("key=value\n") diff --git a/tests/test_linter/test_utils/test_is_ingored.py b/tests/test_linter/test_utils/test_is_ingored.py index a7ff796..dfc69b9 100644 --- a/tests/test_linter/test_utils/test_is_ingored.py +++ b/tests/test_linter/test_utils/test_is_ingored.py @@ -27,6 +27,9 @@ ("Merge my feature", False), ("Add new feature", False), ("feat: this is conventional commit format", False), + ("Bump urllib3 from 1.26.5 to 1.26.17", True), + ("bump @babel/traverse from 7.22.17 to 7.24.0", True), + ("Bump feature1 from feature2", False), ], ) def test__is_ignored(commit_message, expected_result):