addons-server/apps/addons/tests/test_models.py

2265 строки
82 KiB
Python

# -*- coding: utf-8 -*-
from contextlib import nested
import itertools
import json
import os
from datetime import datetime, timedelta
import tempfile
from urlparse import urlparse
from django import forms
from django.contrib.auth.models import AnonymousUser
from django.conf import settings
from django.core import mail
from django.db import IntegrityError
from django.utils import translation
from mock import patch, Mock
from nose.tools import eq_, assert_not_equal, raises
import waffle
import amo
import amo.tests
from amo import set_user
from amo.helpers import absolutify
from amo.signals import _connect, _disconnect
from addons.models import (Addon, AddonCategory, AddonDependency,
AddonDeviceType, AddonRecommendation, AddonType,
AddonUpsell, AddonUser, AppSupport, BlacklistedGuid,
Category, Charity, CompatOverride,
CompatOverrideRange, FrozenAddon,
IncompatibleVersions, Persona, Preview)
from addons.search import setup_mapping
from applications.models import Application, AppVersion
from compat.models import CompatReport
from constants.applications import DEVICE_TYPES
from devhub.models import ActivityLog, AddonLog, RssKey, SubmitStep
from files.models import File, Platform
from files.tests.test_models import TestLanguagePack, UploadTest
from market.models import AddonPaymentData, AddonPremium, Price
from reviews.models import Review
from translations.models import TranslationSequence, Translation
from users.models import UserProfile
from versions.models import ApplicationsVersions, Version
from versions.compare import version_int
from mkt.webapps.models import Webapp
class TestAddonManager(amo.tests.TestCase):
fixtures = ['addons/featured', 'addons/test_manager', 'base/collections',
'base/featured', 'bandwagon/featured_collections',
'base/addon_5299_gcal']
def setUp(self):
set_user(None)
def test_featured(self):
eq_(Addon.objects.featured(amo.FIREFOX).count(), 3)
def test_listed(self):
Addon.objects.filter(id=5299).update(disabled_by_user=True)
q = Addon.objects.listed(amo.FIREFOX, amo.STATUS_PUBLIC)
eq_(len(q.all()), 4)
addon = q[0]
eq_(addon.id, 2464)
# Disabling hides it.
addon.disabled_by_user = True
addon.save()
# Should be 3 now, since the one is now disabled.
eq_(q.count(), 3)
# If we search for public or unreviewed we find it.
addon.disabled_by_user = False
addon.status = amo.STATUS_UNREVIEWED
addon.save()
eq_(q.count(), 3)
eq_(Addon.objects.listed(amo.FIREFOX, amo.STATUS_PUBLIC,
amo.STATUS_UNREVIEWED).count(), 4)
# Can't find it without a file.
addon.versions.get().files.get().delete()
eq_(q.count(), 3)
def test_public(self):
public = Addon.objects.public()
for a in public:
assert_not_equal(
a.id, 3, 'public() must not return unreviewed add-ons')
def test_reviewed(self):
for a in Addon.objects.reviewed():
assert a.status in amo.REVIEWED_STATUSES, (a.id, a.status)
def test_unreviewed(self):
"""
Tests for unreviewed addons.
"""
exp = Addon.objects.unreviewed()
for addon in exp:
assert addon.status in amo.UNREVIEWED_STATUSES, (
"unreviewed() must return unreviewed addons.")
def test_valid(self):
addon = Addon.objects.get(pk=5299)
addon.update(disabled_by_user=True)
objs = Addon.objects.valid()
for addon in objs:
assert addon.status in amo.LISTED_STATUSES
assert not addon.disabled_by_user
def test_valid_disabled_by_user(self):
before = Addon.objects.valid_and_disabled().count()
addon = Addon.objects.get(pk=5299)
addon.update(disabled_by_user=True)
eq_(Addon.objects.valid_and_disabled().count(), before)
def test_valid_disabled_by_admin(self):
before = Addon.objects.valid_and_disabled().count()
addon = Addon.objects.get(pk=5299)
addon.update(status=amo.STATUS_DISABLED)
eq_(Addon.objects.valid_and_disabled().count(), before)
def test_invalid_deleted(self):
before = Addon.objects.valid_and_disabled().count()
addon = Addon.objects.get(pk=5299)
addon.update(status=amo.STATUS_DELETED)
eq_(Addon.objects.valid_and_disabled().count(), before - 1)
def test_top_free_public(self):
addons = list(Addon.objects.listed(amo.FIREFOX))
eq_(list(Addon.objects.top_free(amo.FIREFOX)),
sorted(addons, key=lambda x: x.weekly_downloads, reverse=True))
eq_(list(Addon.objects.top_free(amo.THUNDERBIRD)), [])
def test_top_free_all(self):
addons = list(Addon.objects.filter(appsupport__app=amo.FIREFOX.id)
.exclude(premium_type__in=amo.ADDON_PREMIUMS)
.exclude(addonpremium__price__price__isnull=False))
eq_(list(Addon.objects.top_free(amo.FIREFOX, listed=False)),
sorted(addons, key=lambda x: x.weekly_downloads, reverse=True))
eq_(list(Addon.objects.top_free(amo.THUNDERBIRD, listed=False)), [])
def make_paid(self, addons, type=amo.ADDON_PREMIUM):
price = Price.objects.create(price='1.00')
for addon in addons:
addon.update(premium_type=type)
AddonPremium.objects.create(addon=addon, price=price)
def test_top_paid_public(self):
addons = list(Addon.objects.listed(amo.FIREFOX)[:3])
self.make_paid(addons)
eq_(list(Addon.objects.top_paid(amo.FIREFOX)),
sorted(addons, key=lambda x: x.weekly_downloads, reverse=True))
eq_(list(Addon.objects.top_paid(amo.THUNDERBIRD)), [])
def test_top_paid_all(self):
addons = list(Addon.objects.listed(amo.FIREFOX)[:3])
for addon in addons:
addon.update(status=amo.STATUS_LITE)
self.make_paid(addons)
eq_(list(Addon.objects.top_paid(amo.FIREFOX, listed=False)),
sorted(addons, key=lambda x: x.weekly_downloads, reverse=True))
eq_(list(Addon.objects.top_paid(amo.THUNDERBIRD, listed=False)), [])
def test_top_paid_in_app_all(self):
addons = list(Addon.objects.listed(amo.FIREFOX)[:3])
for addon in addons:
addon.update(status=amo.STATUS_LITE)
self.make_paid(addons, amo.ADDON_PREMIUM_INAPP)
eq_(list(Addon.objects.top_paid(amo.FIREFOX, listed=False)),
sorted(addons, key=lambda x: x.weekly_downloads, reverse=True))
eq_(list(Addon.objects.top_paid(amo.THUNDERBIRD, listed=False)), [])
def test_new_featured(self):
f = Addon.objects.featured(amo.FIREFOX)
eq_(f.count(), 3)
eq_(sorted(x.id for x in f),
[2464, 7661, 15679])
f = Addon.objects.featured(amo.THUNDERBIRD)
assert not f.exists()
class TestNewAddonVsWebapp(amo.tests.TestCase):
def test_addon_from_kwargs(self):
a = Addon(type=amo.ADDON_EXTENSION)
assert isinstance(a, Addon)
def test_webapp_from_kwargs(self):
w = Addon(type=amo.ADDON_WEBAPP)
assert isinstance(w, Webapp)
def test_addon_from_db(self):
a = Addon.objects.create(type=amo.ADDON_EXTENSION)
assert isinstance(a, Addon)
assert isinstance(Addon.objects.get(id=a.id), Addon)
def test_webapp_from_db(self):
a = Addon.objects.create(type=amo.ADDON_WEBAPP)
assert isinstance(a, Webapp)
assert isinstance(Addon.objects.get(id=a.id), Webapp)
class TestAddonModels(amo.tests.TestCase):
fixtures = ['base/apps',
'base/collections',
'base/featured',
'base/users',
'base/addon_5299_gcal',
'base/addon_3615',
'base/addon_3723_listed',
'base/addon_6704_grapple.json',
'base/addon_4594_a9',
'base/addon_4664_twitterbar',
'base/thunderbird',
'addons/featured',
'addons/invalid_latest_version',
'addons/blacklisted',
'bandwagon/featured_collections']
def setUp(self):
TranslationSequence.objects.create(id=99243)
# TODO(andym): use Mock appropriately here.
self.old_version = amo.FIREFOX.latest_version
amo.FIREFOX.latest_version = '3.6.15'
def tearDown(self):
amo.FIREFOX.latest_version = self.old_version
def test_current_version(self):
"""
Tests that we get the current (latest public) version of an addon.
"""
a = Addon.objects.get(pk=3615)
eq_(a.current_version.id, 81551)
def test_current_version_listed(self):
a = Addon.objects.get(pk=3723)
eq_(a.current_version.id, 89774)
def test_current_version_listed_no_version(self):
Addon.objects.filter(pk=3723).update(_current_version=None)
Version.objects.filter(addon=3723).delete()
a = Addon.objects.get(pk=3723)
eq_(a.current_version, None)
def test_latest_version(self):
"""
Tests that we get the latest version of an addon.
"""
a = Addon.objects.get(pk=3615)
eq_(a.latest_version.id, Version.objects.filter(addon=a).latest().id)
def test_latest_version_no_version(self):
Addon.objects.filter(pk=3723).update(_current_version=None)
Version.objects.filter(addon=3723).delete()
a = Addon.objects.get(pk=3723)
eq_(a.latest_version, None)
def test_latest_version_ignore_beta(self):
a = Addon.objects.get(pk=3615)
v1 = Version.objects.create(addon=a, version='1.0')
File.objects.create(version=v1)
eq_(a.latest_version.id, v1.id)
v2 = Version.objects.create(addon=a, version='2.0beta')
File.objects.create(version=v2, status=amo.STATUS_BETA)
eq_(a.latest_version.id, v1.id) # Still should be f1
def test_current_beta_version(self):
a = Addon.objects.get(pk=5299)
eq_(a.current_beta_version.id, 50000)
def _delete(self):
"""Test deleting add-ons."""
a = Addon.objects.get(pk=3615)
a.name = u'é'
a.delete('bye')
eq_(len(mail.outbox), 1)
assert BlacklistedGuid.objects.filter(guid=a.guid)
def test_delete_hard(self):
deleted_count = Addon.with_deleted.count()
self._delete()
eq_(deleted_count, Addon.with_deleted.count() + 1)
def test_delete_soft(self):
waffle.models.Switch.objects.create(name='soft_delete', active=True)
deleted_count = Addon.with_deleted.count()
self._delete()
eq_(deleted_count, Addon.with_deleted.count())
addon = Addon.with_deleted.get(pk=3615)
eq_(addon.status, amo.STATUS_DELETED)
eq_(addon.slug, None)
eq_(addon.app_slug, None)
def _delete_url(self):
"""Test deleting addon has URL in the email."""
a = Addon.objects.get(pk=4594)
url = a.get_url_path()
a.delete('bye')
assert absolutify(url) in mail.outbox[0].body
def test_delete_url_hard(self):
count = Addon.with_deleted.count()
self._delete_url()
eq_(count, Addon.with_deleted.count() + 1)
def test_delete_url_soft(self):
waffle.models.Switch.objects.create(name='soft_delete', active=True)
count = Addon.with_deleted.count()
self._delete_url()
eq_(count, Addon.with_deleted.count())
def _delete_status_gone_wild(self):
"""
Test deleting add-ons where the higheststatus is zero, but there's a
non-zero status.
"""
a = Addon.objects.get(pk=3615)
a.status = amo.STATUS_UNREVIEWED
a.highest_status = 0
a.delete('bye')
eq_(len(mail.outbox), 1)
assert BlacklistedGuid.objects.filter(guid=a.guid)
def test_delete_status_gone_wild_hard(self):
count = Addon.objects.count()
self._delete_status_gone_wild()
eq_(count, Addon.with_deleted.count() + 1)
def test_delete_status_gone_wild_soft(self):
waffle.models.Switch.objects.create(name='soft_delete', active=True)
count = Addon.objects.count()
self._delete_status_gone_wild()
eq_(count, Addon.with_deleted.count())
def test_delete_incomplete(self):
"""Test deleting incomplete add-ons."""
count = Addon.with_deleted.count()
a = Addon.objects.get(pk=3615)
a.status = 0
a.highest_status = 0
a.save()
a.delete(None)
eq_(len(mail.outbox), 0)
assert not BlacklistedGuid.objects.filter(guid=a.guid)
eq_(Addon.with_deleted.count(), count - 1)
def test_delete_searchengine(self):
"""
Test deleting searchengines (which have no guids) should not barf up
the deletion machine.
"""
a = Addon.objects.get(pk=4594)
a.delete('bye')
eq_(len(mail.outbox), 1)
def test_incompatible_latest_apps(self):
a = Addon.objects.get(pk=3615)
eq_(a.incompatible_latest_apps(), [])
av = ApplicationsVersions.objects.get(pk=47881)
av.max = AppVersion.objects.get(pk=97) # Firefox 2.0
av.save()
a = Addon.objects.get(pk=3615)
eq_(a.incompatible_latest_apps(), [amo.FIREFOX])
# Check a search engine addon.
a = Addon.objects.get(pk=4594)
eq_(a.incompatible_latest_apps(), [])
def test_incompatible_asterix(self):
av = ApplicationsVersions.objects.get(pk=47881)
av.max = AppVersion.objects.create(application_id=amo.FIREFOX.id,
version_int=version_int('5.*'),
version='5.*')
av.save()
a = Addon.objects.get(pk=3615)
eq_(a.incompatible_latest_apps(), [])
def test_icon_url(self):
"""
Tests for various icons.
1. Test for an icon that exists.
2. Test for default THEME icon.
3. Test for default non-THEME icon.
"""
a = Addon.objects.get(pk=3615)
expected = (settings.ADDON_ICON_URL % (3, 3615, 32, 0)).rstrip('/0')
assert a.icon_url.startswith(expected)
a = Addon.objects.get(pk=6704)
a.icon_type = None
assert a.icon_url.endswith('/icons/default-theme.png'), (
"No match for %s" % a.icon_url)
a = Addon.objects.get(pk=3615)
a.icon_type = None
assert a.icon_url.endswith('icons/default-32.png')
def test_icon_url_default(self):
a = Addon.objects.get(pk=3615)
a.update(icon_type='')
default = 'icons/default-32.png'
eq_(a.icon_url.endswith(default), True)
eq_(a.get_icon_url(32).endswith(default), True)
eq_(a.get_icon_url(32, use_default=True).endswith(default), True)
eq_(a.get_icon_url(32, use_default=False), None)
def test_thumbnail_url(self):
"""
Test for the actual thumbnail URL if it should exist, or the no-preview
url.
"""
a = Addon.objects.get(pk=4664)
a.thumbnail_url.index('/previews/thumbs/20/20397.png?modified=')
a = Addon.objects.get(pk=5299)
assert a.thumbnail_url.endswith('/icons/no-preview.png'), (
"No match for %s" % a.thumbnail_url)
def test_is_unreviewed(self):
"""Test if add-on is unreviewed or not"""
# public add-on
a = Addon.objects.get(pk=3615)
assert not a.is_unreviewed(), 'public add-on: is_unreviewed=False'
# unreviewed add-on
a = Addon(status=amo.STATUS_UNREVIEWED)
assert a.is_unreviewed(), 'sandboxed add-on: is_unreviewed=True'
a.status = amo.STATUS_PENDING
assert a.is_unreviewed(), 'pending add-on: is_unreviewed=True'
def test_is_public(self):
# Public add-on.
a = Addon.objects.get(pk=3615)
assert a.is_public(), 'public add-on should not be is_pulic()'
# Public, disabled add-on.
a.disabled_by_user = True
assert not a.is_public(), (
'public, disabled add-on should not be is_public()')
# Lite add-on.
a.status = amo.STATUS_LITE
a.disabled_by_user = False
assert not a.is_public(), 'lite add-on should not be is_public()'
# Unreviewed add-on.
a.status = amo.STATUS_UNREVIEWED
assert not a.is_public(), 'unreviewed add-on should not be is_public()'
# Unreviewed, disabled add-on.
a.status = amo.STATUS_UNREVIEWED
a.disabled_by_user = True
assert not a.is_public(), (
'unreviewed, disabled add-on should not be is_public()')
def test_is_selfhosted(self):
"""Test if an add-on is listed or hosted"""
# hosted
a = Addon.objects.get(pk=3615)
assert not a.is_selfhosted(), 'hosted add-on => !is_selfhosted()'
# listed
a.status = amo.STATUS_LISTED
assert a.is_selfhosted(), 'listed add-on => is_selfhosted()'
def test_is_no_restart(self):
a = Addon.objects.get(pk=3615)
f = a.current_version.all_files[0]
eq_(f.no_restart, False)
eq_(a.is_no_restart(), False)
f.update(no_restart=True)
eq_(Addon.objects.get(pk=3615).is_no_restart(), True)
a.versions.all().delete()
a._current_version = None
eq_(a.is_no_restart(), False)
def test_is_featured(self):
"""Test if an add-on is globally featured"""
a = Addon.objects.get(pk=1003)
assert a.is_featured(amo.FIREFOX, 'en-US'), (
'globally featured add-on not recognized')
def test_has_full_profile(self):
"""Test if an add-on's developer profile is complete (public)."""
addon = lambda: Addon.objects.get(pk=3615)
assert not addon().has_full_profile()
a = addon()
a.the_reason = 'some reason'
a.save()
assert not addon().has_full_profile()
a.the_future = 'some future'
a.save()
assert addon().has_full_profile()
a.the_reason = ''
a.the_future = ''
a.save()
assert not addon().has_full_profile()
def test_has_profile(self):
"""Test if an add-on's developer profile is (partially or entirely)
completed.
"""
addon = lambda: Addon.objects.get(pk=3615)
assert not addon().has_profile()
a = addon()
a.the_reason = 'some reason'
a.save()
assert addon().has_profile()
a.the_future = 'some future'
a.save()
assert addon().has_profile()
a.the_reason = ''
a.the_future = ''
a.save()
assert not addon().has_profile()
def test_has_eula(self):
addon = lambda: Addon.objects.get(pk=3615)
assert addon().has_eula
a = addon()
a.eula = ''
a.save()
assert not addon().has_eula
a.eula = 'eula'
a.save()
assert addon().has_eula
def newlines_helper(self, string_before):
addon = Addon.objects.get(pk=3615)
addon.privacy_policy = string_before
addon.save()
return addon.privacy_policy.localized_string_clean
def test_newlines_normal(self):
before = ("Paragraph one.\n"
"This should be on the very next line.\n\n"
"Should be two nl's before this line.\n\n\n"
"Should be three nl's before this line.\n\n\n\n"
"Should be four nl's before this line.")
after = before # Nothing special; this shouldn't change.
eq_(self.newlines_helper(before), after)
def test_newlines_ul(self):
before = ("<ul>\n\n"
"<li>No nl's between the ul and the li.</li>\n\n"
"<li>No nl's between li's.\n\n"
"But there should be two before this line.</li>\n\n"
"</ul>")
after = ("<ul>"
"<li>No nl's between the ul and the li.</li>"
"<li>No nl's between li's.\n\n"
"But there should be two before this line.</li>"
"</ul>")
eq_(self.newlines_helper(before), after)
def test_newlines_ul_tight(self):
before = ("There should be one nl between this and the ul.\n"
"<ul><li>test</li><li>test</li></ul>\n"
"There should be no nl's above this line.")
after = ("There should be one nl between this and the ul.\n"
"<ul><li>test</li><li>test</li></ul>"
"There should be no nl's above this line.")
eq_(self.newlines_helper(before), after)
def test_newlines_ul_loose(self):
before = ("There should be two nl's between this and the ul.\n\n"
"<ul><li>test</li><li>test</li></ul>\n\n"
"There should be one nl above this line.")
after = ("There should be two nl's between this and the ul.\n\n"
"<ul><li>test</li><li>test</li></ul>\n"
"There should be one nl above this line.")
eq_(self.newlines_helper(before), after)
def test_newlines_blockquote_tight(self):
before = ("There should be one nl below this.\n"
"<blockquote>Hi</blockquote>\n"
"There should be no nl's above this.")
after = ("There should be one nl below this.\n"
"<blockquote>Hi</blockquote>"
"There should be no nl's above this.")
eq_(self.newlines_helper(before), after)
def test_newlines_blockquote_loose(self):
before = ("There should be two nls below this.\n\n"
"<blockquote>Hi</blockquote>\n\n"
"There should be one nl above this.")
after = ("There should be two nls below this.\n\n"
"<blockquote>Hi</blockquote>\n"
"There should be one nl above this.")
eq_(self.newlines_helper(before), after)
def test_newlines_inline(self):
before = ("If we end a paragraph w/ a <b>non-block-level tag</b>\n\n"
"<b>The newlines</b> should be kept")
after = before # Should stay the same
eq_(self.newlines_helper(before), after)
def test_newlines_code_inline(self):
before = ("Code tags aren't blocks.\n\n"
"<code>alert(test);</code>\n\n"
"See?")
after = before # Should stay the same
eq_(self.newlines_helper(before), after)
def test_newlines_li_newlines(self):
before = ("<ul><li>\nxx</li></ul>")
after = ("<ul><li>xx</li></ul>")
eq_(self.newlines_helper(before), after)
before = ("<ul><li>xx\n</li></ul>")
after = ("<ul><li>xx</li></ul>")
eq_(self.newlines_helper(before), after)
before = ("<ul><li>xx\nxx</li></ul>")
after = ("<ul><li>xx\nxx</li></ul>")
eq_(self.newlines_helper(before), after)
before = ("<ul><li></li></ul>")
after = ("<ul><li></li></ul>")
eq_(self.newlines_helper(before), after)
# All together now
before = ("<ul><li>\nxx</li> <li>xx\n</li> <li>xx\nxx</li> "
"<li></li>\n</ul>")
after = ("<ul><li>xx</li> <li>xx</li> <li>xx\nxx</li> "
"<li></li></ul>")
eq_(self.newlines_helper(before), after)
def test_newlines_empty_tag(self):
before = ("This is a <b></b> test!")
after = before
eq_(self.newlines_helper(before), after)
def test_newlines_empty_tag_nested(self):
before = ("This is a <b><i></i></b> test!")
after = before
eq_(self.newlines_helper(before), after)
def test_newlines_empty_tag_block_nested(self):
b = ("Test.\n\n<blockquote><ul><li></li></ul></blockquote>\ntest.")
a = ("Test.\n\n<blockquote><ul><li></li></ul></blockquote>test.")
eq_(self.newlines_helper(b), a)
def test_newlines_empty_tag_block_nested_spaced(self):
before = ("Test.\n\n<blockquote>\n\n<ul>\n\n<li>"
"</li>\n\n</ul>\n\n</blockquote>\ntest.")
after = ("Test.\n\n<blockquote><ul><li></li></ul></blockquote>test.")
eq_(self.newlines_helper(before), after)
def test_newlines_li_newlines_inline(self):
before = ("<ul><li>\n<b>test\ntest\n\ntest</b>\n</li>"
"<li>Test <b>test</b> test.</li></ul>")
after = ("<ul><li><b>test\ntest\n\ntest</b></li>"
"<li>Test <b>test</b> test.</li></ul>")
eq_(self.newlines_helper(before), after)
def test_newlines_li_all_inline(self):
before = ("Test with <b>no newlines</b> and <code>block level "
"stuff</code> to see what happens.")
after = before # Should stay the same
eq_(self.newlines_helper(before), after)
def test_newlines_spaced_blocks(self):
before = ("<blockquote>\n\n<ul>\n\n<li>\n\ntest\n\n</li>\n\n"
"</ul>\n\n</blockquote>")
after = "<blockquote><ul><li>test</li></ul></blockquote>"
eq_(self.newlines_helper(before), after)
def test_newlines_spaced_inline(self):
before = "Line.\n\n<b>\nThis line is bold.\n</b>\n\nThis isn't."
after = before
eq_(self.newlines_helper(before), after)
def test_newlines_nested_inline(self):
before = "<b>\nThis line is bold.\n\n<i>This is also italic</i></b>"
after = before
eq_(self.newlines_helper(before), after)
def test_newlines_xss_script(self):
before = "<script>\n\nalert('test');\n</script>"
after = "&lt;script&gt;\n\nalert('test');\n&lt;/script&gt;"
eq_(self.newlines_helper(before), after)
def test_newlines_xss_inline(self):
before = "<b onclick=\"alert('test');\">test</b>"
after = "<b>test</b>"
eq_(self.newlines_helper(before), after)
def test_newlines_attribute_link_doublequote(self):
before = '<a href="http://google.com">test</a>'
parsed = self.newlines_helper(before)
assert parsed.endswith('google.com" rel="nofollow">test</a>')
def test_newlines_attribute_singlequote(self):
before = "<abbr title='laugh out loud'>lol</abbr>"
after = '<abbr title="laugh out loud">lol</abbr>'
eq_(self.newlines_helper(before), after)
def test_newlines_attribute_doublequote(self):
before = '<abbr title="laugh out loud">lol</abbr>'
after = before
eq_(self.newlines_helper(before), after)
def test_newlines_attribute_nestedquotes_doublesingle(self):
before = '<abbr title="laugh \'out\' loud">lol</abbr>'
after = before
eq_(self.newlines_helper(before), after)
def test_newlines_attribute_nestedquotes_singledouble(self):
before = '<abbr title=\'laugh "out" loud\'>lol</abbr>'
after = before
eq_(self.newlines_helper(before), after)
def test_newlines_unclosed_b(self):
before = ("<b>test")
after = ("<b>test</b>")
eq_(self.newlines_helper(before), after)
def test_newlines_unclosed_b_wrapped(self):
before = ("This is a <b>test")
after = ("This is a <b>test</b>")
eq_(self.newlines_helper(before), after)
def test_newlines_unclosed_li(self):
before = ("<ul><li>test</ul>")
after = ("<ul><li>test</li></ul>")
eq_(self.newlines_helper(before), after)
def test_newlines_malformed_faketag(self):
before = "<madonna"
after = ""
eq_(self.newlines_helper(before), after)
def test_newlines_correct_faketag(self):
before = "<madonna>"
after = "&lt;madonna&gt;"
eq_(self.newlines_helper(before), after)
def test_newlines_malformed_tag(self):
before = "<strong"
after = ""
eq_(self.newlines_helper(before), after)
def test_newlines_malformed_faketag_surrounded(self):
before = "This is a <test of bleach"
after = 'This is a &lt;test of="" bleach=""&gt;'
# Output is ugly, but not much we can do. Bleach+html5lib is adamant
# this is a tag.
eq_(self.newlines_helper(before), after)
def test_newlines_malformed_tag_surrounded(self):
before = "This is a <strong of bleach"
after = "This is a <strong></strong>"
# Bleach interprets 'of' and 'bleach' as attributes, and strips them.
# Good? No. Any way around it? Not really.
eq_(self.newlines_helper(before), after)
def test_newlines_less_than(self):
before = "3 < 5"
after = "3 &lt; 5"
eq_(self.newlines_helper(before), after)
def test_newlines_less_than_tight(self):
before = "abc 3<5 def"
after = "abc 3&lt;5 def"
eq_(self.newlines_helper(before), after)
def test_app_categories(self):
addon = lambda: Addon.objects.get(pk=3615)
c22 = Category.objects.get(id=22)
c22.name = 'CCC'
c22.save()
c23 = Category.objects.get(id=23)
c23.name = 'BBB'
c23.save()
c24 = Category.objects.get(id=24)
c24.name = 'AAA'
c24.save()
cats = addon().all_categories
eq_(cats, [c22, c23, c24])
for cat in cats:
eq_(cat.application.id, amo.FIREFOX.id)
cats = [c24, c23, c22]
app_cats = [(amo.FIREFOX, cats)]
eq_(addon().app_categories, app_cats)
tb = Application.objects.get(id=amo.THUNDERBIRD.id)
c = Category(application=tb, name='XXX', type=addon().type, count=1,
weight=1)
c.save()
AddonCategory.objects.create(addon=addon(), category=c)
c24.save() # Clear the app_categories cache.
app_cats += [(amo.THUNDERBIRD, [c])]
eq_(addon().app_categories, app_cats)
def test_app_categories_sunbird(self):
get_addon = lambda: Addon.objects.get(pk=3615)
addon = get_addon()
# This add-on is already associated with three Firefox categories.
cats = sorted(addon.categories.all(), key=lambda x: x.name)
eq_(addon.app_categories, [(amo.FIREFOX, cats)])
# Associate this add-on with a Sunbird category.
a = Application.objects.create(id=amo.SUNBIRD.id)
c2 = Category.objects.create(application=a, type=amo.ADDON_EXTENSION,
name='Sunny D')
AddonCategory.objects.create(addon=addon, category=c2)
# Sunbird category should be excluded.
eq_(get_addon().app_categories, [(amo.FIREFOX, cats)])
def test_review_replies(self):
"""
Make sure that developer replies are not returned as if they were
original reviews.
"""
addon = Addon.objects.get(id=3615)
u = UserProfile.objects.get(pk=999)
version = addon.current_version
new_review = Review(version=version, user=u, rating=2, body='hello',
addon=addon)
new_review.save()
new_reply = Review(version=version, user=addon.authors.all()[0],
addon=addon, reply_to=new_review,
rating=2, body='my reply')
new_reply.save()
review_list = [r.pk for r in addon.reviews]
assert new_review.pk in review_list, (
'Original review must show up in review list.')
assert new_reply.pk not in review_list, (
'Developer reply must not show up in review list.')
def test_takes_contributions(self):
a = Addon(status=amo.STATUS_PUBLIC, wants_contributions=True,
paypal_id='$$')
assert a.takes_contributions
a.status = amo.STATUS_UNREVIEWED
assert not a.takes_contributions
a.status = amo.STATUS_PUBLIC
a.wants_contributions = False
assert not a.takes_contributions
a.wants_contributions = True
a.paypal_id = None
assert not a.takes_contributions
a.charity_id = 12
assert a.takes_contributions
def test_show_beta(self):
# Addon.current_beta_version will be empty, so show_beta is False.
a = Addon(status=amo.STATUS_PUBLIC)
assert not a.show_beta
@patch('addons.models.Addon.current_beta_version')
def test_show_beta_with_beta_version(self, beta_mock):
beta_mock.return_value = object()
# Fake current_beta_version to return something truthy.
a = Addon(status=amo.STATUS_PUBLIC)
assert a.show_beta
# We have a beta version but status has to be public.
a.status = amo.STATUS_UNREVIEWED
assert not a.show_beta
def test_update_logs(self):
addon = Addon.objects.get(id=3615)
set_user(UserProfile.objects.all()[0])
addon.versions.all().delete()
entries = ActivityLog.objects.all()
eq_(entries[0].action, amo.LOG.CHANGE_STATUS.id)
def test_can_request_review_waiting_period(self):
now = datetime.now()
a = Addon.objects.create(type=1)
v = Version.objects.create(addon=a)
# The first LITE version is only 5 days old, no dice.
first_f = File.objects.create(status=amo.STATUS_LITE, version=v)
first_f.update(datestatuschanged=now - timedelta(days=5),
created=now - timedelta(days=20))
# TODO(andym): can this go in Addon.objects.create? bug 618444
a.update(status=amo.STATUS_LITE)
eq_(a.can_request_review(), ())
# Now the first LITE is > 10 days old, change can happen.
first_f.update(datestatuschanged=now - timedelta(days=11))
# Add a second file, to be sure that we test the date
# of the first created file.
second_f = File.objects.create(status=amo.STATUS_LITE, version=v)
second_f.update(datestatuschanged=now - timedelta(days=5))
eq_(a.status, amo.STATUS_LITE)
eq_(a.can_request_review(), (amo.STATUS_PUBLIC,))
def test_days_until_full_nomination(self):
# Normalize to 12am for reliable day subtraction:
now = datetime.now().date()
a = Addon.objects.create(type=1)
v = Version.objects.create(addon=a)
f = File.objects.create(status=amo.STATUS_LITE, version=v)
a.update(status=amo.STATUS_LITE)
f.update(datestatuschanged=now - timedelta(days=4))
eq_(a.days_until_full_nomination(), 6)
f.update(datestatuschanged=now - timedelta(days=1))
eq_(a.days_until_full_nomination(), 9)
f.update(datestatuschanged=now - timedelta(days=10))
eq_(a.days_until_full_nomination(), 0)
f.update(datestatuschanged=now)
eq_(a.days_until_full_nomination(), 10)
# Only calculate days from first submitted version:
f.update(datestatuschanged=now - timedelta(days=2),
created=now - timedelta(days=2))
# Ignore this one:
f2 = File.objects.create(status=amo.STATUS_LITE, version=v)
f2.update(datestatuschanged=now - timedelta(days=1),
created=now - timedelta(days=1))
eq_(a.days_until_full_nomination(), 8)
# Wrong status:
a.update(status=amo.STATUS_PUBLIC)
f.update(datestatuschanged=now - timedelta(days=4))
eq_(a.days_until_full_nomination(), 0)
def setup_files(self, status):
addon = Addon.objects.create(type=1)
version = Version.objects.create(addon=addon)
File.objects.create(status=status, version=version)
return addon, version
def test_no_change_disabled_user(self):
addon, version = self.setup_files(amo.STATUS_UNREVIEWED)
addon.update(status=amo.STATUS_PUBLIC)
addon.update(disabled_by_user=True)
version.save()
eq_(addon.status, amo.STATUS_PUBLIC)
assert addon.is_disabled
def test_no_change_disabled(self):
addon = Addon.objects.create(type=1)
version = Version.objects.create(addon=addon)
addon.update(status=amo.STATUS_DISABLED)
version.save()
eq_(addon.status, amo.STATUS_DISABLED)
assert addon.is_disabled
def test_no_change_deleted(self):
addon = Addon.objects.create(type=1)
version = Version.objects.create(addon=addon)
addon.update(status=amo.STATUS_DELETED)
version.save()
eq_(addon.status, amo.STATUS_DELETED)
assert addon.is_deleted
def test_can_alter_in_prelim(self):
addon, version = self.setup_files(amo.STATUS_LITE)
addon.update(status=amo.STATUS_LITE)
version.save()
eq_(addon.status, amo.STATUS_LITE)
def test_removing_public(self):
addon, version = self.setup_files(amo.STATUS_UNREVIEWED)
addon.update(status=amo.STATUS_PUBLIC)
version.save()
eq_(addon.status, amo.STATUS_UNREVIEWED)
def test_removing_public_with_prelim(self):
addon, version = self.setup_files(amo.STATUS_LITE)
addon.update(status=amo.STATUS_PUBLIC)
version.save()
eq_(addon.status, amo.STATUS_LITE)
def test_can_request_review_no_files(self):
addon = Addon.objects.get(pk=3615)
addon.versions.all()[0].files.all().delete()
eq_(addon.can_request_review(), ())
def check(self, status, exp, kw={}):
addon = Addon.objects.get(pk=3615)
changes = {'status': status, 'disabled_by_user': False}
changes.update(**kw)
addon.update(**changes)
eq_(addon.can_request_review(), exp)
def test_can_request_review_null(self):
self.check(amo.STATUS_NULL, (amo.STATUS_LITE, amo.STATUS_PUBLIC))
def test_can_request_review_null_disabled(self):
self.check(amo.STATUS_NULL, (), {'disabled_by_user': True})
def test_can_request_review_unreviewed(self):
self.check(amo.STATUS_UNREVIEWED, (amo.STATUS_PUBLIC,))
def test_can_request_review_nominated(self):
self.check(amo.STATUS_NOMINATED, (amo.STATUS_LITE,))
def test_can_request_review_public(self):
self.check(amo.STATUS_PUBLIC, ())
def test_can_request_review_disabled(self):
self.check(amo.STATUS_DISABLED, ())
def test_can_request_review_deleted(self):
self.check(amo.STATUS_DELETED, ())
def test_can_request_review_lite(self):
self.check(amo.STATUS_LITE, (amo.STATUS_PUBLIC,))
def test_can_request_review_lite_and_nominated(self):
self.check(amo.STATUS_LITE_AND_NOMINATED, ())
def test_can_request_review_purgatory(self):
self.check(amo.STATUS_PURGATORY, (amo.STATUS_LITE, amo.STATUS_PUBLIC,))
def test_none_homepage(self):
# There was an odd error when a translation was set to None.
Addon.objects.create(homepage=None, type=amo.ADDON_EXTENSION)
def test_slug_isdigit(self):
a = Addon.objects.create(type=1, name='xx', slug='123')
eq_(a.slug, '123~')
a.slug = '44'
a.save()
eq_(a.slug, '44~')
def test_slug_isblacklisted(self):
# When an addon is uploaded, it doesn't use the form validation,
# so we'll just mangle the slug if its blacklisted.
a = Addon.objects.create(type=1, name='xx', slug='validate')
eq_(a.slug, 'validate~')
a.slug = 'validate'
a.save()
eq_(a.slug, 'validate~')
def delete(self):
addon = Addon.objects.get(id=3615)
eq_(len(mail.outbox), 0)
addon.delete('so long and thanks for all the fish')
eq_(len(mail.outbox), 1)
def test_delete_to(self):
self.delete()
eq_(mail.outbox[0].to, [settings.FLIGTAR])
def test_delete_by(self):
try:
user = Addon.objects.get(id=3615).authors.all()[0]
set_user(user)
self.delete()
assert 'DELETED BY: 55021' in mail.outbox[0].body
finally:
set_user(None)
def test_delete_by_unknown(self):
self.delete()
assert 'DELETED BY: Unknown' in mail.outbox[0].body
def test_view_source(self):
# view_source should default to True.
a = Addon.objects.create(type=1)
assert a.view_source
@patch('files.models.File.hide_disabled_file')
def test_admin_disabled_file_hidden(self, hide_mock):
a = Addon.objects.get(id=3615)
a.status = amo.STATUS_PUBLIC
a.save()
assert not hide_mock.called
a.status = amo.STATUS_DISABLED
a.save()
assert hide_mock.called
@patch('files.models.File.hide_disabled_file')
def test_user_disabled_file_hidden(self, hide_mock):
a = Addon.objects.get(id=3615)
a.disabled_by_user = False
a.save()
assert not hide_mock.called
a.disabled_by_user = True
a.save()
assert hide_mock.called
def test_set_nomination(self):
a = Addon.objects.get(id=3615)
a.update(status=amo.STATUS_NULL)
for s in (amo.STATUS_NOMINATED, amo.STATUS_LITE_AND_NOMINATED):
a.versions.latest().update(nomination=None)
a.update(status=s)
assert a.versions.latest().nomination
def test_new_version_inherits_nomination(self):
a = Addon.objects.get(id=3615)
ver = 10
for st in (amo.STATUS_NOMINATED, amo.STATUS_LITE_AND_NOMINATED):
a.update(status=st)
old_ver = a.versions.latest()
v = Version.objects.create(addon=a, version=str(ver))
eq_(v.nomination, old_ver.nomination)
ver += 1
def test_beta_version_does_not_inherit_nomination(self):
a = Addon.objects.get(id=3615)
a.update(status=amo.STATUS_LISTED)
v = Version.objects.create(addon=a, version='1.0')
v.nomination = None
v.save()
a.update(status=amo.STATUS_NOMINATED)
File.objects.create(version=v, status=amo.STATUS_BETA,
filename='foobar.xpi')
v.version = '1.1'
v.save()
eq_(v.nomination, None)
def test_lone_version_does_not_inherit_nomination(self):
a = Addon.objects.get(id=3615)
Version.objects.all().delete()
v = Version.objects.create(addon=a, version='1.0')
eq_(v.nomination, None)
def test_reviwed_addon_does_not_inherit_nomination(self):
a = Addon.objects.get(id=3615)
ver = 10
for st in (amo.STATUS_PUBLIC, amo.STATUS_BETA, amo.STATUS_LISTED):
a.update(status=st)
v = Version.objects.create(addon=a, version=str(ver))
eq_(v.nomination, None)
ver += 1
def test_nomination_no_version(self):
# Check that the on_change method still works if there are no versions.
a = Addon.objects.get(id=3615)
a.versions.all().delete()
a.update(status=amo.STATUS_NOMINATED)
def test_nomination_already_set(self):
addon = Addon.objects.get(id=3615)
earlier = datetime.today() - timedelta(days=2)
addon.versions.latest().update(nomination=earlier)
addon.update(status=amo.STATUS_NOMINATED)
eq_(addon.versions.latest().nomination.date(), earlier.date())
def test_category_transform(self):
addon = Addon.objects.get(id=3615)
cats = addon.categories.filter(application=amo.FIREFOX.id)
names = [c.name for c in cats]
assert addon.get_category(amo.FIREFOX.id).name in names
def test_binary_property(self):
addon = Addon.objects.get(id=3615)
file = addon.current_version.files.all()[0]
file.update(binary=True)
eq_(addon.binary, True)
def test_binary_components_property(self):
addon = Addon.objects.get(id=3615)
file = addon.current_version.files.all()[0]
file.update(binary_components=True)
eq_(addon.binary_components, True)
def test_compat_counts_transform_none(self):
addon = Addon.objects.get(id=3615)
eq_(addon._compat_counts, {'success': 0, 'failure': 0})
def test_compat_counts_transform_some(self):
guid = '{2fa4ed95-0317-4c6a-a74c-5f3e3912c1f9}'
CompatReport.objects.create(guid=guid, works_properly=True)
CompatReport.objects.create(guid=guid, works_properly=True)
CompatReport.objects.create(guid=guid, works_properly=False)
CompatReport.objects.create(guid='ballin', works_properly=True)
CompatReport.objects.create(guid='ballin', works_properly=False)
eq_(Addon.objects.get(id=3615)._compat_counts,
{'success': 2, 'failure': 1})
class TestAddonDelete(amo.tests.TestCase):
def test_cascades(self):
addon = Addon.objects.create(type=amo.ADDON_EXTENSION)
AddonCategory.objects.create(addon=addon,
category=Category.objects.create(type=amo.ADDON_EXTENSION))
AddonDependency.objects.create(addon=addon,
dependent_addon=addon)
AddonDeviceType.objects.create(addon=addon,
device_type=DEVICE_TYPES.keys()[0])
AddonRecommendation.objects.create(addon=addon,
other_addon=addon, score=0)
AddonUpsell.objects.create(free=addon, premium=addon)
AddonUser.objects.create(addon=addon,
user=UserProfile.objects.create())
AppSupport.objects.create(addon=addon,
app=Application.objects.create())
CompatOverride.objects.create(addon=addon)
FrozenAddon.objects.create(addon=addon)
Persona.objects.create(addon=addon, persona_id=0)
Preview.objects.create(addon=addon)
AddonLog.objects.create(addon=addon,
activity_log=ActivityLog.objects.create(action=0))
RssKey.objects.create(addon=addon)
SubmitStep.objects.create(addon=addon, step=0)
AddonPremium.objects.create(addon=addon)
AddonPaymentData.objects.create(addon=addon)
# This should not throw any FK errors if all the cascades work.
addon.delete()
class TestAddonGetURLPath(amo.tests.TestCase):
def test_get_url_path(self):
if not settings.MARKETPLACE:
addon = Addon(slug='woo')
eq_(addon.get_url_path(), '/en-US/firefox/addon/woo/')
def test_get_url_path_more(self):
if not settings.MARKETPLACE:
addon = Addon(slug='yeah')
eq_(addon.get_url_path(more=True),
'/en-US/firefox/addon/yeah/more')
def test_get_url_path_theme(self):
if settings.MARKETPLACE:
addon = Addon(slug='boi', type=amo.ADDON_PERSONA)
eq_(addon.get_url_path(), '/en-US/theme/boi/')
class TestAddonModelsFeatured(amo.tests.TestCase):
fixtures = ['addons/featured', 'bandwagon/featured_collections',
'base/addon_3615', 'base/collections', 'base/featured']
def setUp(self):
# Addon._featured keeps an in-process cache we need to clear.
if hasattr(Addon, '_featured'):
del Addon._featured
def _test_featured_random(self):
f = Addon.featured_random(amo.FIREFOX, 'en-US')
eq_(sorted(f), [1001, 1003, 2464, 3481, 7661, 15679])
f = Addon.featured_random(amo.FIREFOX, 'fr')
eq_(sorted(f), [1001, 1003, 2464, 7661, 15679])
f = Addon.featured_random(amo.THUNDERBIRD, 'en-US')
eq_(f, [])
def test_featured_random(self):
self._test_featured_random()
class TestBackupVersion(amo.tests.TestCase):
fixtures = ['addons/update']
def setUp(self):
self.version_1_2_0 = 105387
self.addon = Addon.objects.get(pk=1865)
set_user(None)
def setup_new_version(self):
for version in Version.objects.filter(pk__gte=self.version_1_2_0):
appversion = version.apps.all()[0]
appversion.min = AppVersion.objects.get(version='4.0b1')
appversion.save()
def test_no_backup_version(self):
self.addon.update_version()
eq_(self.addon.backup_version, None)
eq_(self.addon.current_version.version, '1.2.2')
def test_no_current_version(self):
Version.objects.all().delete()
self.addon.update(_current_version=None)
eq_(self.addon.backup_version, None)
eq_(self.addon.current_version, None)
def test_has_backup_version(self):
self.setup_new_version()
assert self.addon.update_version()
eq_(self.addon.backup_version.version, '1.1.3')
eq_(self.addon.current_version.version, '1.2.2')
def test_backup_version(self):
self.setup_new_version()
assert self.addon.update_version()
eq_(self.addon.backup_version.version, '1.1.3')
def test_firefox_versions(self):
self.setup_new_version()
assert self.addon.update_version()
backup = self.addon.backup_version.compatible_apps[amo.FIREFOX]
eq_(backup.max.version, '3.7a5pre')
eq_(backup.min.version, '3.0.12')
current = self.addon.current_version.compatible_apps[amo.FIREFOX]
eq_(current.max.version, '4.0b8pre')
eq_(current.min.version, '3.0.12')
def test_version_signals(self):
self.setup_new_version()
version = self.addon.versions.all()[0]
assert not self.addon.backup_version
version.save()
assert Addon.objects.get(pk=1865).backup_version
class TestCategoryModel(amo.tests.TestCase):
def test_category_url(self):
"""Every type must have a url path for its categories."""
for t in amo.ADDON_TYPE.keys():
if t == amo.ADDON_DICT:
continue # Language packs don't have categories.
cat = Category(type=AddonType(id=t), slug='omg')
assert cat.get_url_path()
class TestPersonaModel(amo.tests.TestCase):
fixtures = ['addons/persona', 'base/apps']
def setUp(self):
self.addon = Addon.objects.get(id=15663)
self.persona = self.addon.persona
self.persona.header = 'header.jpg'
self.persona.footer = 'footer.jpg'
self.persona.save()
def test_image_urls(self):
self.persona.persona_id = 0
self.persona.save()
p = lambda x: '/15663/' + x
assert self.persona.thumb_url.endswith(p('thumb.jpg')), (
self.persona.thumb_url)
assert self.persona.icon_url.endswith(p('icon.jpg')), (
self.persona.icon_url)
assert self.persona.preview_url.endswith(p('preview.jpg')), (
self.persona.preview_url)
assert self.persona.header_url.endswith(p('header.jpg')), (
self.persona.header_url)
assert self.persona.footer_url.endswith(p('footer.jpg')), (
self.persona.footer_url)
def test_old_image_urls(self):
p = lambda x: '/1/3/813/' + x
assert self.persona.thumb_url.endswith(p('preview.jpg')), (
self.persona.thumb_url)
assert self.persona.icon_url.endswith(p('preview_small.jpg')), (
self.persona.icon_url)
assert self.persona.preview_url.endswith(p('preview_large.jpg')), (
self.persona.preview_url)
assert self.persona.header_url.endswith(p('header.jpg')), (
self.persona.header_url)
assert self.persona.footer_url.endswith(p('footer.jpg')), (
self.persona.footer_url)
def test_update_url(self):
assert self.persona.update_url.endswith(str(self.persona.persona_id))
class TestPreviewModel(amo.tests.TestCase):
fixtures = ['base/previews']
def test_as_dict(self):
expect = ['caption', 'full', 'thumbnail']
reality = sorted(Preview.objects.all()[0].as_dict().keys())
eq_(expect, reality)
def test_filename(self):
preview = Preview.objects.get(pk=24)
eq_(preview.file_extension, 'png')
preview.update(filetype='')
eq_(preview.file_extension, 'png')
preview.update(filetype='video/webm')
eq_(preview.file_extension, 'webm')
def test_filename_in_url(self):
preview = Preview.objects.get(pk=24)
preview.update(filetype='video/webm')
assert 'png' in preview.thumbnail_path
assert 'webm' in preview.image_path
class TestAddonRecommendations(amo.tests.TestCase):
fixtures = ['base/addon-recs']
def test_scores(self):
ids = [5299, 1843, 2464, 7661, 5369]
scores = AddonRecommendation.scores(ids)
q = AddonRecommendation.objects.filter(addon__in=ids)
for addon, recs in itertools.groupby(q, lambda x: x.addon_id):
for rec in recs:
eq_(scores[addon][rec.other_addon_id], rec.score)
class TestAddonDependencies(amo.tests.TestCase):
fixtures = ['base/addon_5299_gcal',
'base/addon_3615',
'base/addon_3723_listed',
'base/addon_6704_grapple',
'base/addon_4664_twitterbar']
def test_dependencies(self):
ids = [3615, 3723, 4664, 6704]
a = Addon.objects.get(id=5299)
for dependent_id in ids:
AddonDependency(addon=a,
dependent_addon=Addon.objects.get(id=dependent_id)).save()
eq_(sorted([a.id for a in a.dependencies.all()]), sorted(ids))
eq_(list(a.dependencies.all()), a.all_dependencies)
def test_unique_dependencies(self):
a = Addon.objects.get(id=5299)
b = Addon.objects.get(id=3615)
AddonDependency.objects.create(addon=a, dependent_addon=b)
try:
AddonDependency.objects.create(addon=a, dependent_addon=b)
except IntegrityError:
pass
eq_(list(a.dependencies.values_list('id', flat=True)), [3615])
class TestListedAddonTwoVersions(amo.tests.TestCase):
fixtures = ['addons/listed-two-versions']
def test_listed_two_versions(self):
Addon.objects.get(id=2795) # bug 563967
class TestFlushURLs(amo.tests.TestCase):
fixtures = ['base/addon_5579',
'base/previews',
'base/addon_4664_twitterbar',
'addons/persona']
def setUp(self):
settings.ADDON_ICON_URL = (settings.STATIC_URL +
'/img/uploads/addon_icons/%s/%s-%s.png?modified=%s')
settings.PREVIEW_THUMBNAIL_URL = (settings.STATIC_URL +
'/img/uploads/previews/thumbs/%s/%d.png?modified=%d')
settings.PREVIEW_FULL_URL = (settings.STATIC_URL +
'/img/uploads/previews/full/%s/%d.%s?modified=%d')
_connect()
def tearDown(self):
_disconnect()
def is_url_hashed(self, url):
return urlparse(url).query.find('modified') > -1
@patch('amo.tasks.flush_front_end_cache_urls.apply_async')
def test_addon_flush(self, flush):
addon = Addon.objects.get(pk=159)
addon.icon_type = "image/png"
addon.save()
for url in (addon.thumbnail_url, addon.icon_url):
assert url in flush.call_args[1]['args'][0]
assert self.is_url_hashed(url), url
@patch('amo.tasks.flush_front_end_cache_urls.apply_async')
def test_preview_flush(self, flush):
addon = Addon.objects.get(pk=4664)
preview = addon.previews.all()[0]
preview.save()
for url in (preview.thumbnail_url, preview.image_url):
assert url in flush.call_args[1]['args'][0]
assert self.is_url_hashed(url), url
class TestAddonFromUpload(UploadTest):
fixtures = ('base/apps', 'base/users')
def setUp(self):
super(TestAddonFromUpload, self).setUp()
u = UserProfile.objects.get(pk=999)
set_user(u)
self.platform = Platform.objects.create(id=amo.PLATFORM_MAC.id)
for version in ('3.0', '3.6.*'):
AppVersion.objects.create(application_id=1, version=version)
self.addCleanup(translation.deactivate)
def manifest(self, basename):
return os.path.join(settings.ROOT, 'apps', 'devhub', 'tests',
'addons', basename)
def test_blacklisted_guid(self):
BlacklistedGuid.objects.create(guid='guid@xpi')
with self.assertRaises(forms.ValidationError) as e:
Addon.from_upload(self.get_upload('extension.xpi'),
[self.platform])
eq_(e.exception.messages, ['Duplicate UUID found.'])
def test_xpi_attributes(self):
addon = Addon.from_upload(self.get_upload('extension.xpi'),
[self.platform])
eq_(addon.name, 'xpi name')
eq_(addon.guid, 'guid@xpi')
eq_(addon.type, amo.ADDON_EXTENSION)
eq_(addon.status, amo.STATUS_NULL)
eq_(addon.homepage, 'http://homepage.com')
eq_(addon.summary, 'xpi description')
eq_(addon.description, None)
eq_(addon.slug, 'xpi-name')
def test_manifest_url(self):
upload = self.get_upload(abspath=self.manifest('mozball.webapp'))
addon = Addon.from_upload(upload, [self.platform])
assert addon.is_webapp()
eq_(addon.manifest_url, upload.name)
def test_app_domain(self):
upload = self.get_upload(abspath=self.manifest('mozball.webapp'))
upload.name = 'http://mozilla.com/my/rad/app.webapp' # manifest URL
addon = Addon.from_upload(upload, [self.platform])
eq_(addon.app_domain, 'http://mozilla.com')
def test_non_english_app(self):
upload = self.get_upload(abspath=self.manifest('non-english.webapp'))
upload.name = 'http://mozilla.com/my/rad/app.webapp' # manifest URL
addon = Addon.from_upload(upload, [self.platform])
eq_(addon.default_locale, 'it')
eq_(unicode(addon.name), 'ItalianMozBall')
eq_(addon.name.locale, 'it')
def test_xpi_version(self):
addon = Addon.from_upload(self.get_upload('extension.xpi'),
[self.platform])
v = addon.versions.get()
eq_(v.version, '0.1')
eq_(v.files.get().platform_id, self.platform.id)
eq_(v.files.get().status, amo.STATUS_UNREVIEWED)
def test_xpi_for_multiple_platforms(self):
platforms = [Platform.objects.get(pk=amo.PLATFORM_LINUX.id),
Platform.objects.get(pk=amo.PLATFORM_MAC.id)]
addon = Addon.from_upload(self.get_upload('extension.xpi'),
platforms)
v = addon.versions.get()
eq_(sorted([f.platform.id for f in v.all_files]),
sorted([p.id for p in platforms]))
def test_search_attributes(self):
addon = Addon.from_upload(self.get_upload('search.xml'),
[self.platform])
eq_(addon.name, 'search tool')
eq_(addon.guid, None)
eq_(addon.type, amo.ADDON_SEARCH)
eq_(addon.status, amo.STATUS_NULL)
eq_(addon.homepage, None)
eq_(addon.description, None)
eq_(addon.slug, 'search-tool')
eq_(addon.summary, 'Search Engine for Firefox')
def test_search_version(self):
addon = Addon.from_upload(self.get_upload('search.xml'),
[self.platform])
v = addon.versions.get()
eq_(v.version, datetime.now().strftime('%Y%m%d'))
eq_(v.files.get().platform_id, amo.PLATFORM_ALL.id)
eq_(v.files.get().status, amo.STATUS_UNREVIEWED)
def test_no_homepage(self):
addon = Addon.from_upload(self.get_upload('extension-no-homepage.xpi'),
[self.platform])
eq_(addon.homepage, None)
def test_default_locale(self):
# Make sure default_locale follows the active translation.
addon = Addon.from_upload(self.get_upload('search.xml'),
[self.platform])
eq_(addon.default_locale, 'en-US')
translation.activate('es')
addon = Addon.from_upload(self.get_upload('search.xml'),
[self.platform])
eq_(addon.default_locale, 'es')
def test_webapp_default_locale_override(self):
with nested(tempfile.NamedTemporaryFile('w', suffix='.webapp'),
open(self.manifest('mozball.webapp'))) as (tmp, mf):
mf = json.load(mf)
mf['default_locale'] = 'gb'
tmp.write(json.dumps(mf))
tmp.flush()
upload = self.get_upload(abspath=tmp.name)
addon = Addon.from_upload(upload, [self.platform])
eq_(addon.default_locale, 'gb')
def test_browsing_locale_does_not_override(self):
translation.activate('gb')
# Upload app with en-US as default.
upload = self.get_upload(abspath=self.manifest('mozball.webapp'))
addon = Addon.from_upload(upload, [self.platform])
eq_(addon.default_locale, 'en-US') # not gb
@raises(forms.ValidationError)
def test_malformed_locales(self):
manifest = self.manifest('malformed-locales.webapp')
upload = self.get_upload(abspath=manifest)
Addon.from_upload(upload, [self.platform])
REDIRECT_URL = 'http://outgoing.mozilla.org/v1/'
class TestCharity(amo.tests.TestCase):
fixtures = ['base/charity.json']
@patch.object(settings, 'REDIRECT_URL', REDIRECT_URL)
def test_url(self):
charity = Charity(name="a", paypal="b", url="http://foo.com")
charity.save()
assert charity.outgoing_url.startswith(REDIRECT_URL)
@patch.object(settings, 'REDIRECT_URL', REDIRECT_URL)
def test_url_foundation(self):
foundation = Charity.objects.get(pk=amo.FOUNDATION_ORG)
assert not foundation.outgoing_url.startswith(REDIRECT_URL)
class TestFrozenAddons(amo.tests.TestCase):
def test_immediate_freeze(self):
# Adding a FrozenAddon should immediately drop the addon's hotness.
a = Addon.objects.create(type=1, hotness=22)
FrozenAddon.objects.create(addon=a)
eq_(Addon.objects.get(id=a.id).hotness, 0)
class TestRemoveLocale(amo.tests.TestCase):
def test_remove(self):
a = Addon.objects.create(type=1)
a.name = {'en-US': 'woo', 'el': 'yeah'}
a.description = {'en-US': 'woo', 'el': 'yeah', 'he': 'ola'}
a.save()
a.remove_locale('el')
qs = (Translation.objects.filter(localized_string__isnull=False)
.values_list('locale', flat=True))
eq_(sorted(qs.filter(id=a.name_id)), ['en-US'])
eq_(sorted(qs.filter(id=a.description_id)), ['en-US', 'he'])
def test_remove_version_locale(self):
addon = Addon.objects.create(type=amo.ADDON_THEME)
version = Version.objects.create(addon=addon)
version.releasenotes = {'fr': 'oui'}
version.save()
addon.remove_locale('fr')
assert not (Translation.objects.filter(localized_string__isnull=False)
.values_list('locale', flat=True))
class TestAddonWatchDisabled(amo.tests.TestCase):
def setUp(self):
self.addon = Addon(type=amo.ADDON_THEME, disabled_by_user=False,
status=amo.STATUS_PUBLIC)
self.addon.save()
@patch('addons.models.File.objects.filter')
def test_no_disabled_change(self, file_mock):
mock = Mock()
file_mock.return_value = [mock]
self.addon.save()
assert not mock.unhide_disabled_file.called
assert not mock.hide_disabled_file.called
@patch('addons.models.File.objects.filter')
def test_disable_addon(self, file_mock):
mock = Mock()
file_mock.return_value = [mock]
self.addon.update(disabled_by_user=True)
assert not mock.unhide_disabled_file.called
assert mock.hide_disabled_file.called
@patch('addons.models.File.objects.filter')
def test_admin_disable_addon(self, file_mock):
mock = Mock()
file_mock.return_value = [mock]
self.addon.update(status=amo.STATUS_DISABLED)
assert not mock.unhide_disabled_file.called
assert mock.hide_disabled_file.called
@patch('addons.models.File.objects.filter')
def test_enable_addon(self, file_mock):
mock = Mock()
file_mock.return_value = [mock]
self.addon.update(status=amo.STATUS_DISABLED)
mock.reset_mock()
self.addon.update(status=amo.STATUS_PUBLIC)
assert mock.unhide_disabled_file.called
assert not mock.hide_disabled_file.called
class TestSearchSignals(amo.tests.ESTestCase):
es = True
def setUp(self):
super(TestSearchSignals, self).setUp()
setup_mapping()
self.addCleanup(self.cleanup)
def cleanup(self):
for index in settings.ES_INDEXES.values():
self.es.delete_index_if_exists(index)
def test_no_addons(self):
eq_(Addon.search().count(), 0)
def test_create(self):
addon = Addon.objects.create(type=amo.ADDON_EXTENSION, name='woo')
self.refresh()
eq_(Addon.search().count(), 1)
eq_(Addon.search().query(name='woo')[0].id, addon.id)
def test_update(self):
addon = Addon.objects.create(type=amo.ADDON_EXTENSION, name='woo')
self.refresh()
eq_(Addon.search().count(), 1)
addon.name = 'yeah'
addon.save()
self.refresh()
eq_(Addon.search().count(), 1)
eq_(Addon.search().query(name='woo').count(), 0)
eq_(Addon.search().query(name='yeah')[0].id, addon.id)
def test_delete(self):
addon = Addon.objects.create(type=amo.ADDON_EXTENSION, name='woo')
self.refresh()
eq_(Addon.search().count(), 1)
addon.delete('woo')
self.refresh()
eq_(Addon.search().count(), 0)
class TestLanguagePack(TestLanguagePack):
def setUp(self):
super(TestLanguagePack, self).setUp()
self.platform = Platform.objects.create(id=amo.PLATFORM_ANDROID.id)
def test_extract(self):
File.objects.create(platform=self.platform, version=self.version,
filename=self.xpi_path('langpack-localepicker'))
assert 'title=Select a language' in self.addon.get_localepicker()
def test_extract_no_file(self):
File.objects.create(platform=self.platform, version=self.version,
filename=self.xpi_path('langpack'))
eq_(self.addon.get_localepicker(), '')
def test_extract_no_files(self):
eq_(self.addon.get_localepicker(), '')
def test_extract_not_language_pack(self):
self.addon.update(type=amo.ADDON_LPAPP)
eq_(self.addon.get_localepicker(), '')
def test_extract_not_platform_all(self):
self.mac = Platform.objects.create(id=amo.PLATFORM_MAC.id)
File.objects.create(platform=self.mac, version=self.version,
filename=self.xpi_path('langpack'))
eq_(self.addon.get_localepicker(), '')
class TestMarketplace(amo.tests.TestCase):
def setUp(self):
self.addon = Addon.objects.create(type=amo.ADDON_EXTENSION)
def test_is_premium(self):
assert not self.addon.is_premium()
self.addon.update(premium_type=amo.ADDON_PREMIUM)
assert self.addon.is_premium()
def test_is_premium_inapp(self):
assert not self.addon.is_premium()
self.addon.update(premium_type=amo.ADDON_PREMIUM_INAPP)
assert self.addon.is_premium()
def test_is_premium_free(self):
assert not self.addon.is_premium()
self.addon.update(premium_type=amo.ADDON_FREE_INAPP)
assert not self.addon.is_premium()
def test_is_free(self):
assert self.addon.is_free()
self.addon.update(premium_type=amo.ADDON_PREMIUM)
AddonPremium.objects.create(addon=self.addon, price=None)
assert self.addon.is_free()
price = Price.objects.create(price='1.00')
self.addon.premium.update(price=price)
assert not self.addon.is_free()
# An add-on of price $0.00 is still technically not "free".
self.addon.premium.price.update(price='0.00')
assert not self.addon.is_free()
self.addon.premium.update(price=None)
assert self.addon.is_free()
def test_has_price(self):
self.addon.update(premium_type=amo.ADDON_PREMIUM)
AddonPremium.objects.create(addon=self.addon, price=None)
assert not self.addon.premium.has_price()
price = Price.objects.create(price='1.00')
self.addon.premium.update(price=price)
assert self.addon.premium.has_price()
def test_does_not_need_paypal(self):
self.addon.update(premium_type=amo.ADDON_FREE)
assert not self.addon.needs_paypal()
def test_other_payments(self):
self.addon.update(premium_type=amo.ADDON_OTHER_INAPP)
assert not self.addon.needs_paypal()
def test_needs_paypal(self):
for status in [amo.ADDON_PREMIUM, amo.ADDON_PREMIUM_INAPP,
amo.ADDON_FREE_INAPP]:
self.addon.update(premium_type=status)
assert self.addon.needs_paypal()
def test_can_be_premium_upsell(self):
self.addon.update(premium_type=amo.ADDON_PREMIUM)
free = Addon.objects.create(type=amo.ADDON_EXTENSION)
AddonUpsell.objects.create(free=free, premium=self.addon)
assert not free.can_become_premium()
def test_can_be_premium_status(self):
for status in amo.STATUS_CHOICES.keys():
self.addon.update(status=status)
if status in amo.PREMIUM_STATUSES:
assert self.addon.can_become_premium()
else:
assert not self.addon.can_become_premium()
def test_webapp_can_become_premium(self):
self.addon.update(type=amo.ADDON_WEBAPP)
for status in amo.STATUS_CHOICES.keys():
self.addon.update(status=status)
assert self.addon.can_become_premium(), status
def test_can_be_premium_type(self):
for type in amo.ADDON_TYPES.keys():
self.addon.update(type=type)
if type in [amo.ADDON_EXTENSION, amo.ADDON_WEBAPP,
amo.ADDON_LPAPP, amo.ADDON_DICT, amo.ADDON_THEME]:
assert self.addon.can_become_premium()
else:
assert not self.addon.can_become_premium()
def test_can_not_be_purchased(self):
assert not self.addon.can_be_purchased()
def test_can_still_not_be_purchased(self):
self.addon.update(premium_type=amo.ADDON_PREMIUM)
assert not self.addon.can_be_purchased()
def test_can_be_purchased(self):
for status in amo.REVIEWED_STATUSES:
self.addon.update(premium_type=amo.ADDON_PREMIUM,
status=status)
assert self.addon.can_be_purchased()
def test_transformer(self):
other = Addon.objects.create(type=amo.ADDON_EXTENSION)
price = Price.objects.create(price='1.00')
self.addon.update(type=amo.ADDON_PREMIUM)
AddonPremium.objects.create(addon=self.addon, price=price)
assert getattr(Addon.objects.get(pk=self.addon.pk), 'premium')
assert not getattr(Addon.objects.get(pk=other.pk), 'premium')
def test_price_transformer(self):
price = Price.objects.create(price='1.00')
price.pricecurrency_set.create(currency='BRL', price='1.01')
self.addon.update(premium_type=amo.ADDON_PREMIUM)
AddonPremium.objects.create(addon=self.addon, price=price)
addon = list(Addon.objects.filter(pk=self.addon.pk))
with self.assertNumQueries(0):
eq_(addon[0].premium.get_price_locale(), '$1.00')
translation.activate('pt_BR')
eq_(addon[0].premium.get_price_locale(), u'R$1,01')
class TestAddonUpsell(amo.tests.TestCase):
def setUp(self):
self.one = Addon.objects.create(type=amo.ADDON_EXTENSION, name='free')
self.two = Addon.objects.create(type=amo.ADDON_EXTENSION,
name='premium')
self.upsell = AddonUpsell.objects.create(free=self.one,
premium=self.two, text='yup')
def test_create_upsell(self):
eq_(self.one.upsell.premium, self.two)
eq_(self.one.upsell.text, 'yup')
eq_(self.two.upsell, None)
class TestAddonPurchase(amo.tests.TestCase):
fixtures = ['base/users']
def setUp(self):
self.user = UserProfile.objects.get(pk=999)
self.addon = Addon.objects.create(type=amo.ADDON_EXTENSION,
premium_type=amo.ADDON_PREMIUM,
name='premium')
def test_no_premium(self):
# If you've purchased something, the fact that its now free
# doesn't change the fact that you purchased it.
self.addon.addonpurchase_set.create(user=self.user)
self.addon.update(premium_type=amo.ADDON_FREE)
assert self.addon.has_purchased(self.user)
def test_has_purchased(self):
self.addon.addonpurchase_set.create(user=self.user)
assert self.addon.has_purchased(self.user)
def test_not_purchased(self):
assert not self.addon.has_purchased(self.user)
def test_anonymous(self):
assert not self.addon.has_purchased(None)
assert not self.addon.has_purchased(AnonymousUser)
def test_is_refunded(self):
self.addon.addonpurchase_set.create(user=self.user,
type=amo.CONTRIB_REFUND)
assert self.addon.is_refunded(self.user)
def test_is_chargeback(self):
self.addon.addonpurchase_set.create(user=self.user,
type=amo.CONTRIB_CHARGEBACK)
assert self.addon.is_chargeback(self.user)
def test_purchase_state(self):
purchase = self.addon.addonpurchase_set.create(user=self.user)
for state in [amo.CONTRIB_PURCHASE, amo.CONTRIB_REFUND,
amo.CONTRIB_CHARGEBACK]:
purchase.update(type=state)
eq_(state, self.addon.get_purchase_type(self.user))
class TestWatermarkHash(amo.tests.TestCase):
fixtures = ['base/addon_3615', 'base/users']
def setUp(self):
self.addon = Addon.objects.get(pk=3615)
self.user = UserProfile.objects.get(email='regular@mozilla.com')
def test_watermark_change_email(self):
hsh = self.addon.get_watermark_hash(self.user)
self.user.update(email='foo@bar.com')
eq_(hsh, self.addon.get_watermark_hash(self.user))
def test_check_hash(self):
hsh = self.addon.get_watermark_hash(self.user)
eq_(self.user, self.addon.get_user_from_hash(self.user.email, hsh))
def test_check_hash_messed(self):
hsh = self.addon.get_watermark_hash(self.user)
hsh = hsh + 'asd'
eq_(None, self.addon.get_user_from_hash(self.user.email, hsh))
def test_check_user_change(self):
self.user.update(email='foo@bar.com')
hsh = self.addon.get_watermark_hash(self.user)
eq_(self.user,
self.addon.get_user_from_hash('regular@mozilla.com', hsh))
def test_check_user_multiple(self):
hsh = self.addon.get_watermark_hash(self.user)
self.user.update(email='foo@bar.com')
UserProfile.objects.create(email='regular@mozilla.com')
eq_(self.user,
self.addon.get_user_from_hash('regular@mozilla.com', hsh))
def test_cant_takeover(self):
hsh = self.addon.get_watermark_hash(self.user)
self.user.delete()
UserProfile.objects.create(email='regular@mozilla.com')
eq_(None, self.addon.get_user_from_hash('regular@mozilla.com', hsh))
class TestCompatOverride(amo.tests.TestCase):
def setUp(self):
self.app = Application.objects.create(id=1)
one = CompatOverride.objects.create(guid='one')
CompatOverrideRange.objects.create(compat=one, app=self.app)
two = CompatOverride.objects.create(guid='two')
CompatOverrideRange.objects.create(compat=two, app=self.app,
min_version='1', max_version='2')
CompatOverrideRange.objects.create(compat=two, app=self.app,
min_version='1', max_version='2',
min_app_version='3',
max_app_version='4')
def check(self, obj, **kw):
"""Check that key/value pairs in kw match attributes of obj."""
for key, expected in kw.items():
actual = getattr(obj, key)
eq_(actual, expected, '[%s] %r != %r' % (key, actual, expected))
def test_is_hosted(self):
c = CompatOverride.objects.create(guid='a')
assert not c.is_hosted()
Addon.objects.create(type=1, guid='b')
c = CompatOverride.objects.create(guid='b')
assert c.is_hosted()
def test_override_type(self):
one = CompatOverride.objects.get(guid='one')
# The default is incompatible.
c = CompatOverrideRange.objects.create(compat=one, app_id=1)
eq_(c.override_type(), 'incompatible')
c = CompatOverrideRange.objects.create(compat=one, app_id=1, type=0)
eq_(c.override_type(), 'compatible')
def test_guid_match(self):
# We hook up the add-on automatically if we see a matching guid.
addon = Addon.objects.create(id=1, guid='oh yeah', type=1)
c = CompatOverride.objects.create(guid=addon.guid)
eq_(c.addon_id, addon.id)
c = CompatOverride.objects.create(guid='something else')
assert c.addon is None
def test_transformer(self):
compats = list(CompatOverride.objects
.transform(CompatOverride.transformer))
ranges = list(CompatOverrideRange.objects.all())
# If the transformer works then we won't have any more queries.
with self.assertNumQueries(0):
for c in compats:
eq_(c.compat_ranges,
[r for r in ranges if r.compat_id == c.id])
def test_collapsed_ranges(self):
# Test that we get back the right structures from collapsed_ranges().
c = CompatOverride.objects.get(guid='one')
r = c.collapsed_ranges()
eq_(len(r), 1)
compat_range = r[0]
self.check(compat_range, type='incompatible', min='0', max='*')
eq_(len(compat_range.apps), 1)
self.check(compat_range.apps[0], app=amo.FIREFOX, min='0', max='*')
def test_collapsed_ranges_multiple_versions(self):
c = CompatOverride.objects.get(guid='one')
CompatOverrideRange.objects.create(compat=c, app_id=1,
min_version='1', max_version='2',
min_app_version='3',
max_app_version='3.*')
r = c.collapsed_ranges()
eq_(len(r), 2)
self.check(r[0], type='incompatible', min='0', max='*')
eq_(len(r[0].apps), 1)
self.check(r[0].apps[0], app=amo.FIREFOX, min='0', max='*')
self.check(r[1], type='incompatible', min='1', max='2')
eq_(len(r[1].apps), 1)
self.check(r[1].apps[0], app=amo.FIREFOX, min='3', max='3.*')
def test_collapsed_ranges_different_types(self):
# If the override ranges have different types they should be separate
# entries.
c = CompatOverride.objects.get(guid='one')
CompatOverrideRange.objects.create(compat=c, app_id=1, type=0,
min_app_version='3',
max_app_version='3.*')
r = c.collapsed_ranges()
eq_(len(r), 2)
self.check(r[0], type='compatible', min='0', max='*')
eq_(len(r[0].apps), 1)
self.check(r[0].apps[0], app=amo.FIREFOX, min='3', max='3.*')
self.check(r[1], type='incompatible', min='0', max='*')
eq_(len(r[1].apps), 1)
self.check(r[1].apps[0], app=amo.FIREFOX, min='0', max='*')
def test_collapsed_ranges_multiple_apps(self):
c = CompatOverride.objects.get(guid='two')
r = c.collapsed_ranges()
eq_(len(r), 1)
compat_range = r[0]
self.check(compat_range, type='incompatible', min='1', max='2')
eq_(len(compat_range.apps), 2)
self.check(compat_range.apps[0], app=amo.FIREFOX, min='0', max='*')
self.check(compat_range.apps[1], app=amo.FIREFOX, min='3', max='4')
def test_collapsed_ranges_multiple_versions_and_apps(self):
c = CompatOverride.objects.get(guid='two')
CompatOverrideRange.objects.create(min_version='5', max_version='6',
compat=c, app_id=1)
r = c.collapsed_ranges()
eq_(len(r), 2)
self.check(r[0], type='incompatible', min='1', max='2')
eq_(len(r[0].apps), 2)
self.check(r[0].apps[0], app=amo.FIREFOX, min='0', max='*')
self.check(r[0].apps[1], app=amo.FIREFOX, min='3', max='4')
self.check(r[1], type='incompatible', min='5', max='6')
eq_(len(r[1].apps), 1)
self.check(r[1].apps[0], app=amo.FIREFOX, min='0', max='*')
class TestIncompatibleVersions(amo.tests.TestCase):
def setUp(self):
self.app = Application.objects.create(id=amo.FIREFOX.id)
self.addon = Addon.objects.create(guid='r@b', type=amo.ADDON_EXTENSION)
def test_signals_min(self):
eq_(IncompatibleVersions.objects.count(), 0)
c = CompatOverride.objects.create(guid='r@b')
CompatOverrideRange.objects.create(compat=c, app=self.app,
min_version='0',
max_version='1.0')
# Test the max version matched.
version1 = Version.objects.create(id=2, addon=self.addon,
version='1.0')
eq_(IncompatibleVersions.objects.filter(version=version1).count(), 1)
eq_(IncompatibleVersions.objects.count(), 1)
# Test the lower range.
version2 = Version.objects.create(id=1, addon=self.addon,
version='0.5')
eq_(IncompatibleVersions.objects.filter(version=version2).count(), 1)
eq_(IncompatibleVersions.objects.count(), 2)
# Test delete signals.
version1.delete()
eq_(IncompatibleVersions.objects.count(), 1)
version2.delete()
eq_(IncompatibleVersions.objects.count(), 0)
def test_signals_max(self):
eq_(IncompatibleVersions.objects.count(), 0)
c = CompatOverride.objects.create(guid='r@b')
CompatOverrideRange.objects.create(compat=c, app=self.app,
min_version='1.0',
max_version='*')
# Test the min_version matched.
version1 = Version.objects.create(addon=self.addon, version='1.0')
eq_(IncompatibleVersions.objects.filter(version=version1).count(), 1)
eq_(IncompatibleVersions.objects.count(), 1)
# Test the upper range.
version2 = Version.objects.create(addon=self.addon, version='99.0')
eq_(IncompatibleVersions.objects.filter(version=version2).count(), 1)
eq_(IncompatibleVersions.objects.count(), 2)
# Test delete signals.
version1.delete()
eq_(IncompatibleVersions.objects.count(), 1)
version2.delete()
eq_(IncompatibleVersions.objects.count(), 0)