Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests and code cov requirements in ci #4

Merged
merged 5 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install flake8 pytest
python -m pip install flake8 pytest pytest-cov
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
# - name: Test with pytest
# run: |
# pytest
- name: Test with pytest
run: |
make test
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"python.testing.pytestArgs": [
"."
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
test:
pytest -v --cov=. --cov-fail-under=80
40 changes: 29 additions & 11 deletions stale_repos.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,7 @@ def main():
load_dotenv(dotenv_path)

# Auth to github.com
ghe = os.getenv("GH_ENTERPRISE_URL", default="").strip()
token = os.getenv("GH_TOKEN")
if ghe and token:
github_connection = github3.github.GitHubEnterprise(ghe, token=token)
elif token:
github_connection = github3.login(token=os.getenv("GH_TOKEN"))
else:
raise ValueError("GH_TOKEN environment variable not set")

if not github_connection:
raise ValueError("Unable to authenticate to GitHub")
github_connection = auth_to_github()

# Set the threshold for inactive days
inactive_days_threshold = os.getenv("INACTIVE_DAYS")
Expand All @@ -56,6 +46,18 @@ def main():

# Iterate over repos in the org, acquire inactive days,
# and print out the repo url and days inactive if it's over the threshold (inactive_days)
print_inactive_repos(github_connection, inactive_days_threshold, organization)


def print_inactive_repos(github_connection, inactive_days_threshold, organization):
"""Print out the repo url and days inactive if it's over the threshold (inactive_days).

Args:
github_connection: The GitHub connection object.
inactive_days_threshold: The threshold (in days) for considering a repo as inactive.
organization: The name of the organization to retrieve repositories from.

"""
inactive_repos = []
for repo in github_connection.repositories_by(organization):
last_push_str = repo.pushed_at # type: ignore
Expand All @@ -69,5 +71,21 @@ def main():
print(f"Found {len(inactive_repos)} stale repos in {organization}")


def auth_to_github():
"""Connect to github.com or GitHub Enterprise, depending on env variables."""
ghe = os.getenv("GH_ENTERPRISE_URL", default="").strip()
token = os.getenv("GH_TOKEN")
if ghe and token:
github_connection = github3.github.GitHubEnterprise(ghe, token=token)
elif token:
github_connection = github3.login(token=os.getenv("GH_TOKEN"))
else:
raise ValueError("GH_TOKEN environment variable not set")

if not github_connection:
raise ValueError("Unable to authenticate to GitHub")
return github_connection # type: ignore


if __name__ == "__main__":
main()
203 changes: 203 additions & 0 deletions test_stale_repos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
"""
Unit tests for the auth_to_github() function.

This module contains a set of unit tests to verify the behavior of the auth_to_github() function.
The function is responsible for connecting to github.com or GitHub Enterprise,
depending on environment variables.

The tests cover different scenarios, such as successful authentication with both enterprise URL
and token, authentication with only a token, missing environment variables, and authentication
failures.

To run the tests, execute this module as the main script.

Example:
$ pytest test_auth_to_github.py

"""

import io
import os
import unittest
from datetime import datetime, timedelta
from unittest.mock import MagicMock, patch

import github3.github
from stale_repos import auth_to_github, print_inactive_repos


class AuthToGithubTestCase(unittest.TestCase):
"""
Unit test case for the auth_to_github() function.

This test case class contains a set of individual test methods to verify the behavior
of the auth_to_github() function. The function is responsible for connecting to
github.com or GitHub Enterprise based on environment variables.

The test methods cover different scenarios, such as successful authentication with both
enterprise URL and token, authentication with only a token, missing environment variables,
and authentication failures.

Test methods:
- test_auth_to_github_with_enterprise_url_and_token: Tests authentication with both
enterprise URL and token.
- test_auth_to_github_with_token: Tests authentication with only a token.
- test_auth_to_github_without_environment_variables: Tests authentication with
missing environment variables.
- test_auth_to_github_without_enterprise_url: Tests authentication without an
enterprise URL.
- test_auth_to_github_authentication_failure: Tests authentication failure.

"""

@patch.dict(
os.environ, {"GH_ENTERPRISE_URL": "https://example.com", "GH_TOKEN": "abc123"}
)
def test_auth_to_github_with_enterprise_url_and_token(self):
"""
Test authentication with both enterprise URL and token.

This test verifies that when both the GH_ENTERPRISE_URL and GH_TOKEN environment
variables are set, the auth_to_github() function returns a connection object of
type github3.github.GitHubEnterprise.

"""
connection = auth_to_github()
self.assertIsInstance(connection, github3.github.GitHubEnterprise)

@patch.dict(os.environ, {"GH_TOKEN": "abc123"})
def test_auth_to_github_with_token(self):
"""
Test authentication with only a token.

This test verifies that when only the GH_TOKEN environment variable is set,
the auth_to_github() function returns a connection object of type github3.github.GitHub.

"""
connection = auth_to_github()
self.assertIsInstance(connection, github3.github.GitHub)

@patch.dict(os.environ, {"GH_ENTERPRISE_URL": "", "GH_TOKEN": ""})
def test_auth_to_github_without_environment_variables(self):
"""
Test authentication with missing environment variables.

This test verifies that when both the GH_ENTERPRISE_URL and GH_TOKEN environment
variables are empty, the auth_to_github() function raises a ValueError.

"""
with self.assertRaises(ValueError):
auth_to_github()

@patch("github3.login")
def test_auth_to_github_without_enterprise_url(self, mock_login):
"""
Test authentication without an enterprise URL.

This test verifies that when the GH_ENTERPRISE_URL environment variable is empty,
and the GH_TOKEN environment variable is set, the auth_to_github() function returns
a connection object of type github3.github.GitHub.

"""
mock_login.return_value = None
with patch.dict(os.environ, {"GH_ENTERPRISE_URL": "", "GH_TOKEN": "abc123"}):
with self.assertRaises(ValueError):
auth_to_github()

@patch("github3.login")
def test_auth_to_github_authentication_failure(self, mock_login):
"""
Test authentication failure.

This test verifies that when the GH_ENTERPRISE_URL environment variable is empty,
the GH_TOKEN environment variable is set, and the authentication process fails,
the auth_to_github() function raises a ValueError.

"""
mock_login.return_value = None
with patch.dict(os.environ, {"GH_ENTERPRISE_URL": "", "GH_TOKEN": "abc123"}):
with self.assertRaises(ValueError):
auth_to_github()


class PrintInactiveReposTestCase(unittest.TestCase):
"""
Unit test case for the print_inactive_repos() function.

This test case class verifies the behavior and correctness of the print_inactive_repos()
function, which prints the URL and days inactive for repositories that exceed the
specified threshold.

...

Test methods:
- test_print_inactive_repos_with_inactive_repos: Tests printing of inactive repos
that exceed the threshold.
- test_print_inactive_repos_with_no_inactive_repos: Tests printing of no inactive repos.

"""

def test_print_inactive_repos_with_inactive_repos(self):
"""Test printing inactive repos that exceed the threshold.

This test verifies that the print_inactive_repos() function correctly prints the URL and
days inactive for repositories that have been inactive for more than the specified
threshold.

"""
# Create a mock GitHub connection object
github_connection = MagicMock()

# Create a mock repository object with a last push time of 30 days ago
thirty_days_ago = datetime.now() - timedelta(days=30)
mock_repo = MagicMock()
mock_repo.pushed_at = thirty_days_ago.isoformat()
mock_repo.html_url = "https://github.com/example/repo"
github_connection.repositories_by.return_value = [mock_repo]

# Call the function with a threshold of 20 days
inactive_days_threshold = 20
organization = "example"
with patch("sys.stdout", new_callable=io.StringIO) as mock_stdout:
print_inactive_repos(
github_connection, inactive_days_threshold, organization
)
output = mock_stdout.getvalue()

# Check that the output contains the expected repo URL and days inactive
expected_output = f"{mock_repo.html_url}: 30 days inactive\nFound 1 stale repos in {organization}\n"
self.assertEqual(expected_output, output)

def test_print_inactive_repos_with_no_inactive_repos(self):
"""Test printing no inactive repos.

This test verifies that the print_inactive_repos() function
does not print anything when there are no repositories that
exceed the specified threshold.

"""
github_connection = MagicMock()

# Create a mock repository object with a last push time of 30 days ago
thirty_days_ago = datetime.now() - timedelta(days=30)
mock_repo = MagicMock()
mock_repo.pushed_at = thirty_days_ago.isoformat()
mock_repo.html_url = "https://github.com/example/repo"
github_connection.repositories_by.return_value = [mock_repo]

# Call the function with a threshold of 40 days
inactive_days_threshold = 40
organization = "example"
with patch("sys.stdout", new_callable=io.StringIO) as mock_stdout:
print_inactive_repos(
github_connection, inactive_days_threshold, organization
)
output = mock_stdout.getvalue()

# Check that the output contains the expected repo URL and days inactive
expected_output = f"Found 0 stale repos in {organization}\n"
self.assertEqual(expected_output, output)


if __name__ == "__main__":
unittest.main()