Procházet zdrojové kódy

Refactor issue filtering (#5129)

Rohit Malhotra před 1 rokem
rodič
revize
7f5022c8fe

+ 29 - 6
openhands/resolver/issue_definitions.py

@@ -18,7 +18,9 @@ class IssueHandlerInterface(ABC):
     issue_type: ClassVar[str]
 
     @abstractmethod
-    def get_converted_issues(self, comment_id: int | None = None) -> list[GithubIssue]:
+    def get_converted_issues(
+        self, issue_numbers: list[int] | None = None, comment_id: int | None = None
+    ) -> list[GithubIssue]:
         """Download issues from GitHub."""
         pass
 
@@ -138,13 +140,29 @@ class IssueHandler(IssueHandlerInterface):
 
         return all_comments if all_comments else None
 
-    def get_converted_issues(self, comment_id: int | None = None) -> list[GithubIssue]:
+    def get_converted_issues(
+        self, issue_numbers: list[int] | None = None, comment_id: int | None = None
+    ) -> list[GithubIssue]:
         """Download issues from Github.
 
         Returns:
             List of Github issues.
         """
+
+        if not issue_numbers:
+            raise ValueError('Unspecified issue number')
+
         all_issues = self._download_issues_from_github()
+        logger.info(f'Limiting resolving to issues {issue_numbers}.')
+        all_issues = [
+            issue
+            for issue in all_issues
+            if issue['number'] in issue_numbers and 'pull_request' not in issue
+        ]
+
+        if len(issue_numbers) == 1 and not all_issues:
+            raise ValueError(f'Issue {issue_numbers[0]} not found')
+
         converted_issues = []
         for issue in all_issues:
             if any([issue.get(key) is None for key in ['number', 'title', 'body']]):
@@ -153,9 +171,6 @@ class IssueHandler(IssueHandlerInterface):
                 )
                 continue
 
-            if 'pull_request' in issue:
-                continue
-
             # Get issue thread comments
             thread_comments = self._get_issue_comments(
                 issue['number'], comment_id=comment_id
@@ -486,8 +501,16 @@ class PRHandler(IssueHandler):
 
         return closing_issues
 
-    def get_converted_issues(self, comment_id: int | None = None) -> list[GithubIssue]:
+    def get_converted_issues(
+        self, issue_numbers: list[int] | None = None, comment_id: int | None = None
+    ) -> list[GithubIssue]:
+        if not issue_numbers:
+            raise ValueError('Unspecified issue numbers')
+
         all_issues = self._download_issues_from_github()
+        logger.info(f'Limiting resolving to issues {issue_numbers}.')
+        all_issues = [issue for issue in all_issues if issue['number'] in issue_numbers]
+
         converted_issues = []
         for issue in all_issues:
             # For PRs, body can be None

+ 3 - 4
openhands/resolver/resolve_all_issues.py

@@ -83,11 +83,10 @@ async def resolve_issues(
     issue_handler = issue_handler_factory(issue_type, owner, repo, token)
 
     # Load dataset
-    issues: list[GithubIssue] = issue_handler.get_converted_issues()
+    issues: list[GithubIssue] = issue_handler.get_converted_issues(
+        issue_numbers=issue_numbers
+    )
 
-    if issue_numbers is not None:
-        issues = [issue for issue in issues if issue.number in issue_numbers]
-        logger.info(f'Limiting resolving to issues {issue_numbers}.')
     if limit_issues is not None:
         issues = issues[:limit_issues]
         logger.info(f'Limiting resolving to first {limit_issues} issues.')

+ 2 - 5
openhands/resolver/resolve_issue.py

@@ -339,13 +339,10 @@ async def resolve_issue(
 
     # Load dataset
     issues: list[GithubIssue] = issue_handler.get_converted_issues(
-        comment_id=comment_id
+        issue_numbers=[issue_number], comment_id=comment_id
     )
 
-    # Find the specific issue
-    issue = next((i for i in issues if i.number == issue_number), None)
-    if not issue:
-        raise ValueError(f'Issue {issue_number} not found')
+    issue = issues[0]
 
     if comment_id is not None:
         if (

+ 216 - 209
tests/unit/resolver/test_issue_handler.py

@@ -1,17 +1,18 @@
-from unittest.mock import patch, MagicMock
-from openhands.resolver.issue_definitions import IssueHandler, PRHandler
-from openhands.resolver.github_issue import GithubIssue, ReviewThread
-from openhands.events.action.message import MessageAction
+from unittest.mock import MagicMock, patch
+
 from openhands.core.config import LLMConfig
+from openhands.events.action.message import MessageAction
+from openhands.resolver.github_issue import GithubIssue, ReviewThread
+from openhands.resolver.issue_definitions import IssueHandler, PRHandler
 
 
 def test_get_converted_issues_initializes_review_comments():
     # Mock the necessary dependencies
-    with patch("requests.get") as mock_get:
+    with patch('requests.get') as mock_get:
         # Mock the response for issues
         mock_issues_response = MagicMock()
         mock_issues_response.json.return_value = [
-            {"number": 1, "title": "Test Issue", "body": "Test Body"}
+            {'number': 1, 'title': 'Test Issue', 'body': 'Test Body'}
         ]
         # Mock the response for comments
         mock_comments_response = MagicMock()
@@ -26,10 +27,10 @@ def test_get_converted_issues_initializes_review_comments():
         ]  # Need two comment responses because we make two API calls
 
         # Create an instance of IssueHandler
-        handler = IssueHandler("test-owner", "test-repo", "test-token")
+        handler = IssueHandler('test-owner', 'test-repo', 'test-token')
 
         # Get converted issues
-        issues = handler.get_converted_issues()
+        issues = handler.get_converted_issues(issue_numbers=[1])
 
         # Verify that we got exactly one issue
         assert len(issues) == 1
@@ -39,35 +40,35 @@ def test_get_converted_issues_initializes_review_comments():
 
         # Verify other fields are set correctly
         assert issues[0].number == 1
-        assert issues[0].title == "Test Issue"
-        assert issues[0].body == "Test Body"
-        assert issues[0].owner == "test-owner"
-        assert issues[0].repo == "test-repo"
+        assert issues[0].title == 'Test Issue'
+        assert issues[0].body == 'Test Body'
+        assert issues[0].owner == 'test-owner'
+        assert issues[0].repo == 'test-repo'
 
 
 def test_pr_handler_guess_success_with_thread_comments():
     # Create a PR handler instance
-    handler = PRHandler("test-owner", "test-repo", "test-token")
+    handler = PRHandler('test-owner', 'test-repo', 'test-token')
 
     # Create a mock issue with thread comments but no review comments
     issue = GithubIssue(
-        owner="test-owner",
-        repo="test-repo",
+        owner='test-owner',
+        repo='test-repo',
         number=1,
-        title="Test PR",
-        body="Test Body",
-        thread_comments=["First comment", "Second comment"],
-        closing_issues=["Issue description"],
+        title='Test PR',
+        body='Test Body',
+        thread_comments=['First comment', 'Second comment'],
+        closing_issues=['Issue description'],
         review_comments=None,
         thread_ids=None,
-        head_branch="test-branch",
+        head_branch='test-branch',
     )
 
     # Create mock history
-    history = [MessageAction(content="Fixed the issue by implementing X and Y")]
+    history = [MessageAction(content='Fixed the issue by implementing X and Y')]
 
     # Create mock LLM config
-    llm_config = LLMConfig(model="test-model", api_key="test-key")
+    llm_config = LLMConfig(model='test-model', api_key='test-key')
 
     # Mock the LLM response
     mock_response = MagicMock()
@@ -84,7 +85,7 @@ The changes successfully address the feedback."""
     ]
 
     # Test the guess_success method
-    with patch("litellm.completion", return_value=mock_response):
+    with patch('litellm.completion', return_value=mock_response):
         success, success_list, explanation = handler.guess_success(
             issue, history, llm_config
         )
@@ -92,39 +93,39 @@ The changes successfully address the feedback."""
         # Verify the results
         assert success is True
         assert success_list == [True]
-        assert "successfully address" in explanation
+        assert 'successfully address' in explanation
 
 
 def test_pr_handler_get_converted_issues_with_comments():
     # Mock the necessary dependencies
-    with patch("requests.get") as mock_get:
+    with patch('requests.get') as mock_get:
         # Mock the response for PRs
         mock_prs_response = MagicMock()
         mock_prs_response.json.return_value = [
             {
-                "number": 1,
-                "title": "Test PR",
-                "body": "Test Body fixes #1",
-                "head": {"ref": "test-branch"},
+                'number': 1,
+                'title': 'Test PR',
+                'body': 'Test Body fixes #1',
+                'head': {'ref': 'test-branch'},
             }
         ]
 
         # Mock the response for PR comments
         mock_comments_response = MagicMock()
         mock_comments_response.json.return_value = [
-            {"body": "First comment"},
-            {"body": "Second comment"},
+            {'body': 'First comment'},
+            {'body': 'Second comment'},
         ]
 
         # Mock the response for PR metadata (GraphQL)
         mock_graphql_response = MagicMock()
         mock_graphql_response.json.return_value = {
-            "data": {
-                "repository": {
-                    "pullRequest": {
-                        "closingIssuesReferences": {"edges": []},
-                        "reviews": {"nodes": []},
-                        "reviewThreads": {"edges": []},
+            'data': {
+                'repository': {
+                    'pullRequest': {
+                        'closingIssuesReferences': {'edges': []},
+                        'reviews': {'nodes': []},
+                        'reviewThreads': {'edges': []},
                     }
                 }
             }
@@ -138,7 +139,7 @@ def test_pr_handler_get_converted_issues_with_comments():
         # Mock the response for fetching the external issue referenced in PR body
         mock_external_issue_response = MagicMock()
         mock_external_issue_response.json.return_value = {
-            "body": "This is additional context from an externally referenced issue."
+            'body': 'This is additional context from an externally referenced issue.'
         }
 
         mock_get.side_effect = [
@@ -150,56 +151,56 @@ def test_pr_handler_get_converted_issues_with_comments():
         ]
 
         # Mock the post request for GraphQL
-        with patch("requests.post") as mock_post:
+        with patch('requests.post') as mock_post:
             mock_post.return_value = mock_graphql_response
 
             # Create an instance of PRHandler
-            handler = PRHandler("test-owner", "test-repo", "test-token")
+            handler = PRHandler('test-owner', 'test-repo', 'test-token')
 
             # Get converted issues
-            prs = handler.get_converted_issues()
+            prs = handler.get_converted_issues(issue_numbers=[1])
 
             # Verify that we got exactly one PR
             assert len(prs) == 1
 
             # Verify that thread_comments are set correctly
-            assert prs[0].thread_comments == ["First comment", "Second comment"]
+            assert prs[0].thread_comments == ['First comment', 'Second comment']
 
             # Verify other fields are set correctly
             assert prs[0].number == 1
-            assert prs[0].title == "Test PR"
-            assert prs[0].body == "Test Body fixes #1"
-            assert prs[0].owner == "test-owner"
-            assert prs[0].repo == "test-repo"
-            assert prs[0].head_branch == "test-branch"
+            assert prs[0].title == 'Test PR'
+            assert prs[0].body == 'Test Body fixes #1'
+            assert prs[0].owner == 'test-owner'
+            assert prs[0].repo == 'test-repo'
+            assert prs[0].head_branch == 'test-branch'
             assert prs[0].closing_issues == [
-                "This is additional context from an externally referenced issue."
+                'This is additional context from an externally referenced issue.'
             ]
 
 
 def test_pr_handler_guess_success_only_review_comments():
     # Create a PR handler instance
-    handler = PRHandler("test-owner", "test-repo", "test-token")
+    handler = PRHandler('test-owner', 'test-repo', 'test-token')
 
     # Create a mock issue with only review comments
     issue = GithubIssue(
-        owner="test-owner",
-        repo="test-repo",
+        owner='test-owner',
+        repo='test-repo',
         number=1,
-        title="Test PR",
-        body="Test Body",
+        title='Test PR',
+        body='Test Body',
         thread_comments=None,
-        closing_issues=["Issue description"],
-        review_comments=["Please fix the formatting", "Add more tests"],
+        closing_issues=['Issue description'],
+        review_comments=['Please fix the formatting', 'Add more tests'],
         thread_ids=None,
-        head_branch="test-branch",
+        head_branch='test-branch',
     )
 
     # Create mock history
-    history = [MessageAction(content="Fixed the formatting and added more tests")]
+    history = [MessageAction(content='Fixed the formatting and added more tests')]
 
     # Create mock LLM config
-    llm_config = LLMConfig(model="test-model", api_key="test-key")
+    llm_config = LLMConfig(model='test-model', api_key='test-key')
 
     # Mock the LLM response
     mock_response = MagicMock()
@@ -216,7 +217,7 @@ The changes successfully address the review comments."""
     ]
 
     # Test the guess_success method
-    with patch("litellm.completion", return_value=mock_response):
+    with patch('litellm.completion', return_value=mock_response):
         success, success_list, explanation = handler.guess_success(
             issue, history, llm_config
         )
@@ -224,32 +225,32 @@ The changes successfully address the review comments."""
         # Verify the results
         assert success is True
         assert success_list == [True]
-        assert "successfully address" in explanation
+        assert 'successfully address' in explanation
 
 
 def test_pr_handler_guess_success_no_comments():
     # Create a PR handler instance
-    handler = PRHandler("test-owner", "test-repo", "test-token")
+    handler = PRHandler('test-owner', 'test-repo', 'test-token')
 
     # Create a mock issue with no comments
     issue = GithubIssue(
-        owner="test-owner",
-        repo="test-repo",
+        owner='test-owner',
+        repo='test-repo',
         number=1,
-        title="Test PR",
-        body="Test Body",
+        title='Test PR',
+        body='Test Body',
         thread_comments=None,
-        closing_issues=["Issue description"],
+        closing_issues=['Issue description'],
         review_comments=None,
         thread_ids=None,
-        head_branch="test-branch",
+        head_branch='test-branch',
     )
 
     # Create mock history
-    history = [MessageAction(content="Fixed the issue")]
+    history = [MessageAction(content='Fixed the issue')]
 
     # Create mock LLM config
-    llm_config = LLMConfig(model="test-model", api_key="test-key")
+    llm_config = LLMConfig(model='test-model', api_key='test-key')
 
     # Test that it returns appropriate message when no comments are present
     success, success_list, explanation = handler.guess_success(
@@ -257,29 +258,29 @@ def test_pr_handler_guess_success_no_comments():
     )
     assert success is False
     assert success_list is None
-    assert explanation == "No feedback was found to process"
+    assert explanation == 'No feedback was found to process'
 
 
 def test_get_issue_comments_with_specific_comment_id():
     # Mock the necessary dependencies
-    with patch("requests.get") as mock_get:
+    with patch('requests.get') as mock_get:
         # Mock the response for comments
         mock_comments_response = MagicMock()
         mock_comments_response.json.return_value = [
-            {"id": 123, "body": "First comment"},
-            {"id": 456, "body": "Second comment"},
+            {'id': 123, 'body': 'First comment'},
+            {'id': 456, 'body': 'Second comment'},
         ]
 
         mock_get.return_value = mock_comments_response
 
         # Create an instance of IssueHandler
-        handler = IssueHandler("test-owner", "test-repo", "test-token")
+        handler = IssueHandler('test-owner', 'test-repo', 'test-token')
 
         # Get comments with a specific comment_id
         specific_comment = handler._get_issue_comments(issue_number=1, comment_id=123)
 
         # Verify only the specific comment is returned
-        assert specific_comment == ["First comment"]
+        assert specific_comment == ['First comment']
 
 
 def test_pr_handler_get_converted_issues_with_specific_thread_comment():
@@ -287,50 +288,50 @@ def test_pr_handler_get_converted_issues_with_specific_thread_comment():
     specific_comment_id = 123
 
     # Mock GraphQL response for review threads
-    with patch("requests.get") as mock_get:
+    with patch('requests.get') as mock_get:
         # Mock the response for PRs
         mock_prs_response = MagicMock()
         mock_prs_response.json.return_value = [
             {
-                "number": 1,
-                "title": "Test PR",
-                "body": "Test Body",
-                "head": {"ref": "test-branch"},
+                'number': 1,
+                'title': 'Test PR',
+                'body': 'Test Body',
+                'head': {'ref': 'test-branch'},
             }
         ]
 
         # Mock the response for PR comments
         mock_comments_response = MagicMock()
         mock_comments_response.json.return_value = [
-            {"body": "First comment", "id": 123},
-            {"body": "Second comment", "id": 124},
+            {'body': 'First comment', 'id': 123},
+            {'body': 'Second comment', 'id': 124},
         ]
 
         # Mock the response for PR metadata (GraphQL)
         mock_graphql_response = MagicMock()
         mock_graphql_response.json.return_value = {
-            "data": {
-                "repository": {
-                    "pullRequest": {
-                        "closingIssuesReferences": {"edges": []},
-                        "reviews": {"nodes": []},
-                        "reviewThreads": {
-                            "edges": [
+            'data': {
+                'repository': {
+                    'pullRequest': {
+                        'closingIssuesReferences': {'edges': []},
+                        'reviews': {'nodes': []},
+                        'reviewThreads': {
+                            'edges': [
                                 {
-                                    "node": {
-                                        "id": "review-thread-1",
-                                        "isResolved": False,
-                                        "comments": {
-                                            "nodes": [
+                                    'node': {
+                                        'id': 'review-thread-1',
+                                        'isResolved': False,
+                                        'comments': {
+                                            'nodes': [
                                                 {
-                                                    "fullDatabaseId": 121,
-                                                    "body": "Specific review comment",
-                                                    "path": "file1.txt",
+                                                    'fullDatabaseId': 121,
+                                                    'body': 'Specific review comment',
+                                                    'path': 'file1.txt',
                                                 },
                                                 {
-                                                    "fullDatabaseId": 456,
-                                                    "body": "Another review comment",
-                                                    "path": "file2.txt",
+                                                    'fullDatabaseId': 456,
+                                                    'body': 'Another review comment',
+                                                    'path': 'file2.txt',
                                                 },
                                             ]
                                         },
@@ -356,30 +357,32 @@ def test_pr_handler_get_converted_issues_with_specific_thread_comment():
         ]
 
         # Mock the post request for GraphQL
-        with patch("requests.post") as mock_post:
+        with patch('requests.post') as mock_post:
             mock_post.return_value = mock_graphql_response
 
             # Create an instance of PRHandler
-            handler = PRHandler("test-owner", "test-repo", "test-token")
+            handler = PRHandler('test-owner', 'test-repo', 'test-token')
 
             # Get converted issues
-            prs = handler.get_converted_issues(comment_id=specific_comment_id)
+            prs = handler.get_converted_issues(
+                issue_numbers=[1], comment_id=specific_comment_id
+            )
 
             # Verify that we got exactly one PR
             assert len(prs) == 1
 
             # Verify that thread_comments are set correctly
-            assert prs[0].thread_comments == ["First comment"]
+            assert prs[0].thread_comments == ['First comment']
             assert prs[0].review_comments == []
             assert prs[0].review_threads == []
 
             # Verify other fields are set correctly
             assert prs[0].number == 1
-            assert prs[0].title == "Test PR"
-            assert prs[0].body == "Test Body"
-            assert prs[0].owner == "test-owner"
-            assert prs[0].repo == "test-repo"
-            assert prs[0].head_branch == "test-branch"
+            assert prs[0].title == 'Test PR'
+            assert prs[0].body == 'Test Body'
+            assert prs[0].owner == 'test-owner'
+            assert prs[0].repo == 'test-repo'
+            assert prs[0].head_branch == 'test-branch'
 
 
 def test_pr_handler_get_converted_issues_with_specific_review_thread_comment():
@@ -387,50 +390,50 @@ def test_pr_handler_get_converted_issues_with_specific_review_thread_comment():
     specific_comment_id = 123
 
     # Mock GraphQL response for review threads
-    with patch("requests.get") as mock_get:
+    with patch('requests.get') as mock_get:
         # Mock the response for PRs
         mock_prs_response = MagicMock()
         mock_prs_response.json.return_value = [
             {
-                "number": 1,
-                "title": "Test PR",
-                "body": "Test Body",
-                "head": {"ref": "test-branch"},
+                'number': 1,
+                'title': 'Test PR',
+                'body': 'Test Body',
+                'head': {'ref': 'test-branch'},
             }
         ]
 
         # Mock the response for PR comments
         mock_comments_response = MagicMock()
         mock_comments_response.json.return_value = [
-            {"body": "First comment", "id": 120},
-            {"body": "Second comment", "id": 124},
+            {'body': 'First comment', 'id': 120},
+            {'body': 'Second comment', 'id': 124},
         ]
 
         # Mock the response for PR metadata (GraphQL)
         mock_graphql_response = MagicMock()
         mock_graphql_response.json.return_value = {
-            "data": {
-                "repository": {
-                    "pullRequest": {
-                        "closingIssuesReferences": {"edges": []},
-                        "reviews": {"nodes": []},
-                        "reviewThreads": {
-                            "edges": [
+            'data': {
+                'repository': {
+                    'pullRequest': {
+                        'closingIssuesReferences': {'edges': []},
+                        'reviews': {'nodes': []},
+                        'reviewThreads': {
+                            'edges': [
                                 {
-                                    "node": {
-                                        "id": "review-thread-1",
-                                        "isResolved": False,
-                                        "comments": {
-                                            "nodes": [
+                                    'node': {
+                                        'id': 'review-thread-1',
+                                        'isResolved': False,
+                                        'comments': {
+                                            'nodes': [
                                                 {
-                                                    "fullDatabaseId": specific_comment_id,
-                                                    "body": "Specific review comment",
-                                                    "path": "file1.txt",
+                                                    'fullDatabaseId': specific_comment_id,
+                                                    'body': 'Specific review comment',
+                                                    'path': 'file1.txt',
                                                 },
                                                 {
-                                                    "fullDatabaseId": 456,
-                                                    "body": "Another review comment",
-                                                    "path": "file1.txt",
+                                                    'fullDatabaseId': 456,
+                                                    'body': 'Another review comment',
+                                                    'path': 'file1.txt',
                                                 },
                                             ]
                                         },
@@ -456,14 +459,16 @@ def test_pr_handler_get_converted_issues_with_specific_review_thread_comment():
         ]
 
         # Mock the post request for GraphQL
-        with patch("requests.post") as mock_post:
+        with patch('requests.post') as mock_post:
             mock_post.return_value = mock_graphql_response
 
             # Create an instance of PRHandler
-            handler = PRHandler("test-owner", "test-repo", "test-token")
+            handler = PRHandler('test-owner', 'test-repo', 'test-token')
 
             # Get converted issues
-            prs = handler.get_converted_issues(comment_id=specific_comment_id)
+            prs = handler.get_converted_issues(
+                issue_numbers=[1], comment_id=specific_comment_id
+            )
 
             # Verify that we got exactly one PR
             assert len(prs) == 1
@@ -475,17 +480,17 @@ def test_pr_handler_get_converted_issues_with_specific_review_thread_comment():
             assert isinstance(prs[0].review_threads[0], ReviewThread)
             assert (
                 prs[0].review_threads[0].comment
-                == "Specific review comment\n---\nlatest feedback:\nAnother review comment\n"
+                == 'Specific review comment\n---\nlatest feedback:\nAnother review comment\n'
             )
-            assert prs[0].review_threads[0].files == ["file1.txt"]
+            assert prs[0].review_threads[0].files == ['file1.txt']
 
             # Verify other fields are set correctly
             assert prs[0].number == 1
-            assert prs[0].title == "Test PR"
-            assert prs[0].body == "Test Body"
-            assert prs[0].owner == "test-owner"
-            assert prs[0].repo == "test-repo"
-            assert prs[0].head_branch == "test-branch"
+            assert prs[0].title == 'Test PR'
+            assert prs[0].body == 'Test Body'
+            assert prs[0].owner == 'test-owner'
+            assert prs[0].repo == 'test-repo'
+            assert prs[0].head_branch == 'test-branch'
 
 
 def test_pr_handler_get_converted_issues_with_specific_comment_and_issue_refs():
@@ -493,50 +498,50 @@ def test_pr_handler_get_converted_issues_with_specific_comment_and_issue_refs():
     specific_comment_id = 123
 
     # Mock GraphQL response for review threads
-    with patch("requests.get") as mock_get:
+    with patch('requests.get') as mock_get:
         # Mock the response for PRs
         mock_prs_response = MagicMock()
         mock_prs_response.json.return_value = [
             {
-                "number": 1,
-                "title": "Test PR fixes #3",
-                "body": "Test Body",
-                "head": {"ref": "test-branch"},
+                'number': 1,
+                'title': 'Test PR fixes #3',
+                'body': 'Test Body',
+                'head': {'ref': 'test-branch'},
             }
         ]
 
         # Mock the response for PR comments
         mock_comments_response = MagicMock()
         mock_comments_response.json.return_value = [
-            {"body": "First comment", "id": 120},
-            {"body": "Second comment", "id": 124},
+            {'body': 'First comment', 'id': 120},
+            {'body': 'Second comment', 'id': 124},
         ]
 
         # Mock the response for PR metadata (GraphQL)
         mock_graphql_response = MagicMock()
         mock_graphql_response.json.return_value = {
-            "data": {
-                "repository": {
-                    "pullRequest": {
-                        "closingIssuesReferences": {"edges": []},
-                        "reviews": {"nodes": []},
-                        "reviewThreads": {
-                            "edges": [
+            'data': {
+                'repository': {
+                    'pullRequest': {
+                        'closingIssuesReferences': {'edges': []},
+                        'reviews': {'nodes': []},
+                        'reviewThreads': {
+                            'edges': [
                                 {
-                                    "node": {
-                                        "id": "review-thread-1",
-                                        "isResolved": False,
-                                        "comments": {
-                                            "nodes": [
+                                    'node': {
+                                        'id': 'review-thread-1',
+                                        'isResolved': False,
+                                        'comments': {
+                                            'nodes': [
                                                 {
-                                                    "fullDatabaseId": specific_comment_id,
-                                                    "body": "Specific review comment that references #6",
-                                                    "path": "file1.txt",
+                                                    'fullDatabaseId': specific_comment_id,
+                                                    'body': 'Specific review comment that references #6',
+                                                    'path': 'file1.txt',
                                                 },
                                                 {
-                                                    "fullDatabaseId": 456,
-                                                    "body": "Another review comment referencing #7",
-                                                    "path": "file2.txt",
+                                                    'fullDatabaseId': 456,
+                                                    'body': 'Another review comment referencing #7',
+                                                    'path': 'file2.txt',
                                                 },
                                             ]
                                         },
@@ -557,13 +562,13 @@ def test_pr_handler_get_converted_issues_with_specific_comment_and_issue_refs():
         # Mock the response for fetching the external issue referenced in PR body
         mock_external_issue_response_in_body = MagicMock()
         mock_external_issue_response_in_body.json.return_value = {
-            "body": "External context #1."
+            'body': 'External context #1.'
         }
 
         # Mock the response for fetching the external issue referenced in review thread
         mock_external_issue_response_review_thread = MagicMock()
         mock_external_issue_response_review_thread.json.return_value = {
-            "body": "External context #2."
+            'body': 'External context #2.'
         }
 
         mock_get.side_effect = [
@@ -576,14 +581,16 @@ def test_pr_handler_get_converted_issues_with_specific_comment_and_issue_refs():
         ]
 
         # Mock the post request for GraphQL
-        with patch("requests.post") as mock_post:
+        with patch('requests.post') as mock_post:
             mock_post.return_value = mock_graphql_response
 
             # Create an instance of PRHandler
-            handler = PRHandler("test-owner", "test-repo", "test-token")
+            handler = PRHandler('test-owner', 'test-repo', 'test-token')
 
             # Get converted issues
-            prs = handler.get_converted_issues(comment_id=specific_comment_id)
+            prs = handler.get_converted_issues(
+                issue_numbers=[1], comment_id=specific_comment_id
+            )
 
             # Verify that we got exactly one PR
             assert len(prs) == 1
@@ -595,52 +602,52 @@ def test_pr_handler_get_converted_issues_with_specific_comment_and_issue_refs():
             assert isinstance(prs[0].review_threads[0], ReviewThread)
             assert (
                 prs[0].review_threads[0].comment
-                == "Specific review comment that references #6\n---\nlatest feedback:\nAnother review comment referencing #7\n"
+                == 'Specific review comment that references #6\n---\nlatest feedback:\nAnother review comment referencing #7\n'
             )
             assert prs[0].closing_issues == [
-                "External context #1.",
-                "External context #2.",
+                'External context #1.',
+                'External context #2.',
             ]  # Only includes references inside comment ID and body PR
 
             # Verify other fields are set correctly
             assert prs[0].number == 1
-            assert prs[0].title == "Test PR fixes #3"
-            assert prs[0].body == "Test Body"
-            assert prs[0].owner == "test-owner"
-            assert prs[0].repo == "test-repo"
-            assert prs[0].head_branch == "test-branch"
+            assert prs[0].title == 'Test PR fixes #3'
+            assert prs[0].body == 'Test Body'
+            assert prs[0].owner == 'test-owner'
+            assert prs[0].repo == 'test-repo'
+            assert prs[0].head_branch == 'test-branch'
 
 
 def test_pr_handler_get_converted_issues_with_duplicate_issue_refs():
     # Mock the necessary dependencies
-    with patch("requests.get") as mock_get:
+    with patch('requests.get') as mock_get:
         # Mock the response for PRs
         mock_prs_response = MagicMock()
         mock_prs_response.json.return_value = [
             {
-                "number": 1,
-                "title": "Test PR",
-                "body": "Test Body fixes #1",
-                "head": {"ref": "test-branch"},
+                'number': 1,
+                'title': 'Test PR',
+                'body': 'Test Body fixes #1',
+                'head': {'ref': 'test-branch'},
             }
         ]
 
         # Mock the response for PR comments
         mock_comments_response = MagicMock()
         mock_comments_response.json.return_value = [
-            {"body": "First comment addressing #1"},
-            {"body": "Second comment addressing #2"},
+            {'body': 'First comment addressing #1'},
+            {'body': 'Second comment addressing #2'},
         ]
 
         # Mock the response for PR metadata (GraphQL)
         mock_graphql_response = MagicMock()
         mock_graphql_response.json.return_value = {
-            "data": {
-                "repository": {
-                    "pullRequest": {
-                        "closingIssuesReferences": {"edges": []},
-                        "reviews": {"nodes": []},
-                        "reviewThreads": {"edges": []},
+            'data': {
+                'repository': {
+                    'pullRequest': {
+                        'closingIssuesReferences': {'edges': []},
+                        'reviews': {'nodes': []},
+                        'reviewThreads': {'edges': []},
                     }
                 }
             }
@@ -654,13 +661,13 @@ def test_pr_handler_get_converted_issues_with_duplicate_issue_refs():
         # Mock the response for fetching the external issue referenced in PR body
         mock_external_issue_response_in_body = MagicMock()
         mock_external_issue_response_in_body.json.return_value = {
-            "body": "External context #1."
+            'body': 'External context #1.'
         }
 
         # Mock the response for fetching the external issue referenced in review thread
         mock_external_issue_response_in_comment = MagicMock()
         mock_external_issue_response_in_comment.json.return_value = {
-            "body": "External context #2."
+            'body': 'External context #2.'
         }
 
         mock_get.side_effect = [
@@ -673,32 +680,32 @@ def test_pr_handler_get_converted_issues_with_duplicate_issue_refs():
         ]
 
         # Mock the post request for GraphQL
-        with patch("requests.post") as mock_post:
+        with patch('requests.post') as mock_post:
             mock_post.return_value = mock_graphql_response
 
             # Create an instance of PRHandler
-            handler = PRHandler("test-owner", "test-repo", "test-token")
+            handler = PRHandler('test-owner', 'test-repo', 'test-token')
 
             # Get converted issues
-            prs = handler.get_converted_issues()
+            prs = handler.get_converted_issues(issue_numbers=[1])
 
             # Verify that we got exactly one PR
             assert len(prs) == 1
 
             # Verify that thread_comments are set correctly
             assert prs[0].thread_comments == [
-                "First comment addressing #1",
-                "Second comment addressing #2",
+                'First comment addressing #1',
+                'Second comment addressing #2',
             ]
 
             # Verify other fields are set correctly
             assert prs[0].number == 1
-            assert prs[0].title == "Test PR"
-            assert prs[0].body == "Test Body fixes #1"
-            assert prs[0].owner == "test-owner"
-            assert prs[0].repo == "test-repo"
-            assert prs[0].head_branch == "test-branch"
+            assert prs[0].title == 'Test PR'
+            assert prs[0].body == 'Test Body fixes #1'
+            assert prs[0].owner == 'test-owner'
+            assert prs[0].repo == 'test-repo'
+            assert prs[0].head_branch == 'test-branch'
             assert prs[0].closing_issues == [
-                "External context #1.",
-                "External context #2.",
+                'External context #1.',
+                'External context #2.',
             ]

+ 6 - 4
tests/unit/resolver/test_resolve_issues.py

@@ -112,7 +112,7 @@ def test_download_issues_from_github():
         return mock_issues_response
 
     with patch('requests.get', side_effect=get_mock_response):
-        issues = handler.get_converted_issues()
+        issues = handler.get_converted_issues(issue_numbers=[1, 3])
 
     assert len(issues) == 2
     assert handler.issue_type == 'issue'
@@ -225,7 +225,7 @@ def test_download_pr_from_github():
 
     with patch('requests.get', side_effect=get_mock_response):
         with patch('requests.post', return_value=mock_graphql_response):
-            issues = handler.get_converted_issues()
+            issues = handler.get_converted_issues(issue_numbers=[1, 2, 3])
 
     assert len(issues) == 3
     assert handler.issue_type == 'pr'
@@ -811,7 +811,7 @@ def test_download_pr_with_review_comments():
 
     with patch('requests.get', side_effect=get_mock_response):
         with patch('requests.post', return_value=mock_graphql_response):
-            issues = handler.get_converted_issues()
+            issues = handler.get_converted_issues(issue_numbers=[1])
 
     assert len(issues) == 1
     assert handler.issue_type == 'pr'
@@ -867,7 +867,9 @@ def test_download_issue_with_specific_comment():
         return mock_issue_response
 
     with patch('requests.get', side_effect=get_mock_response):
-        issues = handler.get_converted_issues(comment_id=specific_comment_id)
+        issues = handler.get_converted_issues(
+            issue_numbers=[1], comment_id=specific_comment_id
+        )
 
     assert len(issues) == 1
     assert issues[0].number == 1