Skip to content
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
87 changes: 87 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: Test Repository Listing

on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
fail-fast: false

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov

- name: Install package
run: |
pip install -e .

- name: Run linting checks
run: |
pip install flake8
# Stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# Treat all other issues as warnings
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=79 --statistics

- name: Test package installation and imports
run: |
python -c "from github_repo_deleter.repo_deleter import main, get_token, run_delete; print('All imports successful')"
python -c "import github_repo_deleter; print('Package import successful')"

- name: Test CLI help command
run: |
python -m github_repo_deleter.repo_deleter --help || echo "Help command test completed"
remove_github_repos --help || echo "Console script help test completed"

- name: Run unit tests
run: |
python -m pytest tests/test_repo_listing.py::TestRepoListing -v --tb=short
python -m pytest tests/test_cli.py::TestCLI -v --tb=short
python -m pytest tests/test_cli.py::TestErrorHandling -v --tb=short

- name: Run full test suite
run: |
python -m pytest tests/ -v --tb=short --durations=10

- name: Test GitHub API integration (if token available)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ -n "$GITHUB_TOKEN" ]; then
echo "Running integration tests with GitHub API..."
python -m pytest tests/test_repo_listing.py::TestGitHubIntegration -v --tb=short
else
echo "No GITHUB_TOKEN available, skipping integration tests"
fi

- name: Test package metadata
run: |
python -c "
import pkg_resources
import github_repo_deleter
print('Package version check passed')

# Check if console script is registered
entry_points = pkg_resources.get_entry_map('pygithubrepodeleter')
console_scripts = entry_points.get('console_scripts', {})
assert 'remove_github_repos' in console_scripts, 'Console script not found'
print('Console script registration check passed')
"
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.idea
.vscode

# Created by https://www.toptal.com/developers/gitignore/api/python,pycharm,jetbrains
# Edit at https://www.toptal.com/developers/gitignore?templates=python,pycharm,jetbrains

Expand Down
82 changes: 57 additions & 25 deletions github_repo_deleter/repo_deleter.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from argparse import ArgumentParser
from os import getenv

from PyInquirer import prompt
import inquirer
from github import Github, BadCredentialsException


Expand All @@ -11,33 +11,52 @@ def run_delete(token):
try:
user = g.get_user()
choices = []
questions = [{
'type': 'checkbox',
'message': 'Select repos to delete',
'name': 'repos',
'choices': choices,
}]
repos_unsorted = user.get_repos()
repos = sorted(repos_unsorted, key=lambda x: x.updated_at)
except BadCredentialsException:
print("Invalid token. Make sure the token is correct and you have the repo and delete_repo rights")
print("Invalid token. Make sure the token is correct "
"and you have the repo and delete_repo rights")
return
for repo in repos:
if repo.permissions.admin:
choices.append({"name": repo.full_name})
choices.append(repo.full_name)

to_delete_names = prompt(questions)["repos"]
if not choices:
print("No repositories with admin permissions found.")
return

questions = [
inquirer.Checkbox('repos',
message="Select repos to delete",
choices=choices),
]

answers = inquirer.prompt(questions)
if not answers: # User cancelled
print("Aborted")
return

to_delete_names = answers["repos"]

if len(to_delete_names):
to_delete = [repo for repo in repos if repo.full_name in to_delete_names]
to_delete = [repo for repo in repos
if repo.full_name in to_delete_names]
to_delete_str = "\n\t".join(["- " + i for i in to_delete_names])

confirm = prompt([{
'type': 'list',
'message': f'Please confirm you want to delete the following repositories:\n\t{to_delete_str}',
'name': 'choice',
'choices': ["NO", "YES"],
}])["choice"] == "YES"
confirm_message = (f'Please confirm you want to delete '
f'the following repositories:\n\t{to_delete_str}')
confirm_questions = [
inquirer.List('choice',
message=confirm_message,
choices=["NO", "YES"]),
]

confirm_answers = inquirer.prompt(confirm_questions)
if not confirm_answers: # User cancelled
print("Aborted")
return

confirm = confirm_answers["choice"] == "YES"

if not confirm:
print("Aborted")
Expand All @@ -53,24 +72,37 @@ def run_delete(token):

def get_token():
token_questions = [
{
'type': 'input',
'name': 'token',
'message': 'Enter your github token (https://github.com/settings/tokens)',
}
inquirer.Text('token',
message='Enter your github token '
'(https://github.com/settings/tokens)'),
]
parser = ArgumentParser()
parser.add_argument("--token", help="Github token")
token = None
while token is None or token == "":
token = parser.parse_args().token or getenv("GITHUB_TOKEN") or prompt(token_questions)["token"]
parsed_token = parser.parse_args().token
env_token = getenv("GITHUB_TOKEN")
if parsed_token:
token = parsed_token
elif env_token:
token = env_token
else:
prompt_result = inquirer.prompt(token_questions)
if prompt_result:
token = prompt_result["token"]
else:
# User cancelled prompt
break
return token


def main():
token = get_token()
run_delete(token)
print("Finished")
if token:
run_delete(token)
print("Finished")
else:
print("No token provided. Exiting.")


if __name__ == '__main__':
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
PyGithub
PyInquirer
inquirer
98 changes: 98 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Tests for PyGithubRepoDeleter

This directory contains comprehensive tests for the PyGithubRepoDeleter package.

## Test Files

### `test_repo_listing.py`
- **TestRepoListing**: Unit tests for repository listing functionality
- Token handling from environment variables
- Repository listing and filtering (admin repos only)
- Repository sorting by update date
- Error handling for bad credentials
- GitHub API integration tests (when token available)

- **TestGitHubIntegration**: Integration tests with real GitHub API
- Tests actual GitHub API connection (requires GITHUB_TOKEN)
- Verifies repository listing works with real data

### `test_cli.py`
- **TestCLI**: Command-line interface tests
- Console script availability (`remove_github_repos`)
- Module execution (`python -m github_repo_deleter.repo_deleter`)
- Help command functionality
- Import behavior and side effects

- **TestErrorHandling**: Error handling scenarios
- Network error handling
- Empty token handling
- Graceful failure modes

## Running Tests

### Run all tests:
```bash
python -m pytest tests/ -v
```

### Run specific test classes:
```bash
# Unit tests only
python -m pytest tests/test_repo_listing.py::TestRepoListing -v

# CLI tests only
python -m pytest tests/test_cli.py::TestCLI -v

# Integration tests (requires GITHUB_TOKEN)
python -m pytest tests/test_repo_listing.py::TestGitHubIntegration -v
```

### Manual testing:
```bash
# Set your GitHub token
export GITHUB_TOKEN="your_token_here"

# Run manual test script
python test_manual.py
```

## GitHub Actions CI/CD

The `.github/workflows/test.yml` workflow runs these tests automatically on:
- Push to main/master branches
- Pull requests to main/master branches
- Manual workflow dispatch

The workflow tests across multiple Python versions:
- Python 3.8, 3.9, 3.10, 3.11, 3.12, 3.13

### Test Matrix

For each Python version, the workflow:
1. Sets up the environment
2. Installs dependencies
3. Runs linting checks
4. Tests package installation and imports
5. Tests CLI help commands
6. Runs unit tests
7. Runs integration tests (if GitHub token available)
8. Validates package metadata

## Environment Variables

- `GITHUB_TOKEN`: Required for integration tests and manual testing
- Should have `repo` and `delete_repo` permissions
- Not required for unit tests (they use mocking)

## Test Coverage

The tests cover:
- βœ… Repository listing functionality
- βœ… Authentication and token handling
- βœ… Repository filtering (admin permissions)
- βœ… Repository sorting
- βœ… CLI interface and console scripts
- βœ… Error handling (bad credentials, network errors)
- βœ… Package installation and imports
- βœ… Integration with real GitHub API
- βœ… Cross-platform compatibility (via CI)
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Tests for PyGithubRepoDeleter
Loading
Loading