import unittest import click from click.testing import CliRunner as CLIRunner from click import utils as click_utils import mock from packaging import version from git_pw import patch @mock.patch('git_pw.api.detail') @mock.patch('git_pw.api.download') @mock.patch('git_pw.utils.git_am') class ApplyTestCase(unittest.TestCase): def test_apply(self, mock_git_am, mock_download, mock_detail): """Validate behavior with no arguments.""" rsp = {'mbox': 'hello, world'} mock_detail.return_value = rsp mock_download.return_value = object() runner = CLIRunner() result = runner.invoke(patch.apply_cmd, ['123']) assert result.exit_code == 0, result mock_detail.assert_called_once_with('patches', 123) mock_download.assert_called_once_with(rsp['mbox'], {'series': '*'}) mock_git_am.assert_called_once_with(mock_download.return_value, ()) def test_apply_with_series(self, mock_git_am, mock_download, mock_detail): """Validate behavior with a specific series.""" rsp = {'mbox': 'hello, world'} mock_detail.return_value = rsp mock_download.return_value = object() runner = CLIRunner() result = runner.invoke(patch.apply_cmd, ['123', '--series', 3]) assert result.exit_code == 0, result mock_detail.assert_called_once_with('patches', 123) mock_download.assert_called_once_with(rsp['mbox'], {'series': 3}) mock_git_am.assert_called_once_with(mock_download.return_value, ()) def test_apply_without_deps(self, mock_git_am, mock_download, mock_detail): """Validate behavior without using dependencies.""" rsp = {'mbox': 'hello, world'} mock_detail.return_value = rsp mock_download.return_value = object() runner = CLIRunner() result = runner.invoke(patch.apply_cmd, ['123', '--no-deps']) assert result.exit_code == 0, result mock_detail.assert_called_once_with('patches', 123) mock_download.assert_called_once_with(rsp['mbox'], {'series': None}) mock_git_am.assert_called_once_with(mock_download.return_value, ()) def test_apply_with_args(self, mock_git_am, mock_download, mock_detail): """Validate passthrough of arbitrary arguments to git-am.""" rsp = {'mbox': 'hello, world'} mock_detail.return_value = rsp mock_download.return_value = object() runner = CLIRunner() result = runner.invoke(patch.apply_cmd, ['123', '-3']) assert result.exit_code == 0, result mock_detail.assert_called_once_with('patches', 123) mock_download.assert_called_once_with(rsp['mbox'], {'series': '*'}) mock_git_am.assert_called_once_with(mock_download.return_value, ('-3',)) @mock.patch('git_pw.api.detail') @mock.patch('git_pw.api.download') @mock.patch('git_pw.patch.LOG') class DownloadTestCase(unittest.TestCase): def test_download(self, mock_log, mock_download, mock_detail): """Validate standard behavior.""" rsp = {'mbox': 'hello, world', 'diff': 'test'} mock_detail.return_value = rsp mock_download.return_value = '/tmp/abc123.patch' runner = CLIRunner() result = runner.invoke(patch.download_cmd, ['123']) assert result.exit_code == 0, result mock_detail.assert_called_once_with('patches', 123) mock_download.assert_called_once_with(rsp['mbox'], output=None) assert mock_log.info.called def test_download_diff(self, mock_log, mock_download, mock_detail): """Validate behavior if downloading a diff instead of mbox.""" rsp = {'mbox': 'hello, world', 'diff': 'test'} mock_detail.return_value = rsp runner = CLIRunner() result = runner.invoke(patch.download_cmd, ['123', '--diff']) assert result.exit_code == 0, result mock_detail.assert_called_once_with('patches', 123) mock_download.assert_called_once_with( rsp['mbox'].replace('mbox', 'raw'), output=None, ) assert mock_log.info.called def test_download_to_file(self, mock_log, mock_download, mock_detail): """Validate behavior if downloading to a specific file.""" rsp = {'mbox': 'hello, world', 'diff': 'test'} mock_detail.return_value = rsp runner = CLIRunner() result = runner.invoke(patch.download_cmd, ['123', 'test.patch']) assert result.exit_code == 0, result mock_detail.assert_called_once_with('patches', 123) mock_download.assert_called_once_with(rsp['mbox'], output=mock.ANY) assert isinstance( mock_download.call_args[1]['output'], click_utils.LazyFile, ) assert mock_log.info.called def test_download_diff_to_file(self, mock_log, mock_download, mock_detail): """Validate behavior if downloading a diff to a specific file.""" rsp = {'mbox': 'hello, world', 'diff': b'test'} mock_detail.return_value = rsp runner = CLIRunner() with runner.isolated_filesystem(): result = runner.invoke(patch.download_cmd, ['123', '--diff', 'test.diff']) assert result.exit_code == 0, result with open('test.diff') as output: assert [rsp['diff'].decode()] == output.readlines() mock_detail.assert_called_once_with('patches', 123) mock_download.assert_not_called() assert mock_log.info.called class ShowTestCase(unittest.TestCase): @staticmethod def _get_patch(**kwargs): rsp = { 'id': 123, 'msgid': 'hello@example.com', 'date': '2017-01-01 00:00:00', 'name': 'Sample patch', 'submitter': { 'name': 'foo', 'email': 'foo@bar.com', }, 'state': 'new', 'archived': False, 'project': { 'name': 'bar', }, 'delegate': { 'username': 'johndoe', }, 'commit_ref': None, 'series': [ { 'id': 321, 'name': 'Sample series', } ], } rsp.update(**kwargs) return rsp @mock.patch('git_pw.api.detail') def test_show(self, mock_detail): """Validate standard behavior.""" rsp = self._get_patch() mock_detail.return_value = rsp runner = CLIRunner() result = runner.invoke(patch.show_cmd, ['123']) assert result.exit_code == 0, result mock_detail.assert_called_once_with('patches', 123) @mock.patch('git_pw.api.update') @mock.patch.object(patch, '_show_patch') @mock.patch.object(patch, '_get_states') class UpdateTestCase(unittest.TestCase): @staticmethod def _get_person(**kwargs): rsp = { 'id': 1, 'name': 'John Doe', 'email': 'john@example.com', } rsp.update(**kwargs) return rsp def test_update_no_arguments(self, mock_states, mock_show, mock_update): """Validate behavior with no arguments.""" runner = CLIRunner() result = runner.invoke(patch.update_cmd, ['123']) assert result.exit_code == 0, result mock_update.assert_called_once_with('patches', 123, []) mock_show.assert_called_once_with(mock_update.return_value, None) def test_update_with_arguments(self, mock_states, mock_show, mock_update): """Validate behavior with all arguments except delegate.""" mock_states.return_value = ['new'] runner = CLIRunner() result = runner.invoke(patch.update_cmd, [ '123', '--commit-ref', '3ed8fb12', '--state', 'new', '--archived', '1', '--format', 'table']) assert result.exit_code == 0, result mock_update.assert_called_once_with('patches', 123, [ ('commit_ref', '3ed8fb12'), ('state', 'new'), ('archived', True)]) mock_show.assert_called_once_with(mock_update.return_value, 'table') def test_update_with_invalid_state( self, mock_states, mock_show, mock_update): """Validate behavior with invalid state.""" mock_states.return_value = ['foo'] runner = CLIRunner() result = runner.invoke(patch.update_cmd, [ '123', '--state', 'bar']) assert result.exit_code == 2, result if version.parse(click.__version__) >= version.Version('7.1'): assert "Invalid value for '--state'" in result.output, result else: assert 'Invalid value for "--state"' in result.output, result @mock.patch('git_pw.api.index') def test_update_with_delegate( self, mock_index, mock_states, mock_show, mock_update): """Validate behavior with delegate argument.""" mock_index.return_value = [self._get_person()] runner = CLIRunner() result = runner.invoke(patch.update_cmd, [ '123', '--delegate', 'doe@example.com']) assert result.exit_code == 0, result mock_index.assert_called_once_with('users', [('q', 'doe@example.com')]) mock_update.assert_called_once_with('patches', 123, [ ('delegate', mock_index.return_value[0]['id'])]) mock_show.assert_called_once_with(mock_update.return_value, None) @mock.patch('git_pw.api.version', return_value=(1, 0)) @mock.patch('git_pw.api.index') @mock.patch('git_pw.utils.echo_via_pager') class ListTestCase(unittest.TestCase): @staticmethod def _get_patch(**kwargs): return ShowTestCase._get_patch(**kwargs) @staticmethod def _get_person(**kwargs): rsp = { 'id': 1, 'name': 'John Doe', 'email': 'john@example.com', } rsp.update(**kwargs) return rsp @staticmethod def _get_users(**kwargs): rsp = { 'id': 1, 'username': 'john.doe', 'email': 'john@example.com', } rsp.update(**kwargs) return rsp def test_list(self, mock_echo, mock_index, mock_version): """Validate standard behavior.""" rsp = [self._get_patch()] mock_index.return_value = rsp runner = CLIRunner() result = runner.invoke(patch.list_cmd, []) assert result.exit_code == 0, result mock_index.assert_called_once_with('patches', [ ('state', 'under-review'), ('state', 'new'), ('q', None), ('archived', 'false'), ('page', None), ('per_page', None), ('order', '-date')]) def test_list_with_formatting(self, mock_echo, mock_index, mock_version): rsp = [self._get_patch()] mock_index.return_value = rsp runner = CLIRunner() result = runner.invoke(patch.list_cmd, [ '--format', 'simple', '--column', 'ID', '--column', 'Name']) assert result.exit_code == 0, result mock_echo.assert_called_once_with(mock.ANY, ('ID', 'Name'), fmt='simple') def test_list_with_filters(self, mock_echo, mock_index, mock_version): """Validate behavior with filters applied. Apply all filters, including those for pagination. """ submitter_rsp = [self._get_person()] delegate_rsp = [self._get_person()] patch_rsp = [self._get_patch()] mock_index.side_effect = [submitter_rsp, delegate_rsp, patch_rsp] runner = CLIRunner() result = runner.invoke(patch.list_cmd, [ '--state', 'new', '--submitter', 'john@example.com', '--submitter', '2', '--delegate', 'doe@example.com', '--delegate', '2', '--archived', '--limit', 1, '--page', 1, '--sort', '-name', 'test']) assert result.exit_code == 0, result calls = [ mock.call('people', [('q', 'john@example.com')]), mock.call('users', [('q', 'doe@example.com')]), mock.call('patches', [ ('state', 'new'), ('submitter', 1), ('submitter', '2'), ('delegate', 1), ('delegate', '2'), ('q', 'test'), ('archived', 'true'), ('page', 1), ('per_page', 1), ('order', '-name')])] mock_index.assert_has_calls(calls) @mock.patch('git_pw.api.LOG') def test_list_with_wildcard_filters(self, mock_log, mock_echo, mock_index, mock_version): """Validate behavior with a "wildcard" filter. Patchwork API v1.0 did not support multiple filters correctly. Ensure the user is warned as necessary if a filter has multiple matches. """ people_rsp = [self._get_person(), self._get_person()] patch_rsp = [self._get_patch()] mock_index.side_effect = [people_rsp, patch_rsp] runner = CLIRunner() runner.invoke(patch.list_cmd, ['--submitter', 'john@example.com']) assert mock_log.warning.called @mock.patch('git_pw.api.LOG') def test_list_with_multiple_filters(self, mock_log, mock_echo, mock_index, mock_version): """Validate behavior with use of multiple filters. Patchwork API v1.0 did not support multiple filters correctly. Ensure the user is warned as necessary if they specify multiple filters. """ people_rsp = [self._get_person()] user_rsp = [self._get_users()] patch_rsp = [self._get_patch()] mock_index.side_effect = [people_rsp, people_rsp, user_rsp, user_rsp, patch_rsp] runner = CLIRunner() result = runner.invoke(patch.list_cmd, [ '--submitter', 'John Doe', '--submitter', 'Jimmy Foo', '--delegate', 'foo', '--delegate', 'bar']) assert result.exit_code == 0, result assert mock_log.warning.called @mock.patch('git_pw.api.LOG') def test_list_api_v1_1(self, mock_log, mock_echo, mock_index, mock_version): """Validate behavior with API v1.1.""" mock_version.return_value = (1, 1) people_rsp = [self._get_person()] user_rsp = [self._get_users()] patch_rsp = [self._get_patch()] mock_index.side_effect = [people_rsp, user_rsp, patch_rsp] runner = CLIRunner() result = runner.invoke(patch.list_cmd, [ '--submitter', 'jimmy@example.com', '--submitter', 'John Doe', '--delegate', 'foo', '--delegate', 'john@example.com']) assert result.exit_code == 0, result # We should have only made a single call to each of '/users' and # '/people' (for the user specified by an email address and the # submitter specified by name, respectively) since API v1.1 supports # filtering of users with username and people with emails natively calls = [ mock.call('people', [('q', 'John Doe')]), mock.call('users', [('q', 'john@example.com')]), mock.call('patches', [ ('state', 'under-review'), ('state', 'new'), ('submitter', 'jimmy@example.com'), ('submitter', 1), ('delegate', 'foo'), ('delegate', 1), ('q', None), ('archived', 'false'), ('page', None), ('per_page', None), ('order', '-date')])] mock_index.assert_has_calls(calls) # We shouldn't see a warning about multiple versions either assert not mock_log.warning.called