From 658d941b8ef0e77c7e4156810a14ddfcb935870b Mon Sep 17 00:00:00 2001 From: Tomas Sebestik Date: Fri, 9 Feb 2024 13:28:02 +0100 Subject: [PATCH] ci(project-structure): package and tools config by pyproject.toml - add commitizen support to pyproject.toml - add Danger for GitHub - add pre-commit workflow to CI --- .github/workflows/dangerjs.yml | 24 ++ .github/workflows/pre-commit.yml | 16 ++ .github/workflows/test_jira_sync.yml | 16 -- .gitignore | 3 + .pre-commit-config.yaml | 18 +- CONTRIBUTING.md | 1 + Dockerfile | 2 +- pyproject.toml | 85 +++++- {src => sync_jira_actions}/__init__.py | 0 {src => sync_jira_actions}/sync_issue.py | 0 {src => sync_jira_actions}/sync_pr.py | 0 {src => sync_jira_actions}/sync_to_jira.py | 1 + sync_jira_actions/version.py | 16 -- test_sync_to_jira.py | 305 --------------------- tests/test_sync_issue.py | 14 +- tests/test_sync_pr.py | 11 +- tests/test_sync_to_jira.py | 4 +- 17 files changed, 152 insertions(+), 364 deletions(-) create mode 100644 .github/workflows/dangerjs.yml create mode 100644 .github/workflows/pre-commit.yml delete mode 100644 .github/workflows/test_jira_sync.yml create mode 100644 CONTRIBUTING.md rename {src => sync_jira_actions}/__init__.py (100%) rename {src => sync_jira_actions}/sync_issue.py (100%) rename {src => sync_jira_actions}/sync_pr.py (100%) rename {src => sync_jira_actions}/sync_to_jira.py (98%) delete mode 100644 sync_jira_actions/version.py delete mode 100755 test_sync_to_jira.py diff --git a/.github/workflows/dangerjs.yml b/.github/workflows/dangerjs.yml new file mode 100644 index 0000000..4d2b897 --- /dev/null +++ b/.github/workflows/dangerjs.yml @@ -0,0 +1,24 @@ +name: DangerJS Pull Request linter +on: + pull_request_target: + types: [opened, edited, reopened, synchronize] + +permissions: + pull-requests: write + contents: write + +jobs: + pull-request-style-linter: + runs-on: ubuntu-latest + steps: + - name: Check out PR head + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: DangerJS pull request linter + uses: espressif/shared-github-dangerjs@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + instructions-contributions-file: 'CONTRIBUTING.md' diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..6b2c3c0 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,16 @@ +name: Check pre-commit + +on: + pull_request: + +jobs: + check-pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-python@v5 + - uses: pre-commit/action@v3.0.1 + env: + SKIP: pip-compile diff --git a/.github/workflows/test_jira_sync.yml b/.github/workflows/test_jira_sync.yml deleted file mode 100644 index b5b9eaf..0000000 --- a/.github/workflows/test_jira_sync.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Run JIRA Sync Unit Tests - -on: [push] - -jobs: - test_jira_sync: - name: test_jira_sync - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Test JIRA sync - uses: . - with: - entrypoint: ./test_sync_to_jira.py diff --git a/.gitignore b/.gitignore index f2b42df..c6c52ac 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,6 @@ venv.bak/ .mypy_cache/ .ruff_cache/ __pycache__/* + +# Generated by setuptools-scm when using pip install -e +version.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dddd8b9..35e9e04 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: - id: double-quote-string-fixer # Converts single quotes to double quotes in strings - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.280 + rev: v0.2.1 hooks: - id: ruff # Runs ruff linter (replaces flake8) args: [--fix, --exit-non-zero-on-fix] @@ -29,7 +29,7 @@ repos: - id: reorder-python-imports # Reorders Python imports to a standard format (replaces isort) - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.4.1 + rev: v1.8.0 hooks: - id: mypy # Runs mypy for Python type checking additional_dependencies: ['types-all'] @@ -41,11 +41,21 @@ repos: stages: [commit-msg] - repo: https://github.com/psf/black - rev: '23.7.0' + rev: '24.1.1' hooks: - id: black # Formats Python code using black - repo: https://github.com/pylint-dev/pylint - rev: v2.17.5 + rev: v3.0.3 hooks: - id: pylint # Runs pylint on Python code + + # Local hooks + - repo: local + hooks: + - id: pip-compile + name: compile requirements.txt + entry: bash -c 'pip-compile --output-file=requirements.txt pyproject.toml > /dev/null' + language: system + pass_filenames: false + files: requirements.txt|pyproject.toml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +--- diff --git a/Dockerfile b/Dockerfile index 110f719..ff3e9a8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ RUN pip install --no-cache-dir -r /tmp/requirements.txt RUN npm i -g @shogobg/markdown2confluence@0.1.6 # Copy Python scripts -COPY src/ /src +COPY sync_jira_actions/ /sync_jira_actions # Define the entrypoint to use the virtual environment's Python interpreter ENTRYPOINT ["/opt/venv/bin/python", "/sync_jira_actions/sync_to_jira.py"] diff --git a/pyproject.toml b/pyproject.toml index 63f0642..c7cfe2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,12 +71,79 @@ 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/*"] + [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/*"] + +[tool.commitizen] + annotated_tag = true + bump_message = "change: release v$new_version" + name = "cz_customize" + tag_format = "v$version" + update_changelog_on_bump = true + version_provider = "scm" + + [tool.commitizen.customize] + bump_map = { "change" = "MINOR", "feat" = "MINOR", "fix" = "PATCH", "refactor" = "PATCH", "remove" = "PATCH", "revert" = "PATCH" } + bump_pattern = "^(change|feat|fix|refactor|remove|revert)" + change_type_order = [ + "change", + "ci", + "docs", + "feat", + "fix", + "refactor", + "remove", + "revert", + ] + example = "change: this is a custom change type" + message_template = "{% if scope %}{{change_type}}({{scope}}): {{message}}{% else %}{{change_type}}: {{message}}{% endif %}{% if body %}\n\n{{body}}{% endif %}{% if is_breaking_change %}\n\nBREAKING CHANGE{% endif %}{% if footer %}\n\n{{footer}}{% endif %}" + schema = "(): " + schema_pattern = "^([a-z]+)(\\([\\w\\-\\.]+\\))?:\\s.*" + + [[tool.commitizen.customize.questions]] + choices = [ + { value = "change", name = "change: A change made to the codebase." }, + { value = "ci", name = "ci: Changes to our CI configuration files and scripts." }, + { value = "docs", name = "docs: Documentation only changes." }, + { value = "feat", name = "feat: A new feature." }, + { value = "fix", name = "fix: A bug fix." }, + { value = "refactor", name = "refactor: A code change that neither fixes a bug nor adds a feature." }, + { value = "remove", name = "remove: Removing code or files." }, + { value = "revert", name = "revert: Revert to a commit." }, + ] + message = "Select the TYPE of change you are committing" + name = "change_type" + type = "list" + + [[tool.commitizen.customize.questions]] + message = "What is the SCOPE of this change (press enter to skip)?" + name = "scope" + type = "input" + + [[tool.commitizen.customize.questions]] + message = "Describe the changes made (SUMMARY of commit message):" + name = "message" + type = "input" + + [[tool.commitizen.customize.questions]] + message = "Provide additional contextual information - commit message BODY: (press [enter] to skip)" + name = "body" + type = "input" + + [[tool.commitizen.customize.questions]] + default = false + message = "Is this a BREAKING CHANGE? Correlates with MAJOR in SemVer" + name = "is_breaking_change" + type = "confirm" + + [[tool.commitizen.customize.questions]] + message = "Footer. Information about Breaking Changes and reference issues that this commit closes: (press [enter] to skip)" + name = "footer" + type = "input" diff --git a/src/__init__.py b/sync_jira_actions/__init__.py similarity index 100% rename from src/__init__.py rename to sync_jira_actions/__init__.py diff --git a/src/sync_issue.py b/sync_jira_actions/sync_issue.py similarity index 100% rename from src/sync_issue.py rename to sync_jira_actions/sync_issue.py diff --git a/src/sync_pr.py b/sync_jira_actions/sync_pr.py similarity index 100% rename from src/sync_pr.py rename to sync_jira_actions/sync_pr.py diff --git a/src/sync_to_jira.py b/sync_jira_actions/sync_to_jira.py similarity index 98% rename from src/sync_to_jira.py rename to sync_jira_actions/sync_to_jira.py index 38c4fb6..f01ffd2 100755 --- a/src/sync_to_jira.py +++ b/sync_jira_actions/sync_to_jira.py @@ -62,6 +62,7 @@ def main(): # Check if it's a cron job if os.environ.get('INPUT_CRON_JOB'): + print('Running as a cron job. Syncing remaining PRs...') sync_remain_prs(jira) return diff --git a/sync_jira_actions/version.py b/sync_jira_actions/version.py deleted file mode 100644 index 272b12d..0000000 --- a/sync_jira_actions/version.py +++ /dev/null @@ -1,16 +0,0 @@ -# file generated by setuptools_scm -# don't change, don't track in version control -TYPE_CHECKING = False -if TYPE_CHECKING: - from typing import Tuple, Union - VERSION_TUPLE = Tuple[Union[int, str], ...] -else: - VERSION_TUPLE = object - -version: str -__version__: str -__version_tuple__: VERSION_TUPLE -version_tuple: VERSION_TUPLE - -__version__ = version = '0.1.dev6+g5a387d6' -__version_tuple__ = version_tuple = (0, 1, 'dev6', 'g5a387d6') diff --git a/test_sync_to_jira.py b/test_sync_to_jira.py deleted file mode 100755 index b573f58..0000000 --- a/test_sync_to_jira.py +++ /dev/null @@ -1,305 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2019 Espressif Systems (Shanghai) PTE LTD -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -import json -import os -import tempfile -import unittest.mock -from unittest.mock import create_autospec - -import github -import jira - -import sync_issue -import sync_to_jira - -MOCK_GITHUB_TOKEN = 'iamagithubtoken' - - -def run_sync_issue(event_name, event, jira_issue=None): - """ - Run the 'sync_issue' main() function with supplied event (as Python dict), event name, and mocked JIRA PAI. - - If jira_issue is not None, this JIRA issue object will be - returned as the only result of a call to JIRA.search_issues(). - """ - try: - # dump the event data to a JSON file - event_file = tempfile.NamedTemporaryFile('w+', delete=False) # pylint: disable=consider-using-with - json.dump(event, event_file) - event_file.close() - - os.environ['GITHUB_EVENT_NAME'] = event_name - os.environ['GITHUB_EVENT_PATH'] = event_file.name - - os.environ['GITHUB_TOKEN'] = MOCK_GITHUB_TOKEN - os.environ['JIRA_PROJECT'] = 'TEST' - os.environ['JIRA_URL'] = 'https://test.test:88/' - os.environ['JIRA_USER'] = 'test_user' - os.environ['JIRA_PASS'] = 'test_pass' - os.environ['GITHUB_REPOSITORY'] = 'espressif/fake' - - github_class = create_autospec(github.Github) - - create_autospec(github.Repository.Repository) - - # tell repo.has_in_collaborators() to return False by default - github_class.return_value.get_repo.return_value.has_in_collaborators.return_value = False - - jira_class = create_autospec(jira.JIRA) - - # fake a issue_types response also - issue_type_bug = create_autospec(jira.resources.IssueType) - issue_type_bug.name = 'Bug' - issue_type_bug.id = 5001 - issue_type_task = create_autospec(jira.resources.IssueType) - issue_type_task.name = 'Task' - issue_type_task.id = 5002 - issue_type_new_feature = create_autospec(jira.resources.IssueType) - issue_type_task.name = 'New Feature' - issue_type_task.id = 5003 - - jira_class.return_value.issue_types.return_value = [ - issue_type_bug, - issue_type_task, - issue_type_new_feature, - ] - - if jira_issue is not None: - jira_class.return_value.search_issues.return_value = [jira_issue] - remote_link = create_autospec(jira.resources.RemoteLink) - remote_link.globalId = event['issue']['html_url'] - remote_link.relationship = 'synced from' - remote_link.raw = { - 'object': { - 'title': event['issue']['title'], - 'status': {}, - } - } - jira_class.return_value.remote_links.return_value = [remote_link] - else: - jira_class.return_value.search_issues.return_value = [] - - sync_to_jira._JIRA = jira_class # pylint: disable=protected-access - sync_to_jira.Github = github_class - sync_issue.Github = github_class - sync_to_jira.main() - - return jira_class.return_value # mock JIRA object - - finally: - os.unlink(event_file.name) - - -class TestIssuesEvents(unittest.TestCase): - def test_issue_opened(self): - issue = { - 'html_url': 'https://github.com/espressif/fake/issues/3', - 'repository_url': 'https://github.com/espressif/fake', - 'number': 3, - 'title': 'Test issue', - 'body': 'I am a new test issue\nabc\n测试\n', - 'user': {'login': 'testuser'}, - 'labels': [{'name': 'bug'}], - 'state': 'open', - } - event = {'action': 'opened', 'issue': issue} - - m_jira = run_sync_issue('issues', event) - - # Check that create_issue() was called with fields param resembling the GH issue - fields = m_jira.create_issue.call_args[0][0] - self.assertIn(issue['title'], fields['summary']) - self.assertIn(issue['body'], fields['description']) - self.assertIn(issue['html_url'], fields['description']) - - # Mentions 'issue', no mention of 'pull request' - self.assertIn('issue', fields['description']) - self.assertNotIn('pr', fields['summary'].lower()) - self.assertNotIn('pull request', fields['description'].lower()) - - # Check that add_remote_link() was called - rl_args = m_jira.add_remote_link.call_args[1] - self.assertEqual(m_jira.create_issue.return_value, rl_args['issue']) - self.assertEqual(issue['html_url'], rl_args['globalId']) - - # check that the github repo was updated via expected sequence of API calls - sync_issue.Github.assert_called_with(MOCK_GITHUB_TOKEN) - github_obj = sync_issue.Github.return_value - github_obj.get_repo.assert_called_with('espressif/fake') - repo_obj = github_obj.get_repo.return_value - repo_obj.get_issue.assert_called_with(issue['number']) - issue_obj = repo_obj.get_issue.return_value - update_args = issue_obj.edit.call_args[1] - self.assertIn('title', update_args) - - def test_issue_closed(self): - m_jira = self._test_issue_simple_comment('closed') - - # check resolved was set - new_object = m_jira.remote_links.return_value[0].update.call_args[0][0] - new_status = new_object['status'] - self.assertEqual(True, new_status['resolved']) - - def test_issue_deleted(self): - self._test_issue_simple_comment('deleted') - - def test_issue_reopened(self): - m_jira = self._test_issue_simple_comment('reopened') - - # check resolved was cleared - new_object = m_jira.remote_links.return_value[0].update.call_args[0][0] - new_status = new_object['status'] - self.assertEqual(False, new_status['resolved']) - - def test_issue_edited(self): - issue = { - 'html_url': 'https://github.com/espressif/fake/issues/11', - 'repository_url': 'https://github.com/espressif/fake', - 'number': 11, - 'title': 'Edited issue', - 'body': 'Edited issue content goes here', - 'user': {'login': 'edituser'}, - 'state': 'open', - 'labels': [], - } - - m_jira = self._test_issue_simple_comment('edited', issue) - - # check the update resembles the edited issue - m_issue = m_jira.search_issues.return_value[0] - - update_args = m_issue.update.call_args[1] - self.assertIn('description', update_args['fields']) - self.assertIn('summary', update_args['fields']) - self.assertIn(issue['title'], update_args['fields']['summary']) - - def _test_issue_simple_comment(self, action, gh_issue=None): - """ - Wrapper for the simple case of updating an issue (with 'action'). - GitHub issue fields can be supplied, or generic ones will be used. - """ - if gh_issue is None: - gh_number = hash(action) % 43 - gh_issue = { - 'html_url': f'https://github.com/espressif/fake/issues/{gh_number}', - 'number': gh_number, - 'title': 'Test issue', - 'body': 'I am a test issue\nabc\n\n', - 'user': {'login': 'otheruser'}, - 'labels': [{'name': 'Type: New Feature'}], - 'state': 'closed' if action in ['closed', 'deleted'] else 'open', - } - event = {'action': action, 'issue': gh_issue} - - m_issue = create_autospec(jira.Issue)(None, None) - jira_id = hash(action) % 1001 - m_issue.id = jira_id - - m_jira = run_sync_issue('issues', event, m_issue) - - # expect JIRA API added a comment about the action - comment_jira_id, comment = m_jira.add_comment.call_args[0] - self.assertEqual(jira_id, comment_jira_id) - self.assertIn(gh_issue['user']['login'], comment) - self.assertIn(action, comment) - - return m_jira - - def test_pr_opened(self): - pr = { - 'html_url': 'https://github.com/espressif/fake/pulls/4', - 'base': {'repo': {'html_url': 'https://github.com/espressif/fake'}}, - 'number': 4, - 'title': 'Test issue', - 'body': 'I am a new Pull Request!\nabc\n测试\n', - 'user': {'login': 'testuser'}, - 'labels': [{'name': 'bug'}], - 'state': 'open', - } - event = {'action': 'opened', 'pull_request': pr} - - m_jira = run_sync_issue('pull_request', event) - - # Check that create_issue() mentions a PR not an issue - fields = m_jira.create_issue.call_args[0][0] - self.assertIn('PR', fields['summary']) - self.assertIn('Pull Request', fields['description']) - self.assertIn(pr['html_url'], fields['description']) - - -class TestIssueCommentEvents(unittest.TestCase): - def test_issue_comment_created(self): - self._test_issue_comment('created') - - def test_issue_comment_deleted(self): - self._test_issue_comment('deleted') - - def test_issue_comment_edited(self): - self._test_issue_comment( - 'edited', extra_event_data={'changes': {'body': {'from': 'I am the old comment body'}}} - ) - - def _test_issue_comment( - self, action, gh_issue=None, gh_comment=None, extra_event_data={} - ): # pylint: disable=dangerous-default-value - """ - Wrapper for the simple case of an issue comment event (with 'action'). - GitHub issue and comment fields can be supplied, or generic ones will be used. - """ - if gh_issue is None: - gh_number = hash(action) % 50 - gh_issue = { - 'html_url': f'https://github.com/espressif/fake/issues/{gh_number}', - 'repository_url': 'https://github.com/espressif/fake', - 'number': gh_number, - 'title': 'Test issue', - 'body': 'I am a test issue\nabc\n\n', - 'user': {'login': 'otheruser'}, - 'labels': [], - } - if gh_comment is None: - gh_comment_id = hash(action) % 404 - gh_comment = { - 'html_url': gh_issue['html_url'] + '#' + str(gh_comment_id), - 'repository_url': 'https://github.com/espressif/fake', - 'id': gh_comment_id, - 'user': {'login': 'commentuser'}, - 'body': 'ZOMG a comment!', - } - event = {'action': action, 'issue': gh_issue, 'comment': gh_comment} - event.update(extra_event_data) - - m_issue = create_autospec(jira.Issue)(None, None) - jira_id = hash(action) % 1003 - m_issue.id = jira_id - m_issue.key = f'FAKEFAKE-{hash(action) % 333}' - - m_jira = run_sync_issue('issue_comment', event, m_issue) - - # expect JIRA API added a comment about the action - comment_jira_id, comment = m_jira.add_comment.call_args[0] - self.assertEqual(jira_id, comment_jira_id) - self.assertIn(gh_comment['user']['login'], comment) - self.assertIn(gh_comment['html_url'], comment) - if action != 'deleted': - self.assertIn(gh_comment['body'], comment) # note: doesn't account for markdown2wiki - - return m_jira - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_sync_issue.py b/tests/test_sync_issue.py index e9e79ae..9f85f3f 100644 --- a/tests/test_sync_issue.py +++ b/tests/test_sync_issue.py @@ -30,7 +30,7 @@ def mock_jira_client(): @pytest.fixture def sync_issue_module(github_client_mock): from importlib import reload - from src import sync_issue + from sync_jira_actions import sync_issue reload(sync_issue) # Reload to apply the mocked Github client return sync_issue @@ -52,9 +52,10 @@ def test_handle_issue_opened_creates_jira_issue(sync_issue_module, github_client } } - with patch('src.sync_issue._find_jira_issue', return_value=None) as mock_find_jira_issue, patch( - 'src.sync_issue._create_jira_issue' - ) as mock_create_jira_issue: + with ( + patch('sync_jira_actions.sync_issue._find_jira_issue', return_value=None) as mock_find_jira_issue, + patch('sync_jira_actions.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() @@ -89,8 +90,9 @@ def update_labels(fields=None): mock_jira_issue.update = MagicMock(side_effect=update_labels) - with patch('src.sync_issue._find_jira_issue', return_value=mock_jira_issue), patch( - 'src.sync_issue._get_jira_label', side_effect=lambda x: x['name'] + with ( + patch('sync_jira_actions.sync_issue._find_jira_issue', return_value=mock_jira_issue), + patch('sync_jira_actions.sync_issue._get_jira_label', side_effect=lambda x: x['name']), ): sync_issue_module.handle_issue_labeled(mock_jira_client, mock_event) diff --git a/tests/test_sync_pr.py b/tests/test_sync_pr.py index a80aa8f..6270ddf 100644 --- a/tests/test_sync_pr.py +++ b/tests/test_sync_pr.py @@ -33,10 +33,10 @@ def mock_github(): @pytest.fixture def sync_pr_module(mock_github): - # Import the module from the src directory + # Import the module from the sync_jira_actions directory import sys - sys.path.insert(0, 'src') # Add src directory to the Python path + sys.path.insert(0, 'sync_jira_actions') # Add sync_jira_actions directory to the Python path import sync_pr # Reload the module to ensure the mock is applied @@ -47,9 +47,10 @@ def sync_pr_module(mock_github): @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: + 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 diff --git a/tests/test_sync_to_jira.py b/tests/test_sync_to_jira.py index 16d1495..8ff60d3 100644 --- a/tests/test_sync_to_jira.py +++ b/tests/test_sync_to_jira.py @@ -23,7 +23,7 @@ def sync_to_jira_main(monkeypatch): monkeypatch.setattr('jira.JIRA', MagicMock()) # Import the main function dynamically after applying mocks - from src.sync_to_jira import main as dynamically_imported_main + from sync_jira_actions.sync_to_jira import main as dynamically_imported_main return dynamically_imported_main @@ -56,6 +56,6 @@ def test_handle_issue_opened_event(mock_environment, sync_to_jira_main, monkeypa mock_environment.write_text(json.dumps(event_data)) monkeypatch.setenv('GITHUB_EVENT_NAME', 'issues') - with patch('src.sync_to_jira.handle_issue_opened') as mock_handle_issue_opened: + with patch('sync_jira_actions.sync_to_jira.handle_issue_opened') as mock_handle_issue_opened: sync_to_jira_main() mock_handle_issue_opened.assert_called_once()