addons-server/apps/devhub/tests/test_views_validation.py

665 строки
26 KiB
Python

# -*- coding: utf8 -*-
import json
import os
import shutil
import sys
import traceback
from django.conf import settings
import mock
from nose.plugins.attrib import attr
from nose.tools import eq_
from pyquery import PyQuery as pq
import waffle
import amo
import amo.tests
from addons.models import Addon
from amo.tests import assert_no_validation_errors
from amo.tests.test_helpers import get_image_path
from amo.urlresolvers import reverse
from applications.models import AppVersion, Application
from files.models import File, FileUpload, FileValidation
from files.tests.test_models import UploadTest as BaseUploadTest
from files.utils import parse_addon
from users.models import UserProfile
from zadmin.models import ValidationResult
class TestUploadValidation(BaseUploadTest):
fixtures = ['base/apps', 'base/users',
'devhub/invalid-id-uploaded-xpi.json']
def setUp(self):
super(TestUploadValidation, self).setUp()
assert self.client.login(username='regular@mozilla.com',
password='password')
def test_no_html_in_messages(self):
upload = FileUpload.objects.get(name='invalid-id-20101206.xpi')
r = self.client.get(reverse('devhub.upload_detail',
args=[upload.uuid, 'json']))
eq_(r.status_code, 200)
data = json.loads(r.content)
msg = data['validation']['messages'][0]
eq_(msg['message'], 'The value of <em:id> is invalid.')
eq_(sorted(msg['context']),
[[u'<foo/>'], u'<em:description>...'])
def test_date_on_upload(self):
upload = FileUpload.objects.get(name='invalid-id-20101206.xpi')
r = self.client.get(reverse('devhub.upload_detail',
args=[upload.uuid]))
eq_(r.status_code, 200)
doc = pq(r.content)
eq_(doc('td').text(), 'December 6, 2010')
class TestUploadErrors(BaseUploadTest):
fixtures = ('base/apps', 'base/addon_3615', 'base/users')
def setUp(self):
super(TestUploadErrors, self).setUp()
self.client.login(username='regular@mozilla.com',
password='password')
@mock.patch.object(waffle, 'flag_is_active')
def test_dupe_uuid(self, flag_is_active):
flag_is_active.return_value = True
addon = Addon.objects.get(pk=3615)
d = parse_addon(self.get_upload('extension.xpi'))
addon.update(guid=d['guid'])
dupe_xpi = self.get_upload('extension.xpi')
res = self.client.get(reverse('devhub.upload_detail',
args=[dupe_xpi.uuid, 'json']))
eq_(res.status_code, 400)
data = json.loads(res.content)
eq_(data['validation']['messages'],
[{'tier': 1, 'message': 'Duplicate UUID found.',
'type': 'error'}])
eq_(data['validation']['ending_tier'], 1)
class TestFileValidation(amo.tests.TestCase):
fixtures = ['base/apps', 'base/users', 'base/platforms',
'devhub/addon-validation-1']
def setUp(self):
assert self.client.login(username='del@icio.us', password='password')
self.user = UserProfile.objects.get(email='del@icio.us')
self.file_validation = FileValidation.objects.get(pk=1)
self.file = self.file_validation.file
self.addon = self.file.version.addon
args = [self.addon.slug, self.file.id]
self.url = reverse('devhub.file_validation', args=args)
self.json_url = reverse('devhub.json_file_validation', args=args)
def test_version_list(self):
r = self.client.get(self.addon.get_dev_url('versions'))
eq_(r.status_code, 200)
a = pq(r.content)('td.file-validation a')
eq_(a.text(), '0 errors, 0 warnings')
eq_(a.attr('href'), self.url)
def test_results_page(self):
r = self.client.get(self.url, follow=True)
eq_(r.status_code, 200)
eq_(r.context['addon'], self.addon)
doc = pq(r.content)
assert not doc('#site-nav').hasClass('app-nav'), (
'Expected add-ons devhub nav')
eq_(doc('header h2').text(),
u'Validation Results for searchaddon11102010-20101217.xml')
eq_(doc('#addon-validator-suite').attr('data-validateurl'),
self.json_url)
def test_only_dev_can_see_results(self):
self.client.logout()
assert self.client.login(username='regular@mozilla.com',
password='password')
eq_(self.client.head(self.url, follow=True).status_code, 403)
def test_only_dev_can_see_json_results(self):
self.client.logout()
assert self.client.login(username='regular@mozilla.com',
password='password')
eq_(self.client.head(self.json_url, follow=True).status_code, 403)
def test_editor_can_see_results(self):
self.client.logout()
assert self.client.login(username='editor@mozilla.com',
password='password')
eq_(self.client.head(self.url, follow=True).status_code, 200)
def test_editor_can_see_json_results(self):
self.client.logout()
assert self.client.login(username='editor@mozilla.com',
password='password')
eq_(self.client.head(self.json_url, follow=True).status_code, 200)
def test_no_html_in_messages(self):
r = self.client.post(self.json_url, follow=True)
eq_(r.status_code, 200)
data = json.loads(r.content)
msg = data['validation']['messages'][0]
eq_(msg['message'], 'The value of <em:id> is invalid.')
eq_(sorted(msg['context']),
[[u'<foo/>'], u'<em:description>...'])
@mock.patch('files.models.File.has_been_validated')
def test_json_results_post(self, has_been_validated):
has_been_validated.__ne__ = mock.Mock()
has_been_validated.__ne__.return_value = True
eq_(self.client.post(self.json_url).status_code, 200)
has_been_validated.__ne__.return_value = False
eq_(self.client.post(self.json_url).status_code, 200)
@mock.patch('files.models.File.has_been_validated')
def test_json_results_get(self, has_been_validated):
has_been_validated.__eq__ = mock.Mock()
has_been_validated.__eq__.return_value = True
eq_(self.client.get(self.json_url).status_code, 200)
has_been_validated.__eq__.return_value = False
eq_(self.client.get(self.json_url).status_code, 405)
class TestValidateAddon(amo.tests.TestCase):
fixtures = ['base/users']
def setUp(self):
super(TestValidateAddon, self).setUp()
assert self.client.login(username='regular@mozilla.com',
password='password')
def test_login_required(self):
self.client.logout()
r = self.client.get(reverse('devhub.validate_addon'))
eq_(r.status_code, 302)
def test_context(self):
r = self.client.get(reverse('devhub.validate_addon'))
eq_(r.status_code, 200)
doc = pq(r.content)
eq_(doc('#upload-addon').attr('data-upload-url'),
reverse('devhub.standalone_upload'))
class TestValidateFile(BaseUploadTest):
fixtures = ['base/apps', 'base/users', 'base/addon_3615',
'devhub/addon-file-100456', 'base/platforms']
def setUp(self):
super(TestValidateFile, self).setUp()
assert self.client.login(username='del@icio.us', password='password')
self.user = UserProfile.objects.get(email='del@icio.us')
self.file = File.objects.get(pk=100456)
# Move the file into place as if it were a real file
self.file_dir = os.path.dirname(self.file.file_path)
os.makedirs(self.file_dir)
shutil.copyfile(self.file_path('invalid-id-20101206.xpi'),
self.file.file_path)
self.addon = self.file.version.addon
def tearDown(self):
super(TestValidateFile, self).tearDown()
if os.path.exists(self.file_dir):
shutil.rmtree(self.file_dir)
@attr('validator')
def test_lazy_validate(self):
r = self.client.post(reverse('devhub.json_file_validation',
args=[self.addon.slug, self.file.id]),
follow=True)
eq_(r.status_code, 200)
data = json.loads(r.content)
assert_no_validation_errors(data)
msg = data['validation']['messages'][0]
eq_(msg['message'], 'The value of <em:id> is invalid.')
def test_time(self):
r = self.client.post(reverse('devhub.file_validation',
args=[self.addon.slug, self.file.id]),
follow=True)
doc = pq(r.content)
assert doc('time').text()
@mock.patch.object(settings, 'EXPOSE_VALIDATOR_TRACEBACKS', False)
@mock.patch('devhub.tasks.run_validator')
def test_validator_errors(self, v):
v.side_effect = ValueError('catastrophic failure in amo-validator')
r = self.client.post(reverse('devhub.json_file_validation',
args=[self.addon.slug, self.file.id]),
follow=True)
eq_(r.status_code, 200)
data = json.loads(r.content)
eq_(data['validation'], '')
eq_(data['error'].strip(),
'ValueError: catastrophic failure in amo-validator')
@mock.patch('devhub.tasks.run_validator')
def test_validator_sets_binary_flag_for_extensions(self, v):
v.return_value = json.dumps({
"errors": 0,
"success": True,
"warnings": 0,
"notices": 0,
"message_tree": {},
"messages": [],
"metadata": {
"contains_binary_extension": True,
"version": "1.0",
"name": "gK0Bes Bot",
"id": "gkobes@gkobes"
}
})
eq_(self.addon.binary, False)
r = self.client.post(reverse('devhub.json_file_validation',
args=[self.addon.slug, self.file.id]),
follow=True)
eq_(r.status_code, 200)
data = json.loads(r.content)
assert_no_validation_errors(data)
addon = Addon.objects.get(pk=self.addon.id)
eq_(addon.binary, True)
@mock.patch('devhub.tasks.run_validator')
def test_ending_tier_is_preserved(self, v):
v.return_value = json.dumps({
"errors": 0,
"success": True,
"warnings": 0,
"notices": 0,
"message_tree": {},
"messages": [],
"ending_tier": 5,
"metadata": {
"contains_binary_extension": True,
"version": "1.0",
"name": "gK0Bes Bot",
"id": "gkobes@gkobes"
}
})
r = self.client.post(reverse('devhub.json_file_validation',
args=[self.addon.slug, self.file.id]),
follow=True)
eq_(r.status_code, 200)
data = json.loads(r.content)
eq_(data['validation']['ending_tier'], 5)
@mock.patch('devhub.tasks.run_validator')
def test_validator_sets_binary_flag_for_content(self, v):
v.return_value = json.dumps({
"errors": 0,
"success": True,
"warnings": 0,
"notices": 0,
"message_tree": {},
"messages": [],
"metadata": {
"contains_binary_content": True,
"version": "1.0",
"name": "gK0Bes Bot",
"id": "gkobes@gkobes"
}
})
eq_(self.addon.binary, False)
r = self.client.post(reverse('devhub.json_file_validation',
args=[self.addon.slug, self.file.id]),
follow=True)
eq_(r.status_code, 200)
data = json.loads(r.content)
assert_no_validation_errors(data)
addon = Addon.objects.get(pk=self.addon.id)
eq_(addon.binary, True)
@mock.patch('devhub.tasks.run_validator')
def test_linkify_validation_messages(self, v):
v.return_value = json.dumps({
"errors": 0,
"success": True,
"warnings": 1,
"notices": 0,
"message_tree": {},
"messages": [{
"context": ["<code>", None],
"description": [
"Something something, see https://bugzilla.mozilla.org/"],
"column": 0,
"line": 1,
"file": "chrome/content/down.html",
"tier": 2,
"message": "Some warning",
"type": "warning",
"id": [],
"uid": "bb9948b604b111e09dfdc42c0301fe38"
}],
"metadata": {}
})
r = self.client.post(reverse('devhub.json_file_validation',
args=[self.addon.slug, self.file.id]),
follow=True)
eq_(r.status_code, 200)
data = json.loads(r.content)
assert_no_validation_errors(data)
doc = pq(data['validation']['messages'][0]['description'][0])
eq_(doc('a').text(), 'https://bugzilla.mozilla.org/')
@mock.patch.object(settings, 'EXPOSE_VALIDATOR_TRACEBACKS', False)
@mock.patch('devhub.tasks.run_validator')
def test_hide_validation_traceback(self, run_validator):
run_validator.side_effect = RuntimeError('simulated task error')
r = self.client.post(reverse('devhub.json_file_validation',
args=[self.addon.slug, self.file.id]),
follow=True)
eq_(r.status_code, 200)
data = json.loads(r.content)
eq_(data['validation'], '')
eq_(data['error'], 'RuntimeError: simulated task error')
@mock.patch.object(waffle, 'flag_is_active')
@mock.patch('devhub.tasks.run_validator')
def test_rdf_parse_errors_are_ignored(self, run_validator,
flag_is_active):
run_validator.return_value = json.dumps({
"errors": 0,
"success": True,
"warnings": 0,
"notices": 0,
"message_tree": {},
"messages": [],
"metadata": {}
})
flag_is_active.return_value = True
addon = Addon.objects.get(pk=3615)
xpi = self.get_upload('extension.xpi')
d = parse_addon(xpi.path)
# Set up a duplicate upload:
addon.update(guid=d['guid'])
res = self.client.get(reverse('devhub.validate_addon'))
doc = pq(res.content)
upload_url = doc('#upload-addon').attr('data-upload-url')
with open(xpi.path, 'rb') as f:
# Simulate JS file upload
res = self.client.post(upload_url, {'upload': f}, follow=True)
data = json.loads(res.content)
# Simulate JS result polling:
res = self.client.get(data['url'])
data = json.loads(res.content)
# Make sure we don't see a dupe UUID error:
eq_(data['validation']['messages'], [])
# Simulate JS result polling on detail page:
res = self.client.get(data['full_report_url'], follow=True)
res = self.client.get(res.context['validate_url'], follow=True)
data = json.loads(res.content)
# Again, make sure we don't see a dupe UUID error:
eq_(data['validation']['messages'], [])
class TestCompatibilityResults(amo.tests.TestCase):
fixtures = ['base/users', 'devhub/addon-compat-results']
def setUp(self):
super(TestCompatibilityResults, self).setUp()
assert self.client.login(username='editor@mozilla.com',
password='password')
self.addon = Addon.objects.get(slug='addon-compat-results')
self.result = ValidationResult.objects.get(
file__version__addon=self.addon)
self.job = self.result.validation_job
def validate(self, expected_status=200):
r = self.client.post(reverse('devhub.json_bulk_compat_result',
args=[self.addon.slug, self.result.id]),
follow=True)
eq_(r.status_code, expected_status)
return json.loads(r.content)
def test_login_protected(self):
self.client.logout()
r = self.client.get(reverse('devhub.bulk_compat_result',
args=[self.addon.slug, self.result.id]))
eq_(r.status_code, 302)
r = self.client.post(reverse('devhub.json_bulk_compat_result',
args=[self.addon.slug, self.result.id]))
eq_(r.status_code, 302)
def test_target_version(self):
r = self.client.get(reverse('devhub.bulk_compat_result',
args=[self.addon.slug, self.result.id]))
eq_(r.status_code, 200)
doc = pq(r.content)
ver = json.loads(doc('.results').attr('data-target-version'))
assert amo.FIREFOX.guid in ver, ('Unexpected: %s' % ver)
eq_(ver[amo.FIREFOX.guid], self.job.target_version.version)
def test_app_trans(self):
r = self.client.get(reverse('devhub.bulk_compat_result',
args=[self.addon.slug, self.result.id]))
eq_(r.status_code, 200)
doc = pq(r.content)
trans = json.loads(doc('.results').attr('data-app-trans'))
for app in amo.APPS.values():
eq_(trans[app.guid], app.pretty)
def test_app_version_change_links(self):
r = self.client.get(reverse('devhub.bulk_compat_result',
args=[self.addon.slug, self.result.id]))
eq_(r.status_code, 200)
doc = pq(r.content)
trans = json.loads(doc('.results').attr('data-version-change-links'))
eq_(trans['%s 4.0.*' % amo.FIREFOX.guid],
'https://developer.mozilla.org/en/Firefox_4_for_developers')
def test_validation_success(self):
data = self.validate()
eq_(data['validation']['messages'][3]['for_appversions'],
{'{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': ['4.0b3']})
def test_time(self):
r = self.client.post(reverse('devhub.bulk_compat_result',
args=[self.addon.slug, self.result.id]),
follow=True)
eq_(r.status_code, 200)
doc = pq(r.content)
assert doc('time').text()
eq_(doc('table tr td:eq(1)').text(), 'Firefox 4.0.*')
@mock.patch.object(settings, 'EXPOSE_VALIDATOR_TRACEBACKS', True)
def test_validation_error(self):
try:
raise RuntimeError('simulated task error')
except:
error = ''.join(traceback.format_exception(*sys.exc_info()))
self.result.update(validation='', task_error=error)
data = self.validate()
eq_(data['validation'], '')
eq_(data['error'], error)
@mock.patch.object(settings, 'EXPOSE_VALIDATOR_TRACEBACKS', False)
def test_hide_validation_traceback(self):
try:
raise RuntimeError('simulated task error')
except:
error = ''.join(traceback.format_exception(*sys.exc_info()))
self.result.update(validation='', task_error=error)
data = self.validate()
eq_(data['validation'], '')
eq_(data['error'], 'RuntimeError: simulated task error')
class TestUploadCompatCheck(BaseUploadTest):
fixtures = ['base/apps', 'base/appversions', 'base/addon_3615']
compatibility_result = json.dumps({
"errors": 0,
"success": True,
"warnings": 0,
"notices": 0,
"compatibility_summary": {"notices": 0,
"errors": 0,
"warnings": 1},
"message_tree": {},
"messages": [],
"metadata": {}
})
def setUp(self):
super(TestUploadCompatCheck, self).setUp()
assert self.client.login(username='del@icio.us', password='password')
self.app = Application.objects.get(pk=amo.FIREFOX.id)
self.appver = AppVersion.objects.get(application=self.app,
version='3.7a1pre')
self.upload_url = reverse('devhub.standalone_upload')
def poll_upload_status_url(self, upload_uuid):
return reverse('devhub.standalone_upload_detail', args=[upload_uuid])
def fake_xpi(self, filename=None):
"""Any useless file that has a name property (for Django)."""
if not filename:
filename = get_image_path('non-animated.gif')
return open(filename, 'rb')
def upload(self, filename=None):
with self.fake_xpi(filename=filename) as f:
# Simulate how JS posts data w/ app/version from the form.
res = self.client.post(self.upload_url,
{'upload': f,
'app_id': self.app.pk,
'version_id': self.appver.pk},
follow=True)
return json.loads(res.content)
def test_compat_form(self):
res = self.client.get(reverse('devhub.check_addon_compatibility'))
eq_(res.status_code, 200)
doc = pq(res.content)
options = doc('#id_application option')
expected = [(str(a.id), unicode(a.pretty)) for a in amo.APP_USAGE]
for idx, element in enumerate(options):
e = pq(element)
val, text = expected[idx]
eq_(e.val(), val)
eq_(e.text(), text)
eq_(doc('#upload-addon').attr('data-upload-url'), self.upload_url)
# TODO(Kumar) actually check the form here after bug 671587
@mock.patch('devhub.tasks.run_validator')
def test_js_upload_validates_compatibility(self, run_validator):
run_validator.return_value = '' # Empty to simulate unfinished task.
data = self.upload()
kw = run_validator.call_args[1]
eq_(kw['for_appversions'], {self.app.guid: [self.appver.version]})
eq_(kw['overrides'],
{'targetapp_minVersion': {self.app.guid: self.appver.version},
'targetapp_maxVersion': {self.app.guid: self.appver.version}})
eq_(data['url'], self.poll_upload_status_url(data['upload']))
@mock.patch('devhub.tasks.run_validator')
def test_js_poll_upload_status(self, run_validator):
run_validator.return_value = self.compatibility_result
data = self.upload()
url = self.poll_upload_status_url(data['upload'])
res = self.client.get(url)
data = json.loads(res.content)
if data['validation'] and data['validation']['messages']:
raise AssertionError('Unexpected validation errors: %s'
% data['validation']['messages'])
@mock.patch('devhub.tasks.run_validator')
def test_compat_result_report(self, run_validator):
run_validator.return_value = self.compatibility_result
data = self.upload()
poll_url = self.poll_upload_status_url(data['upload'])
res = self.client.get(poll_url)
data = json.loads(res.content)
res = self.client.get(data['full_report_url'])
eq_(res.status_code, 200)
eq_(res.context['result_type'], 'compat')
doc = pq(res.content)
# Shows app/version on the results page.
eq_(doc('table tr td:eq(0)').text(), 'Firefox 3.7a1pre')
eq_(res.context['validate_url'], poll_url)
def test_compat_application_versions(self):
res = self.client.get(reverse('devhub.check_addon_compatibility'))
eq_(res.status_code, 200)
doc = pq(res.content)
data = {'application_id': amo.FIREFOX.id,
'csrfmiddlewaretoken':
doc('input[name=csrfmiddlewaretoken]').val()}
r = self.client.post(doc('#id_application').attr('data-url'),
data)
eq_(r.status_code, 200)
data = json.loads(r.content)
empty = True
for id, ver in data['choices']:
empty = False
eq_(AppVersion.objects.get(pk=id).version, ver)
assert not empty, "Unexpected: %r" % data
@mock.patch.object(waffle, 'flag_is_active')
@mock.patch('devhub.tasks.run_validator')
def test_rdf_parse_errors_are_ignored(self, run_validator,
flag_is_active):
run_validator.return_value = self.compatibility_result
flag_is_active.return_value = True
addon = Addon.objects.get(pk=3615)
dupe_xpi = self.get_upload('extension.xpi')
d = parse_addon(dupe_xpi)
# Set up a duplicate upload:
addon.update(guid=d['guid'])
data = self.upload(filename=dupe_xpi.path)
# Make sure we don't see a dupe UUID error:
eq_(data['validation']['messages'], [])
@mock.patch('devhub.tasks.run_validator')
def test_compat_summary_overrides(self, run_validator):
run_validator.return_value = json.dumps({
"success": True,
"errors": 0,
"warnings": 0,
"notices": 0,
"compatibility_summary": {"notices": 1,
"errors": 2,
"warnings": 3},
"message_tree": {},
"messages": [],
"metadata": {}
})
data = self.upload()
eq_(data['validation']['notices'], 1)
eq_(data['validation']['errors'], 2)
eq_(data['validation']['warnings'], 3)
res = self.client.get(self.poll_upload_status_url(data['upload']))
data = json.loads(res.content)
eq_(data['validation']['notices'], 1)
eq_(data['validation']['errors'], 2)
eq_(data['validation']['warnings'], 3)
@mock.patch('devhub.tasks.run_validator')
def test_compat_error_type_override(self, run_validator):
run_validator.return_value = json.dumps({
"success": True,
"errors": 0,
"warnings": 0,
"notices": 0,
"compatibility_summary": {"notices": 0,
"errors": 1,
"warnings": 0},
"message_tree": {},
"messages": [{"type": "warning",
"compatibility_type": "error",
"tier": 1},
{"type": "warning",
"compatibility_type": None,
"tier": 1}],
"metadata": {}
})
data = self.upload()
eq_(data['validation']['messages'][0]['type'], 'error')
eq_(data['validation']['messages'][1]['type'], 'warning')