diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml new file mode 100644 index 0000000..07dc7c8 --- /dev/null +++ b/.github/workflows/python-test.yml @@ -0,0 +1,27 @@ +name: Python Tests + +on: + pull_request: + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python 3.9 + uses: actions/setup-python@v5 + with: + python-version: 3.9 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest pytest-cov + pip install -r requirements.txt + + - name: Run pytest with coverage + run: | + python -m pytest diff --git a/pyproject.toml b/pyproject.toml index 4ad2e04..4546e18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,3 +41,14 @@ ] [tool.pylint.'FORMAT'] max-line-length = 120 # Specifies the maximum line length for pylint checks + + +[tool.pytest.ini_options] + addopts = "-s --log-cli-level DEBUG --cov=. --cov-report=term" + python_classes = ["Test*"] + python_files = ["test_*.py"] + python_functions = ["test_*"] + testpaths = ["tests"] + +[tool.coverage.run] + omit = ["__*__.py", "tests/*"] diff --git a/tests/test_sync_issue.py b/tests/test_sync_issue.py new file mode 100644 index 0000000..faa22ce --- /dev/null +++ b/tests/test_sync_issue.py @@ -0,0 +1,97 @@ +from unittest.mock import MagicMock +from unittest.mock import patch + +import pytest + + +@pytest.fixture(autouse=True) +def setup_env(monkeypatch): + monkeypatch.setenv('GITHUB_TOKEN', 'fake-token') + monkeypatch.setenv('GITHUB_REPOSITORY', 'fake/repo') + + +@pytest.fixture(scope='module') +def github_client_mock(): + with patch('github.Github') as MockGithub: + mock_github = MockGithub.return_value + mock_repo = MagicMock() + mock_github.get_repo.return_value = mock_repo + yield mock_github, mock_repo + + +# Correct fixture to mock JIRA client +@pytest.fixture(scope='module') +def mock_jira_client(): + with patch('jira.JIRA') as MockJIRA: + mock_jira = MockJIRA.return_value + yield mock_jira + + +@pytest.fixture +def sync_issue_module(github_client_mock): + from importlib import reload + import sync_issue + + reload(sync_issue) # Reload to apply the mocked Github client + return sync_issue + + +# Example test function +def test_handle_issue_opened_creates_jira_issue(sync_issue_module, github_client_mock): + _, mock_repo = github_client_mock + mock_jira_client = MagicMock() + mock_event = { + 'issue': { + 'number': 123, + 'title': 'New Issue', + 'body': 'Issue description here.', + 'user': {'login': 'user123'}, + 'labels': [], + 'html_url': 'https://github.com/user/repo/issues/123', + 'state': 'open', + } + } + + with patch('sync_issue._find_jira_issue', return_value=None) as mock_find_jira_issue, patch( + 'sync_issue._create_jira_issue' + ) as mock_create_jira_issue: + sync_issue_module.handle_issue_opened(mock_jira_client, mock_event) + + mock_find_jira_issue.assert_called_once() + mock_create_jira_issue.assert_called_once() + + +def test_handle_issue_labeled_adds_label(sync_issue_module, github_client_mock, mock_jira_client): + # Setup + mock_github, mock_repo = github_client_mock + + mock_event = { + 'issue': { + 'number': 123, + 'title': 'Issue for Labeling', + 'body': 'Label me!', + 'user': {'login': 'user456'}, + 'labels': [{'name': 'bug'}], + 'html_url': 'https://github.com/user/repo/issues/123', + 'state': 'open', + }, + 'label': {'name': 'bug'}, + } + + # Adjusting the mock to behave more like a list that can be appended to + mock_jira_issue = MagicMock() + labels_list = ['existing-label'] # Starting with an existing label for demonstration + mock_jira_issue.fields.labels = labels_list + + def update_labels(fields=None): + if fields and 'labels' in fields: + labels_list.extend(fields['labels']) # Simulate adding new labels + + mock_jira_issue.update = MagicMock(side_effect=update_labels) + + with patch('sync_issue._find_jira_issue', return_value=mock_jira_issue), patch( + 'sync_issue._get_jira_label', side_effect=lambda x: x['name'] + ): + sync_issue_module.handle_issue_labeled(mock_jira_client, mock_event) + + assert 'bug' in labels_list, "Label 'bug' was not added to the JIRA issue labels" diff --git a/tests/test_sync_pr.py b/tests/test_sync_pr.py new file mode 100644 index 0000000..b616474 --- /dev/null +++ b/tests/test_sync_pr.py @@ -0,0 +1,68 @@ +import importlib +from unittest.mock import MagicMock +from unittest.mock import patch + +import pytest + + +# Patch the GitHub client before importing modules that use it +@pytest.fixture(autouse=True) +def mock_env_vars(monkeypatch): + monkeypatch.setenv('GITHUB_TOKEN', 'fake-token') + monkeypatch.setenv('GITHUB_REPOSITORY', 'fake/repo') + + +@pytest.fixture +def mock_github(): + with patch('github.Github') as MockGithub: + mock_repo = MagicMock() + mock_pr = MagicMock() + mock_pr.number = 1 + mock_pr.title = 'Test PR' + mock_pr.html_url = 'http://example.com/testpr' + mock_pr.user.login = 'testuser' + mock_pr.labels = [] + mock_pr.state = 'open' + mock_pr.body = 'Test body' + mock_repo.get_pulls.return_value = [mock_pr] + mock_repo.has_in_collaborators.return_value = False + + MockGithub.return_value.get_repo.return_value = mock_repo + yield mock_repo + + +@pytest.fixture +def sync_pr_module(mock_github): + # Import the module + import sync_pr + + # Reload the module to ensure the mock is applied + importlib.reload(sync_pr) + # Return the reloaded module + return sync_pr + + +@pytest.fixture +def mock_sync_issue(): + with patch('sync_pr._create_jira_issue') as mock_create_jira_issue, patch( + 'sync_pr._find_jira_issue', return_value=None + ) as mock_find_jira_issue: + yield mock_create_jira_issue, mock_find_jira_issue + + +def test_sync_remain_prs(sync_pr_module, mock_sync_issue, mock_github): + mock_jira = MagicMock() + mock_create_jira_issue, mock_find_jira_issue = mock_sync_issue + + # Use the function from the reloaded module + sync_pr_module.sync_remain_prs(mock_jira) + + # Verify _find_jira_issue was called once with the mock_jira client and the PR data + assert mock_find_jira_issue.call_count == 1 + + # Verify _create_jira_issue was called once since no corresponding JIRA issue was found + assert mock_create_jira_issue.call_count == 1 + + # Example of verifying call arguments (simplified) + call_args = mock_create_jira_issue.call_args + assert 'Test PR' in call_args[0][1]['title'], 'PR title does not match expected value' diff --git a/tests/test_sync_to_jira.py b/tests/test_sync_to_jira.py new file mode 100644 index 0000000..4fb7850 --- /dev/null +++ b/tests/test_sync_to_jira.py @@ -0,0 +1,61 @@ +import json +from unittest.mock import MagicMock +from unittest.mock import patch + +import pytest + + +@pytest.fixture +def mock_environment(tmp_path, monkeypatch): + event_file = tmp_path / 'event.json' + monkeypatch.setenv('GITHUB_REPOSITORY', 'espressif/esp-idf') + monkeypatch.setenv('GITHUB_TOKEN', 'fake-token') + monkeypatch.setenv('GITHUB_EVENT_PATH', str(event_file)) + monkeypatch.setenv('JIRA_URL', 'https://jira.example.com') + monkeypatch.setenv('JIRA_USER', 'user') + monkeypatch.setenv('JIRA_PASS', 'pass') + return event_file + + +@pytest.fixture +def sync_to_jira_main(monkeypatch): + monkeypatch.setattr('github.Github', MagicMock()) + monkeypatch.setattr('jira.JIRA', MagicMock()) + + # Import the main function dynamically after applying mocks + from sync_to_jira import main as dynamically_imported_main + + return dynamically_imported_main + + +def test_not_running_in_github_action_context(capsys, sync_to_jira_main, monkeypatch): + monkeypatch.delenv('GITHUB_REPOSITORY', raising=False) + sync_to_jira_main() + captured = capsys.readouterr() + assert 'Not running in GitHub action context, nothing to do' in captured.out + + +def test_not_espressif_repo(capsys, sync_to_jira_main, monkeypatch): + monkeypatch.setenv('GITHUB_REPOSITORY', 'other/repo') + sync_to_jira_main() + captured = capsys.readouterr() + assert 'Not an Espressif repo, nothing to sync to JIRA' in captured.out + + +def test_handle_issue_opened_event(mock_environment, sync_to_jira_main, monkeypatch): + event_data = { + 'action': 'opened', + 'issue': { + 'number': 1, + 'title': 'Test issue', + 'body': 'This is a test issue', + 'user': {'login': 'testuser'}, + 'html_url': 'https://github.com/espressif/esp-idf/issues/1', + }, + } + mock_environment.write_text(json.dumps(event_data)) + monkeypatch.setenv('GITHUB_EVENT_NAME', 'issues') + + with patch('sync_to_jira.handle_issue_opened') as mock_handle_issue_opened: + sync_to_jira_main() + mock_handle_issue_opened.assert_called_once()