Skip to content

Commit

Permalink
test: add tests for github actions with coverage (#66)
Browse files Browse the repository at this point in the history
Co-authored-by: Ajesh Sen Thapa <[email protected]>
  • Loading branch information
aj3sh and Ajesh Sen Thapa authored Aug 30, 2024
1 parent dffb36b commit 58cf604
Show file tree
Hide file tree
Showing 18 changed files with 848 additions and 10 deletions.
6 changes: 3 additions & 3 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"
4 changes: 2 additions & 2 deletions github_actions/__main__.py
Original file line number Diff line number Diff line change
@@ -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
7 changes: 3 additions & 4 deletions github_actions/action/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -33,6 +32,7 @@
STATUS_FAILURE = "failure"

MAX_PR_COMMITS = 250
PER_PAGE_COMMITS = 50


def get_push_commit_messages(event: GitHubEvent) -> Iterable[str]:
Expand Down Expand Up @@ -75,16 +75,15 @@ 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):
status, data = request_github_api(
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:
Expand Down
2 changes: 1 addition & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[pytest]
pythonpath = src
pythonpath = . src
python_files = test_*.py
addopts = -vvv
26 changes: 26 additions & 0 deletions tests/fixtures/actions_env.py
Original file line number Diff line number Diff line change
@@ -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"
72 changes: 72 additions & 0 deletions tests/test_github_actions/test_event.py
Original file line number Diff line number Diff line change
@@ -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()
98 changes: 98 additions & 0 deletions tests/test_github_actions/test_run/test_check_commit_messages.py
Original file line number Diff line number Diff line change
@@ -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)
106 changes: 106 additions & 0 deletions tests/test_github_actions/test_run/test_get_pr_commit_messages.py
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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"]
Loading

0 comments on commit 58cf604

Please sign in to comment.