def test_validate_diff_too_big(self): """Testing ValidateCommitForm.validate_diff for a diff that is too large """ form = ValidateCommitForm(repository=self.repository, request=self.request, data={ 'commit_id': 'r1', 'parent_id': 'r0', }, files={ 'diff': self.diff, }) self.assertTrue(form.is_valid()) siteconfig = SiteConfiguration.objects.get_current() max_diff_size = siteconfig.get('diffviewer_max_diff_size') siteconfig.set('diffviewer_max_diff_size', 1) siteconfig.save() with self.assertRaises(DiffTooBigError): try: form.validate_diff() finally: siteconfig.set('diffviewer_max_diff_size', max_diff_size) siteconfig.save()
def test_clean_validation_info_invalid_json(self): """Testing ValidateCommitForm.clean_validation_info with base64-encoded non-json data """ validation_info = base64.b64encode('Not valid json.') form = ValidateCommitForm( repository=self.repository, request=self.request, data={ 'commit_id': 'r2', 'parent_id': 'r1', 'validation_info': validation_info, }, files={ 'diff': self.diff, }) self.assertFalse(form.is_valid()) self.assertEqual(form.errors, { 'validation_info': [ 'Could not parse validation info "%s": No JSON object could ' 'be decoded' % validation_info, ], })
def test_clean_parent_diff_subsequent_commit(self): """Testing ValidateCommitForm.clean with a non-empty parent diff for a subsequent commit """ validation_info = base64.b64encode( json.dumps({ 'r1': { 'parent_id': 'r0', 'tree': { 'added': [], 'removed': [], 'modified': [], }, }, })) parent_diff = SimpleUploadedFile('diff', self._PARENT_DIFF_DATA, content_type='text/x-patch') form = ValidateCommitForm(repository=self.repository, request=self.request, data={ 'commit_id': 'r2', 'parent_id': 'r1', 'validation_info': validation_info, }, files={ 'diff': self.diff, 'parent_diff': parent_diff, }) self.assertTrue(form.is_valid())
def test_clean_parent_not_validated(self): """Testing ValidateCommitForm.clean for a commit whose parent has not been validated """ validation_info = base64.b64encode(json.dumps({ 'r1': { 'parent_id': 'r0', 'tree': { 'added': [], 'removed': [], 'modified': [], }, }, })) form = ValidateCommitForm( repository=self.repository, request=self.request, data={ 'commit_id': 'r3', 'parent_id': 'r2', 'validation_info': validation_info, }, files={ 'diff': self.diff, }) self.assertFalse(form.is_valid()) self.assertEqual(form.errors, { 'validation_info': ['The parent commit was not validated.'], })
def test_clean_validation_info_invalid_json(self): """Testing ValidateCommitForm.clean_validation_info with base64-encoded non-json data """ validation_info = base64.b64encode(b'Not valid json.') form = ValidateCommitForm(repository=self.repository, request=self.request, data={ 'commit_id': 'r2', 'parent_id': 'r1', 'validation_info': validation_info, }, files={ 'diff': self.diff, }) self.assertFalse(form.is_valid()) # Python 2 and 3 differ in the error contents you'll get when # attempting to load non-JSON data. if six.PY3: expected_error = 'Expecting value: line 1 column 1 (char 0)' else: expected_error = 'No JSON object could be decoded' self.assertEqual( form.errors, { 'validation_info': [ 'Could not parse validation info "%s": %s' % (validation_info.decode('utf-8'), expected_error), ], })
def test_validate_diff_too_big(self): """Testing ValidateCommitForm.validate_diff for a diff that is too large """ form = ValidateCommitForm( repository=self.repository, request=self.request, data={ 'commit_id': 'r1', 'parent_id': 'r0', }, files={ 'diff': self.diff, }) self.assertTrue(form.is_valid()) siteconfig = SiteConfiguration.objects.get_current() max_diff_size = siteconfig.get('diffviewer_max_diff_size') siteconfig.set('diffviewer_max_diff_size', 1) siteconfig.save() with self.assertRaises(DiffTooBigError): try: form.validate_diff() finally: siteconfig.set('diffviewer_max_diff_size', max_diff_size) siteconfig.save()
def test_clean_validation_info(self): """Testing ValidateCommitForm.clean_validation_info""" validation_info = base64.b64encode( json.dumps({ 'r1': { 'parent_id': 'r0', 'tree': { 'added': [], 'removed': [], 'modified': [], }, }, })) form = ValidateCommitForm(repository=self.repository, request=self.request, data={ 'commit_id': 'r2', 'parent_id': 'r1', 'validation_info': validation_info, }, files={ 'diff': self.diff, }) self.assertTrue(form.is_valid())
def test_validate_diff_missing_files(self): """Testing ValidateCommitForm.validate_diff for a subsequent commit with missing files """ validation_info = base64.b64encode(json.dumps({ 'r1': { 'parent_id': 'r0', 'tree': { 'added': [], 'removed': [], 'modified': [], }, }, })) form = ValidateCommitForm( repository=self.repository, request=self.request, data={ 'commit_id': 'r2', 'parent_id': 'r1', 'validation_info': validation_info, }, files={ 'diff': self.diff, }) self.assertTrue(form.is_valid()) with self.assertRaises(FileNotFoundError): form.validate_diff()
def test_validate_diff_missing_files(self): """Testing ValidateCommitForm.validate_diff for a subsequent commit with missing files """ validation_info = base64.b64encode( json.dumps({ 'r1': { 'parent_id': 'r0', 'tree': { 'added': [], 'removed': [], 'modified': [], }, }, })) form = ValidateCommitForm(repository=self.repository, request=self.request, data={ 'commit_id': 'r2', 'parent_id': 'r1', 'validation_info': validation_info, }, files={ 'diff': self.diff, }) self.assertTrue(form.is_valid()) with self.assertRaises(FileNotFoundError): form.validate_diff()
def test_clean_validation_info(self): """Testing ValidateCommitForm.clean_validation_info""" validation_info = base64.b64encode(json.dumps({ 'r1': { 'parent_id': 'r0', 'tree': { 'added': [], 'removed': [], 'modified': [], }, }, })) form = ValidateCommitForm( repository=self.repository, request=self.request, data={ 'commit_id': 'r2', 'parent_id': 'r1', 'validation_info': validation_info, }, files={ 'diff': self.diff, }) self.assertTrue(form.is_valid())
def test_clean_parent_diff_subsequent_commit(self): """Testing ValidateCommitForm.clean with a non-empty parent diff for a subsequent commit """ validation_info = base64.b64encode(json.dumps({ 'r1': { 'parent_id': 'r0', 'tree': { 'added': [], 'removed': [], 'modified': [], }, }, })) parent_diff = SimpleUploadedFile('diff', self._PARENT_DIFF_DATA, content_type='text/x-patch') form = ValidateCommitForm( repository=self.repository, request=self.request, data={ 'commit_id': 'r2', 'parent_id': 'r1', 'validation_info': validation_info, }, files={ 'diff': self.diff, 'parent_diff': parent_diff, }) self.assertTrue(form.is_valid())
def test_clean_parent_not_validated(self): """Testing ValidateCommitForm.clean for a commit whose parent has not been validated """ validation_info = base64.b64encode( json.dumps({ 'r1': { 'parent_id': 'r0', 'tree': { 'added': [], 'removed': [], 'modified': [], }, }, })) form = ValidateCommitForm(repository=self.repository, request=self.request, data={ 'commit_id': 'r3', 'parent_id': 'r2', 'validation_info': validation_info, }, files={ 'diff': self.diff, }) self.assertFalse(form.is_valid()) self.assertEqual(form.errors, { 'validation_info': ['The parent commit was not validated.'], })
def test_clean_already_validated(self): """Testing ValidateCommitForm.clean for a commit that has already been validated """ validation_info = self._base64_json({ 'r1': { 'parent_id': 'r0', 'tree': { 'added': [], 'removed': [], 'modified': [], }, }, }) form = ValidateCommitForm(repository=self.repository, request=self.request, data={ 'commit_id': 'r1', 'parent_id': 'r0', 'validation_info': validation_info, }, files={ 'diff': self.diff, }) self.assertFalse(form.is_valid()) self.assertEqual(form.errors, { 'validation_info': ['This commit was already validated.'], })
def test_validate_diff(self): """Testing ValidateCommitForm.validate_diff""" self.spy_on(self.repository.get_file_exists, call_fake=lambda *args, **kwargs: True) form = ValidateCommitForm(repository=self.repository, request=self.request, data={ 'commit_id': 'r1', 'parent_id': 'r2', }, files={ 'diff': self.diff, }) self.assertTrue(form.is_valid()) form.validate_diff()
def test_validate_diff(self): """Testing ValidateCommitForm.validate_diff""" self.spy_on(self.repository.get_file_exists, call_fake=lambda *args, **kwargs: True) form = ValidateCommitForm( repository=self.repository, request=self.request, data={ 'commit_id': 'r1', 'parent_id': 'r2', }, files={ 'diff': self.diff, }) self.assertTrue(form.is_valid()) form.validate_diff()
def test_validate_diff_parser_error(self): """Testing ValidateCommitForm.validate_diff for an invalid diff""" form = ValidateCommitForm( repository=self.repository, request=self.request, data={ 'commit_id': 'r1', 'parent_id': 'r0', }, files={ 'diff': SimpleUploadedFile('diff', b'asdf', content_type='text/x-patch'), }) self.assertTrue(form.is_valid()) with self.assertRaises(DiffParserError): form.validate_diff()
def test_validate_diff_subsequent_commit(self): """Testing ValidateCommitForm.validate_diff for a subsequent commit""" diff_content = ( b'diff --git a/foo b/foo\n' b'index %s..%s 1000644\n' b'--- a/foo\n' b'+++ b/foo\n' b'@@ -0,0 +1,2 @@\n' b'+This is not a new file.\n' % (b'a' * 40, b'b' * 40) ) diff = SimpleUploadedFile('diff', diff_content, content_type='text/x-patch') validation_info = base64.b64encode(json.dumps({ 'r1': { 'parent_id': 'r0', 'tree': { 'added': [{ 'filename': 'foo', 'revision': 'a' * 40, }], 'removed': [], 'modified': [], }, }, })) form = ValidateCommitForm( repository=self.repository, request=self.request, data={ 'commit_id': 'r2', 'parent_id': 'r1', 'validation_info': validation_info, }, files={ 'diff': diff, }) self.assertTrue(form.is_valid()) form.validate_diff()
def test_validate_diff_subsequent_commit(self): """Testing ValidateCommitForm.validate_diff for a subsequent commit""" diff_content = ( b'diff --git a/foo b/foo\n' b'index %s..%s 100644\n' b'--- a/foo\n' b'+++ b/foo\n' b'@@ -0,0 +1,2 @@\n' b'+This is not a new file.\n' % (b'a' * 40, b'b' * 40) ) diff = SimpleUploadedFile('diff', diff_content, content_type='text/x-patch') validation_info = self._base64_json({ 'r1': { 'parent_id': 'r0', 'tree': { 'added': [{ 'filename': 'foo', 'revision': 'a' * 40, }], 'removed': [], 'modified': [], }, }, }) form = ValidateCommitForm( repository=self.repository, request=self.request, data={ 'commit_id': 'r2', 'parent_id': 'r1', 'validation_info': validation_info, }, files={ 'diff': diff, }) self.assertTrue(form.is_valid()) form.validate_diff()
def test_validate_diff_too_big(self): """Testing ValidateCommitForm.validate_diff for a diff that is too large """ form = ValidateCommitForm(repository=self.repository, request=self.request, data={ 'commit_id': 'r1', 'parent_id': 'r0', }, files={ 'diff': self.diff, }) self.assertTrue(form.is_valid()) with self.assertRaises(DiffTooBigError): with self.siteconfig_settings({'diffviewer_max_diff_size': 1}, reload_settings=False): form.validate_diff()
def test_clean_validation_info_invalid_base64(self): """Testing ValidateCommitForm.clean_validation_info with non-base64-encoded data""" form = ValidateCommitForm( repository=self.repository, request=self.request, data={ 'commit_id': 'r2', 'parent_id': 'r1', 'validation_info': 'This is not base64!', }, files={ 'diff': self.diff, }) self.assertFalse(form.is_valid()) self.assertEqual(form.errors, { 'validation_info': [ 'Could not parse validation info "This is not base64!": ' 'Incorrect padding', ], })
def test_clean_validation_info_invalid_base64(self): """Testing ValidateCommitForm.clean_validation_info with non-base64-encoded data""" form = ValidateCommitForm(repository=self.repository, request=self.request, data={ 'commit_id': 'r2', 'parent_id': 'r1', 'validation_info': 'This is not base64!', }, files={ 'diff': self.diff, }) self.assertFalse(form.is_valid()) self.assertEqual( form.errors, { 'validation_info': [ 'Could not parse validation info "This is not base64!": ' 'Incorrect padding', ], })
def create(self, request, repository, commit_id, parent_id, base_commit_id=None, local_site_name=None, *args, **kwargs): """Validate a diff for a commit. This API has a similar signature to the :ref:`Draft DiffCommit resource <webapi2.0-draft-diff-commit-list-resource>` POST API, but instead of actually creating commits, it will return a result representing whether or not the included diff file parsed and validated correctly. This API must be called before posting to the :ref:`Draft DiffCommit resource <webapi2.0-draft-diff-commit-list-resource>` because the ``validation_info`` field returned by this resource is required for posting to that resource. """ local_site = self._get_local_site(local_site_name) try: q = Q(pk=int(repository)) except ValueError: q = (Q(path=repository) | Q(mirror_path=repository) | Q(name=repository)) repository_qs = ( Repository.objects .accessible(request.user, local_site=local_site) .filter(q) ) repository_count = len(repository_qs) if repository_count == 0: return INVALID_REPOSITORY, { 'repository': repository, } elif repository_count > 1: msg = ( 'Too many repositories matched "%s". Try specifying the ' 'repository by name instead.' % repository ) return INVALID_REPOSITORY.with_message(msg), { 'repository': repository, } repository = repository_qs.first() if not repository.scmtool_class.supports_history: return INVALID_ATTRIBUTE, { 'reason': ( 'The "%s" repository does not support review requests ' 'created with history.' % repository.name ), } form = ValidateCommitForm(repository=repository, request=request, data=request.POST, files=request.FILES) if not form.is_valid(): return INVALID_FORM_DATA, { 'fields': self._get_form_errors(form), } try: filediffs = form.validate_diff() except FileNotFoundError as e: return REPO_FILE_NOT_FOUND, { 'file': e.path, 'revision': six.text_type(e.revision), } except EmptyDiffError: return DIFF_EMPTY except DiffTooBigError as e: return DIFF_TOO_BIG, { 'reason': six.text_type(e), 'max_size': e.max_diff_size, } except DiffParserError as e: return DIFF_PARSE_ERROR, { 'reason': six.text_type(e), 'linenum': e.linenum, } except ShortSHA1Error as e: return REPO_FILE_NOT_FOUND, { 'reason': six.text_type(e), 'file': e.path, 'revision': six.text_type(e.revision), } except SCMError as e: return DIFF_PARSE_ERROR.with_message(six.text_type(e)) except Exception as e: logger.exception( 'Unexpected exception occurred while validating commit "%s" ' 'in repository "%s" (id %d) with base_commit_id="%s"', commit_id, repository.name, repository.pk, base_commit_id, request=request) return DIFF_PARSE_ERROR.with_message( 'Unexpected error while validating the diff: %s' % e) validation_info = update_validation_info( form.cleaned_data.get('validation_info', {}), commit_id, parent_id, filediffs) return 200, { self.item_result_key: { 'validation_info': serialize_validation_info( validation_info), } }
def create(self, request, repository, commit_id, parent_id, base_commit_id=None, local_site_name=None, *args, **kwargs): """Validate a diff for a commit. This API has a similar signature to the :ref:`Draft DiffCommit resource <webapi2.0-draft-diff-commit-list-resource>` POST API, but instead of actually creating commits, it will return a result representing whether or not the included diff file parsed and validated correctly. This API must be called before posting to the :ref:`Draft DiffCommit resource <webapi2.0-draft-diff-commit-list-resource>` because the ``validation_info`` field returned by this resource is required for posting to that resource. """ local_site = self._get_local_site(local_site_name) try: q = Q(pk=int(repository)) except ValueError: q = (Q(path=repository) | Q(mirror_path=repository) | Q(name=repository)) repository_qs = (Repository.objects.accessible( request.user, local_site=local_site).filter(q)) repository_count = len(repository_qs) if repository_count == 0: return INVALID_REPOSITORY, { 'repository': repository, } elif repository_count > 1: msg = ('Too many repositories matched "%s". Try specifying the ' 'repository by name instead.' % repository) return INVALID_REPOSITORY.with_message(msg), { 'repository': repository, } repository = repository_qs.first() if not repository.scmtool_class.supports_history: return INVALID_ATTRIBUTE, { 'reason': ('The "%s" repository does not support review requests ' 'created with history.' % repository.name), } form = ValidateCommitForm(repository=repository, request=request, data=request.POST, files=request.FILES) if not form.is_valid(): return INVALID_FORM_DATA, { 'fields': self._get_form_errors(form), } try: filediffs = form.validate_diff() except FileNotFoundError as e: return REPO_FILE_NOT_FOUND, { 'file': e.path, 'revision': six.text_type(e.revision), } except EmptyDiffError: return DIFF_EMPTY except DiffTooBigError as e: return DIFF_TOO_BIG, { 'reason': six.text_type(e), 'max_size': e.max_diff_size, } except DiffParserError as e: return DIFF_PARSE_ERROR, { 'reason': six.text_type(e), 'linenum': e.linenum, } except ShortSHA1Error as e: return REPO_FILE_NOT_FOUND, { 'reason': six.text_type(e), 'file': e.path, 'revision': six.text_type(e.revision), } except SCMError as e: return DIFF_PARSE_ERROR.with_message(six.text_type(e)) except Exception as e: logger.exception( 'Unexpected exception occurred while validating commit "%s" ' 'in repository "%s" (id %d) with base_commit_id="%s"', commit_id, repository.name, repository.pk, base_commit_id, request=request) return DIFF_PARSE_ERROR.with_message( 'Unexpected error while validating the diff: %s' % e) validation_info = update_validation_info( form.cleaned_data.get('validation_info', {}), commit_id, parent_id, filediffs) return 200, { self.item_result_key: { 'validation_info': serialize_validation_info(validation_info), } }