Back to Repositories

Testing Pull Request Title Character Escaping in OpenHands

This test suite validates the proper handling of special characters and quotes in pull request titles and commit messages within the OpenHands repository. It ensures that text containing single quotes, double quotes, and special characters is properly escaped without over-escaping.

Test Coverage Overview

The test suite provides comprehensive coverage for pull request title and commit message handling:
  • Verifies correct escaping of single and double quotes in commit messages
  • Tests special character handling in PR titles
  • Validates integration with GitHub API for PR creation
  • Covers edge cases with mixed quote types and special characters

Implementation Analysis

The testing approach utilizes temporary git repositories and mocked HTTP responses to isolate the testing environment. It implements pytest fixtures and monkeypatching to simulate GitHub API interactions and git operations, ensuring reliable and reproducible tests without external dependencies.

Technical Details

Key technical components include:
  • pytest for test framework and assertions
  • tempfile for temporary directory management
  • subprocess for git command execution
  • monkeypatch for mocking external dependencies
  • Custom MockResponse classes for API simulation

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Isolation of test environment using temporary directories
  • Proper mocking of external services
  • Detailed assertion messages for debugging
  • Comprehensive edge case coverage
  • Clean setup and teardown of test resources

all-hands-ai/openhands

tests/unit/resolver/test_pr_title_escaping.py

            
import os
import subprocess
import tempfile

from openhands.resolver.github_issue import GithubIssue
from openhands.resolver.send_pull_request import make_commit


def test_commit_message_with_quotes():
    # Create a temporary directory and initialize git repo
    with tempfile.TemporaryDirectory() as temp_dir:
        subprocess.run(['git', 'init', temp_dir], check=True)

        # Create a test file and add it to git
        test_file = os.path.join(temp_dir, 'test.txt')
        with open(test_file, 'w') as f:
            f.write('test content')

        subprocess.run(['git', '-C', temp_dir, 'add', 'test.txt'], check=True)

        # Create a test issue with problematic title
        issue = GithubIssue(
            owner='test-owner',
            repo='test-repo',
            number=123,
            title="Issue with 'quotes' and \"double quotes\" and <class 'ValueError'>",
            body='Test body',
            labels=[],
            assignees=[],
            state='open',
            created_at='2024-01-01T00:00:00Z',
            updated_at='2024-01-01T00:00:00Z',
            closed_at=None,
            head_branch=None,
            thread_ids=None,
        )

        # Make the commit
        make_commit(temp_dir, issue, 'issue')

        # Get the commit message
        result = subprocess.run(
            ['git', '-C', temp_dir, 'log', '-1', '--pretty=%B'],
            capture_output=True,
            text=True,
            check=True,
        )
        commit_msg = result.stdout.strip()

        # The commit message should contain the quotes without excessive escaping
        expected = "Fix issue #123: Issue with 'quotes' and \"double quotes\" and <class 'ValueError'>"
        assert commit_msg == expected, f'Expected: {expected}
Got: {commit_msg}'


def test_pr_title_with_quotes(monkeypatch):
    # Mock requests.post to avoid actual API calls
    class MockResponse:
        def __init__(self, status_code=201):
            self.status_code = status_code
            self.text = ''

        def json(self):
            return {'html_url': 'https://github.com/test/test/pull/1'}

        def raise_for_status(self):
            pass

    def mock_post(*args, **kwargs):
        # Verify that the PR title is not over-escaped
        data = kwargs.get('json', {})
        title = data.get('title', '')
        expected = "Fix issue #123: Issue with 'quotes' and \"double quotes\" and <class 'ValueError'>"
        assert (
            title == expected
        ), f'PR title was incorrectly escaped.
Expected: {expected}
Got: {title}'
        return MockResponse()

    class MockGetResponse:
        def __init__(self, status_code=200):
            self.status_code = status_code
            self.text = ''

        def json(self):
            return {'default_branch': 'main'}

        def raise_for_status(self):
            pass

    monkeypatch.setattr('requests.post', mock_post)
    monkeypatch.setattr('requests.get', lambda *args, **kwargs: MockGetResponse())
    monkeypatch.setattr(
        'openhands.resolver.send_pull_request.branch_exists',
        lambda *args, **kwargs: False,
    )

    # Mock subprocess.run to avoid actual git commands
    original_run = subprocess.run

    def mock_run(*args, **kwargs):
        print(f"Running command: {args[0] if args else kwargs.get('args', [])}")
        if isinstance(args[0], list) and args[0][0] == 'git':
            if 'push' in args[0]:
                return subprocess.CompletedProcess(
                    args[0], returncode=0, stdout='', stderr=''
                )
            return original_run(*args, **kwargs)
        return original_run(*args, **kwargs)

    monkeypatch.setattr('subprocess.run', mock_run)

    # Create a temporary directory and initialize git repo
    with tempfile.TemporaryDirectory() as temp_dir:
        print('Initializing git repo...')
        subprocess.run(['git', 'init', temp_dir], check=True)

        # Add these lines to configure git
        subprocess.run(
            ['git', '-C', temp_dir, 'config', 'user.name', 'Test User'], check=True
        )
        subprocess.run(
            ['git', '-C', temp_dir, 'config', 'user.email', '[email protected]'],
            check=True,
        )

        # Create a test file and add it to git
        test_file = os.path.join(temp_dir, 'test.txt')
        with open(test_file, 'w') as f:
            f.write('test content')

        print('Adding and committing test file...')
        subprocess.run(['git', '-C', temp_dir, 'add', 'test.txt'], check=True)
        subprocess.run(
            ['git', '-C', temp_dir, 'commit', '-m', 'Initial commit'], check=True
        )

        # Create a test issue with problematic title
        print('Creating test issue...')
        issue = GithubIssue(
            owner='test-owner',
            repo='test-repo',
            number=123,
            title="Issue with 'quotes' and \"double quotes\" and <class 'ValueError'>",
            body='Test body',
            labels=[],
            assignees=[],
            state='open',
            created_at='2024-01-01T00:00:00Z',
            updated_at='2024-01-01T00:00:00Z',
            closed_at=None,
            head_branch=None,
            thread_ids=None,
        )

        # Try to send a PR - this will fail if the title is incorrectly escaped
        print('Sending PR...')
        from openhands.resolver.send_pull_request import send_pull_request

        send_pull_request(
            github_issue=issue,
            github_token='dummy-token',
            github_username='test-user',
            patch_dir=temp_dir,
            pr_type='ready',
        )