зеркало из https://github.com/mozilla/bedrock.git
Move release notes views to rely on database
* No longer rely on release notes JSON files on the file system. * Switch everything to new update_release_notes_data command * Move to using django-memoize for releasenotes caching
This commit is contained in:
Родитель
d93c193b28
Коммит
ebd6a7c438
|
@ -20,11 +20,11 @@ class SimpleDictCache(LocMemCache):
|
|||
def get(self, key, default=None, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
value = None
|
||||
value = default
|
||||
with self._lock.reader():
|
||||
if not self._has_expired(key):
|
||||
value = self._cache[key]
|
||||
if value is not None:
|
||||
if value is not default:
|
||||
return value
|
||||
|
||||
with self._lock.writer():
|
||||
|
|
|
@ -94,6 +94,11 @@ class SimpleDictCacheTests(TestCase):
|
|||
self.assertEqual(cache.get("does_not_exist"), None)
|
||||
self.assertEqual(cache.get("does_not_exist", "bang!"), "bang!")
|
||||
|
||||
def test_non_none_default(self):
|
||||
# Should cache None values if default is not None
|
||||
cache.set('is_none', None)
|
||||
self.assertIsNone(cache.get('is_none', 'bang!'))
|
||||
|
||||
def test_get_many(self):
|
||||
# Multiple cache keys can be returned using get_many
|
||||
cache.set('a', 'a')
|
||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import print_function
|
|||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from bedrock.releasenotes.models import ProductRelease
|
||||
from bedrock.utils.git import GitRepo
|
||||
|
||||
|
||||
|
@ -10,10 +11,29 @@ class Command(BaseCommand):
|
|||
def add_arguments(self, parser):
|
||||
parser.add_argument('-q', '--quiet', action='store_true', dest='quiet', default=False,
|
||||
help='If no error occurs, swallow all output.'),
|
||||
parser.add_argument('-f', '--force', action='store_true', dest='force', default=False,
|
||||
help='Load the data even if nothing new from git.'),
|
||||
|
||||
def output(self, msg):
|
||||
if not self.quiet:
|
||||
print(msg)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
self.quiet = options['quiet']
|
||||
repo = GitRepo(settings.RELEASE_NOTES_PATH, settings.RELEASE_NOTES_REPO,
|
||||
branch_name=settings.RELEASE_NOTES_BRANCH)
|
||||
self.output('Updating git repo')
|
||||
repo.update()
|
||||
if not options['quiet']:
|
||||
print('Release Notes Successfully Updated')
|
||||
if not (options['force'] or repo.has_changes()):
|
||||
self.output('No release note updates')
|
||||
return
|
||||
|
||||
self.output('Loading releases into database')
|
||||
count = ProductRelease.objects.refresh()
|
||||
|
||||
self.output('%s release notes successfully loaded' % count)
|
||||
|
||||
repo.set_db_latest()
|
||||
|
||||
self.output('Saved latest git repo state to database')
|
||||
self.output('Done!')
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
from __future__ import print_function
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from bedrock.releasenotes.models import ProductRelease
|
||||
from bedrock.utils.git import GitRepo
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('-q', '--quiet', action='store_true', dest='quiet', default=False,
|
||||
help='If no error occurs, swallow all output.'),
|
||||
|
||||
def handle(self, *args, **options):
|
||||
repo = GitRepo(settings.RELEASE_NOTES_PATH, settings.RELEASE_NOTES_REPO,
|
||||
branch_name=settings.RELEASE_NOTES_BRANCH)
|
||||
repo.update()
|
||||
if not repo.has_changes():
|
||||
if not options['quiet']:
|
||||
print('No Release Note Updates')
|
||||
return
|
||||
|
||||
count = ProductRelease.objects.refresh()
|
||||
|
||||
if not options['quiet']:
|
||||
print('%s Release Notes Successfully Updated' % count)
|
||||
|
||||
repo.set_db_latest()
|
|
@ -1,25 +1,22 @@
|
|||
import codecs
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from glob import glob
|
||||
from hashlib import sha256
|
||||
from operator import attrgetter
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import caches
|
||||
from django.db import models, transaction
|
||||
from django.http import Http404
|
||||
from django.utils.dateparse import parse_date, parse_datetime
|
||||
from django.utils.dateparse import parse_datetime
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.text import slugify
|
||||
|
||||
import markdown
|
||||
from django_extensions.db.fields.json import JSONField
|
||||
from product_details.version_compare import Version
|
||||
from raven.contrib.django.raven_compat.models import client as sentry_client
|
||||
|
||||
from bedrock.base.urlresolvers import reverse
|
||||
from bedrock.releasenotes.utils import memoize
|
||||
|
||||
|
||||
LONG_RN_CACHE_TIMEOUT = 7200 # 2 hours
|
||||
|
@ -29,10 +26,6 @@ markdowner = markdown.Markdown(extensions=[
|
|||
])
|
||||
|
||||
|
||||
def release_notes_path():
|
||||
return os.path.join(settings.RELEASE_NOTES_PATH, 'releases')
|
||||
|
||||
|
||||
def process_markdown(value):
|
||||
return markdowner.reset().convert(value)
|
||||
|
||||
|
@ -50,26 +43,18 @@ def process_is_public(is_public):
|
|||
|
||||
|
||||
def process_note_release(rel_data):
|
||||
return Release(rel_data)
|
||||
return ProductRelease(**rel_data)
|
||||
|
||||
|
||||
FIELD_PROCESSORS = {
|
||||
'release_date': parse_date,
|
||||
'created': parse_datetime,
|
||||
'modified': parse_datetime,
|
||||
'notes': process_notes,
|
||||
'is_public': process_is_public,
|
||||
'note': process_markdown,
|
||||
'text': process_markdown,
|
||||
'system_requirements': process_markdown,
|
||||
'fixed_in_release': process_note_release,
|
||||
}
|
||||
|
||||
|
||||
class ReleaseNotFound(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class RNModel(object):
|
||||
def __init__(self, data):
|
||||
for key, value in data.items():
|
||||
|
@ -111,45 +96,50 @@ class NotesField(JSONField):
|
|||
|
||||
|
||||
class ProductReleaseQuerySet(models.QuerySet):
|
||||
def public(self):
|
||||
if settings.DEV:
|
||||
return self.all()
|
||||
|
||||
return self.filter(is_public=True)
|
||||
|
||||
def product(self, product_name, channel_name=None):
|
||||
def product(self, product_name, channel_name=None, version=None):
|
||||
if product_name.lower() == 'firefox extended support release':
|
||||
product_name = 'firefox'
|
||||
channel_name = 'esr'
|
||||
q = self.filter(product__iexact=product_name)
|
||||
if channel_name:
|
||||
q = q.filter(channel__iexact=channel_name)
|
||||
if version:
|
||||
q = q.filter(version=version)
|
||||
|
||||
return q
|
||||
|
||||
|
||||
class ProductReleaseManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return ProductReleaseQuerySet(self.model, using=self._db)
|
||||
qs = ProductReleaseQuerySet(self.model, using=self._db)
|
||||
if settings.DEV:
|
||||
return qs
|
||||
|
||||
def public(self):
|
||||
return self.get_queryset().public()
|
||||
return qs.filter(is_public=True)
|
||||
|
||||
def product(self, product_name, channel_name=None):
|
||||
return self.get_queryset().product(product_name, channel_name)
|
||||
def product(self, product_name, channel_name=None, version=None):
|
||||
return self.get_queryset().product(product_name, channel_name, version)
|
||||
|
||||
def refresh(self):
|
||||
count = 0
|
||||
release_objs = []
|
||||
rn_path = os.path.join(settings.RELEASE_NOTES_PATH, 'releases')
|
||||
with transaction.atomic(using=self.db):
|
||||
self.all().delete()
|
||||
releases = glob(os.path.join(release_notes_path(), '*.json'))
|
||||
releases = glob(os.path.join(rn_path, '*.json'))
|
||||
for release_file in releases:
|
||||
with codecs.open(release_file, 'r', encoding='utf-8') as rel_fh:
|
||||
data = json.load(rel_fh)
|
||||
# doing this to simplify queries for Firefox since it is always
|
||||
# looked up with product=Firefox and relies on the version number
|
||||
# and channel to determine ESR.
|
||||
if data['product'] == 'Firefox Extended Support Release':
|
||||
data['product'] = 'Firefox'
|
||||
data['channel'] = 'ESR'
|
||||
self.create(**data)
|
||||
count += 1
|
||||
release_objs.append(ProductRelease(**data))
|
||||
|
||||
return count
|
||||
self.bulk_create(release_objs)
|
||||
|
||||
return len(release_objs)
|
||||
|
||||
|
||||
class ProductRelease(models.Model):
|
||||
|
@ -175,27 +165,12 @@ class ProductRelease(models.Model):
|
|||
|
||||
objects = ProductReleaseManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ['-release_date']
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
class Release(RNModel):
|
||||
CHANNELS = ['release', 'esr', 'beta', 'aurora', 'nightly']
|
||||
product = None
|
||||
channel = None
|
||||
version = None
|
||||
slug = None
|
||||
title = None
|
||||
release_date = None
|
||||
text = ''
|
||||
is_public = True
|
||||
bug_list = None
|
||||
bug_search_url = None
|
||||
system_requirements = None
|
||||
created = None
|
||||
modified = None
|
||||
notes = None
|
||||
|
||||
@cached_property
|
||||
def major_version(self):
|
||||
return str(self.version_obj.major)
|
||||
|
@ -258,13 +233,9 @@ class Release(RNModel):
|
|||
channel and major version with the highest minor version,
|
||||
or None if no such releases exist
|
||||
"""
|
||||
major_version_file_id = get_file_id(product, self.channel, self.major_version + '.*')
|
||||
releases = glob(os.path.join(release_notes_path(), major_version_file_id + '.json'))
|
||||
releases = ProductRelease.objects.product(product, self.channel).filter(version__startswith='%s.' % self.major_version)
|
||||
if releases:
|
||||
releases = [get_release_from_file(fn) for fn in releases]
|
||||
releases = [r for r in releases if r.is_public]
|
||||
if releases:
|
||||
return sorted(releases, reverse=True, key=attrgetter('version_obj'))[0]
|
||||
return sorted(releases, reverse=True, key=attrgetter('version_obj'))[0]
|
||||
|
||||
return None
|
||||
|
||||
|
@ -277,142 +248,47 @@ class Release(RNModel):
|
|||
return self.equivalent_release_for_product('Firefox')
|
||||
|
||||
|
||||
@memoize(LONG_RN_CACHE_TIMEOUT)
|
||||
def get_release(product, version, channel=None):
|
||||
channels = [channel] if channel else Release.CHANNELS
|
||||
channels = [channel] if channel else ProductRelease.CHANNELS
|
||||
if product.lower() == 'firefox extended support release':
|
||||
product = 'firefox'
|
||||
channels = ['esr']
|
||||
for channel in channels:
|
||||
file_name = get_release_file_name(product, channel, version)
|
||||
if not file_name:
|
||||
try:
|
||||
return ProductRelease.objects.product(product, channel, version).get()
|
||||
except ProductRelease.DoesNotExist:
|
||||
continue
|
||||
|
||||
release = get_release_from_file(file_name)
|
||||
if release is not None:
|
||||
return release
|
||||
|
||||
raise ReleaseNotFound()
|
||||
|
||||
|
||||
def get_data_version():
|
||||
"""Add the etag from the repo to the cache keys.
|
||||
|
||||
This will ensure that the cache is invalidated when the repo is updated.
|
||||
"""
|
||||
etag_key = 'releasenotes:repo:etag'
|
||||
etag = cache.get(etag_key)
|
||||
if not etag:
|
||||
etag_file = os.path.join(release_notes_path(), '.latest-update-etag')
|
||||
if os.path.exists(etag_file):
|
||||
try:
|
||||
with codecs.open(etag_file) as fh:
|
||||
etag = fh.read().strip()
|
||||
cache.set(etag_key, etag, 60) # 1 min
|
||||
except IOError:
|
||||
etag = 'default'
|
||||
else:
|
||||
etag = 'default'
|
||||
|
||||
return etag
|
||||
|
||||
|
||||
def get_cache_key(key):
|
||||
"""Cache key returned will be a sha256 hash of the key and repo data version.
|
||||
|
||||
This ensures that we can use a long cache for the release files while still
|
||||
getting fast invalidation when we check the small repo data version file
|
||||
at most once per minute.
|
||||
"""
|
||||
return sha256('%s:%s' % (get_data_version(), key)).hexdigest()
|
||||
|
||||
|
||||
def get_release_from_file(file_name):
|
||||
cache_key = get_cache_key(file_name)
|
||||
release = cache.get(cache_key)
|
||||
if not release:
|
||||
release = get_release_from_file_system(file_name)
|
||||
if release:
|
||||
cache.set(cache_key, release, LONG_RN_CACHE_TIMEOUT)
|
||||
|
||||
return release
|
||||
|
||||
|
||||
def get_release_from_file_system(file_name):
|
||||
try:
|
||||
with codecs.open(file_name, 'r', encoding='utf-8') as rel_fh:
|
||||
return Release(json.load(rel_fh))
|
||||
except Exception:
|
||||
sentry_client.captureException()
|
||||
return None
|
||||
|
||||
|
||||
def get_release_file_name(product, channel, version):
|
||||
file_id = get_file_id(product, channel, version)
|
||||
file_name = os.path.join(release_notes_path(), '{}.json'.format(file_id))
|
||||
if os.path.exists(file_name):
|
||||
return file_name
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_file_id(product, channel, version):
|
||||
product = slugify(product)
|
||||
channel = channel.lower()
|
||||
if product == 'firefox-extended-support-release':
|
||||
product = 'firefox'
|
||||
channel = 'esr'
|
||||
return '-'.join([product, version, channel])
|
||||
|
||||
|
||||
def get_release_or_404(version, product):
|
||||
try:
|
||||
release = get_release(product, version)
|
||||
except ReleaseNotFound:
|
||||
raise Http404
|
||||
|
||||
if not release.is_public:
|
||||
release = get_release(product, version)
|
||||
if release is None:
|
||||
raise Http404
|
||||
|
||||
return release
|
||||
|
||||
|
||||
def get_all_releases(product, channel='release'):
|
||||
file_prefix = get_file_id(product, channel, '*')
|
||||
cache_key = get_cache_key('all:%s:%s' % (product, channel))
|
||||
releases = cache.get(cache_key)
|
||||
product_prefix = file_prefix.split('*')[0]
|
||||
# ensure only files for the specific product are returned
|
||||
# without this the file glob would match e.g. "firefox-for-android-56.0-release.json"
|
||||
# when the glob was "firefox-*-release.json"
|
||||
product_re = re.compile(r'%s\d' % product_prefix)
|
||||
if not releases:
|
||||
releases = glob(os.path.join(release_notes_path(), file_prefix + '.json'))
|
||||
if releases:
|
||||
releases = (get_release_from_file(r) for r in releases if product_re.search(r))
|
||||
releases = sorted((r for r in releases if r.is_public),
|
||||
key=attrgetter('release_date'), reverse=True)
|
||||
if releases:
|
||||
cache.set(cache_key, releases, LONG_RN_CACHE_TIMEOUT)
|
||||
|
||||
return releases
|
||||
@memoize(LONG_RN_CACHE_TIMEOUT)
|
||||
def get_releases(product, channel, num_results=10):
|
||||
return ProductRelease.objects.product(product, channel)[:num_results]
|
||||
|
||||
|
||||
def get_releases_or_404(product, channel):
|
||||
releases = get_all_releases(product, channel)
|
||||
def get_releases_or_404(product, channel, num_results=10):
|
||||
releases = get_releases(product, channel, num_results)
|
||||
if releases:
|
||||
return releases
|
||||
|
||||
raise Http404
|
||||
|
||||
|
||||
@memoize(LONG_RN_CACHE_TIMEOUT)
|
||||
def get_latest_release(product, channel='release'):
|
||||
cache_key = get_cache_key('latest:%s:%s' % (product, channel))
|
||||
release = cache.get(cache_key)
|
||||
if not release:
|
||||
releases = get_all_releases(product, channel)
|
||||
if releases:
|
||||
release = releases[0]
|
||||
cache.set(cache_key, release, LONG_RN_CACHE_TIMEOUT)
|
||||
try:
|
||||
release = ProductRelease.objects.product(product, channel)[0]
|
||||
except IndexError:
|
||||
release = None
|
||||
|
||||
return release
|
||||
|
||||
|
|
|
@ -2,5 +2,8 @@
|
|||
"product": "Firefox",
|
||||
"channel": "Release",
|
||||
"version": "56.0",
|
||||
"release_date": "2017-08-02",
|
||||
"created": "2017-03-21T13:19:13.668000+00:00",
|
||||
"modified": "2017-03-21T13:19:13.668000+00:00",
|
||||
"is_public": true
|
||||
}
|
||||
|
|
|
@ -3,5 +3,7 @@
|
|||
"channel": "Release",
|
||||
"version": "56.0",
|
||||
"release_date": "2017-08-02",
|
||||
"created": "2017-03-21T13:19:13.668000+00:00",
|
||||
"modified": "2017-03-21T13:19:13.668000+00:00",
|
||||
"is_public": true
|
||||
}
|
||||
|
|
|
@ -3,5 +3,7 @@
|
|||
"channel": "Release",
|
||||
"version": "56.0.1",
|
||||
"release_date": "2017-08-03",
|
||||
"created": "2017-03-21T13:19:13.668000+00:00",
|
||||
"modified": "2017-03-21T13:19:13.668000+00:00",
|
||||
"is_public": true
|
||||
}
|
||||
|
|
|
@ -3,5 +3,7 @@
|
|||
"channel": "Release",
|
||||
"version": "56.0.2",
|
||||
"release_date": "2017-08-04",
|
||||
"created": "2017-03-21T13:19:13.668000+00:00",
|
||||
"modified": "2017-03-21T13:19:13.668000+00:00",
|
||||
"is_public": true
|
||||
}
|
||||
|
|
|
@ -3,5 +3,7 @@
|
|||
"channel": "Release",
|
||||
"version": "56.0.3",
|
||||
"release_date": "2017-08-05",
|
||||
"created": "2017-03-21T13:19:13.668000+00:00",
|
||||
"modified": "2017-03-21T13:19:13.668000+00:00",
|
||||
"is_public": false
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
import time
|
||||
|
||||
from mock import Mock, patch
|
||||
|
||||
from django.core.cache import caches
|
||||
|
||||
from bedrock.mozorg.tests import TestCase
|
||||
from bedrock.releasenotes import utils
|
||||
|
||||
|
||||
release_cache = caches['release-notes']
|
||||
|
||||
|
||||
@patch.object(utils, 'GitRepo')
|
||||
class TestGetDataVersion(TestCase):
|
||||
def test_get_data_version(self, git_mock):
|
||||
git_mock().get_db_latest.return_value = 'El Dudarino'
|
||||
assert utils.get_data_version() == 'El Dudarino'
|
||||
|
||||
def test_get_data_version_not_found(self, git_mock):
|
||||
git_mock().get_db_latest.return_value = None
|
||||
assert utils.get_data_version() == 'default'
|
||||
|
||||
|
||||
@patch.object(utils, 'get_data_version')
|
||||
class TestReleaseMemoizer(TestCase):
|
||||
def setUp(self):
|
||||
release_cache.clear()
|
||||
|
||||
def test_calls_version_after_cache_timeout(self, gdv_cache):
|
||||
def mem_func():
|
||||
pass
|
||||
|
||||
gdv_cache.return_value = 'dude'
|
||||
memoizer = utils.ReleaseMemoizer(version_timeout=0.1)
|
||||
memoizer._memoize_version(mem_func)
|
||||
memoizer._memoize_version(mem_func)
|
||||
time.sleep(0.2)
|
||||
memoizer._memoize_version(mem_func)
|
||||
assert gdv_cache.call_count == 2
|
||||
|
||||
def test_calls_function_when_version_changes(self, gdv_cache):
|
||||
"""Memoized function should be called after timeout or version change.
|
||||
|
||||
Also demonstrates that even None return values are cached."""
|
||||
counter = Mock()
|
||||
memoizer = utils.ReleaseMemoizer(version_timeout=0.1)
|
||||
gdv_cache.side_effect = ['thing1', 'thing1', 'thing2', 'thing2']
|
||||
|
||||
@memoizer.memoize(1)
|
||||
def mem_func():
|
||||
counter()
|
||||
return None
|
||||
|
||||
mem_func()
|
||||
# cached
|
||||
mem_func()
|
||||
time.sleep(0.2)
|
||||
# cached, but checked the version
|
||||
mem_func()
|
||||
time.sleep(0.2)
|
||||
# not cached because the version changed
|
||||
mem_func()
|
||||
time.sleep(1)
|
||||
# not cached because timeout
|
||||
mem_func()
|
||||
|
||||
# function should have been called 3 times
|
||||
assert counter.call_count == 3
|
||||
# version should have been called 4 times
|
||||
assert gdv_cache.call_count == 4
|
|
@ -16,7 +16,7 @@ from pyquery import PyQuery as pq
|
|||
from bedrock.firefox.firefox_details import FirefoxDesktop
|
||||
from bedrock.mozorg.tests import TestCase
|
||||
from bedrock.releasenotes import views
|
||||
from bedrock.releasenotes.models import Release, ReleaseNotFound
|
||||
from bedrock.releasenotes.models import ProductRelease
|
||||
from bedrock.thunderbird.details import ThunderbirdDesktop
|
||||
|
||||
|
||||
|
@ -30,6 +30,7 @@ RELEASES_PATH = str(TESTS_PATH)
|
|||
@override_settings(RELEASE_NOTES_PATH=RELEASES_PATH)
|
||||
class TestReleaseViews(TestCase):
|
||||
def setUp(self):
|
||||
ProductRelease.objects.refresh()
|
||||
caches['release-notes'].clear()
|
||||
self.activate('en-US')
|
||||
self.factory = RequestFactory()
|
||||
|
@ -55,7 +56,7 @@ class TestReleaseViews(TestCase):
|
|||
eq_(views.get_release_or_404('version', 'product'),
|
||||
get_release.return_value)
|
||||
get_release.assert_called_with('product', 'version')
|
||||
get_release.side_effect = ReleaseNotFound
|
||||
get_release.return_value = None
|
||||
with self.assertRaises(Http404):
|
||||
views.get_release_or_404('version', 'product')
|
||||
|
||||
|
@ -82,8 +83,7 @@ class TestReleaseViews(TestCase):
|
|||
"""
|
||||
mock_release = get_release_or_404.return_value
|
||||
mock_release.major_version = '34'
|
||||
mock_release.notes.return_value = ([Release({}), Release({})],
|
||||
[Release({}), Release({})])
|
||||
mock_release.notes.return_value = []
|
||||
|
||||
views.release_notes(self.request, '27.0')
|
||||
get_release_or_404.assert_called_with('27.0', 'Firefox')
|
||||
|
@ -165,9 +165,9 @@ class TestReleaseViews(TestCase):
|
|||
@patch('bedrock.releasenotes.models.get_release')
|
||||
def test_non_public_release(self, get_release):
|
||||
"""
|
||||
Should raise 404 if not release.is_public and not settings.DEV
|
||||
Should raise 404 if release is not public and not settings.DEV
|
||||
"""
|
||||
get_release.return_value = Release({'is_public': False})
|
||||
get_release.return_value = None
|
||||
with self.assertRaises(Http404):
|
||||
views.get_release_or_404('42', 'Firefox')
|
||||
|
||||
|
@ -305,7 +305,7 @@ class TestNotesRedirects(TestCase):
|
|||
eq_(response['Location'], 'http://testserver/en-US' + url_to)
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Firefox', version='22.0', channel='Release'))))
|
||||
Mock(return_value=ProductRelease(product='Firefox', version='22.0', channel='Release')))
|
||||
def test_desktop_release_version(self):
|
||||
self._test('/firefox/notes/',
|
||||
'/firefox/22.0/releasenotes/')
|
||||
|
@ -313,49 +313,49 @@ class TestNotesRedirects(TestCase):
|
|||
'/firefox/22.0/releasenotes/')
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Firefox', version='23.0beta', channel='Beta'))))
|
||||
Mock(return_value=ProductRelease(product='Firefox', version='23.0beta', channel='Beta')))
|
||||
def test_desktop_beta_version(self):
|
||||
self._test('/firefox/beta/notes/',
|
||||
'/firefox/23.0beta/releasenotes/')
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Firefox', version='23.0beta', channel='Beta'))))
|
||||
Mock(return_value=ProductRelease(product='Firefox', version='23.0beta', channel='Beta')))
|
||||
def test_desktop_developer_version(self):
|
||||
self._test('/firefox/developer/notes/',
|
||||
'/firefox/23.0beta/releasenotes/')
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Firefox', version='24.2.0', channel='ESR'))))
|
||||
Mock(return_value=ProductRelease(product='Firefox', version='24.2.0', channel='ESR')))
|
||||
def test_desktop_esr_version(self):
|
||||
self._test('/firefox/organizations/notes/',
|
||||
'/firefox/24.2.0/releasenotes/')
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Firefox for Android', version='22.0', channel='Release'))))
|
||||
Mock(return_value=ProductRelease(product='Firefox for Android', version='22.0', channel='Release')))
|
||||
def test_android_release_version(self):
|
||||
self._test('/firefox/android/notes/',
|
||||
'/firefox/android/22.0/releasenotes/')
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Firefox for Android', version='23.0beta', channel='Beta'))))
|
||||
Mock(return_value=ProductRelease(product='Firefox for Android', version='23.0beta', channel='Beta')))
|
||||
def test_android_beta_version(self):
|
||||
self._test('/firefox/android/beta/notes/',
|
||||
'/firefox/android/23.0beta/releasenotes/')
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Firefox for Android', version='24.0a2', channel='Aurora'))))
|
||||
Mock(return_value=ProductRelease(product='Firefox for Android', version='24.0a2', channel='Aurora')))
|
||||
def test_android_aurora_version(self):
|
||||
self._test('/firefox/android/aurora/notes/',
|
||||
'/firefox/android/24.0a2/auroranotes/')
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Firefox for iOS', version='1.4', channel='Release'))))
|
||||
Mock(return_value=ProductRelease(product='Firefox for iOS', version='1.4', channel='Release')))
|
||||
def test_ios_release_version(self):
|
||||
self._test('/firefox/ios/notes/',
|
||||
'/firefox/ios/1.4/releasenotes/')
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Thunderbird', version='22.0', channel='Release'))))
|
||||
Mock(return_value=ProductRelease(product='Thunderbird', version='22.0', channel='Release')))
|
||||
def test_thunderbird_release_version(self):
|
||||
self._test('/thunderbird/notes/',
|
||||
'/thunderbird/22.0/releasenotes/')
|
||||
|
@ -363,13 +363,13 @@ class TestNotesRedirects(TestCase):
|
|||
'/thunderbird/22.0/releasenotes/')
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Thunderbird', version='41.0beta', channel='Beta'))))
|
||||
Mock(return_value=ProductRelease(product='Thunderbird', version='41.0beta', channel='Beta')))
|
||||
def test_thunderbird_beta_version(self):
|
||||
self._test('/thunderbird/beta/notes/',
|
||||
'/thunderbird/41.0beta/releasenotes/')
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Thunderbird', version='41.0beta', channel='Beta'))))
|
||||
Mock(return_value=ProductRelease(product='Thunderbird', version='41.0beta', channel='Beta')))
|
||||
def test_thunderbird_earlybird_version(self):
|
||||
self._test('/thunderbird/earlybird/notes/',
|
||||
'/thunderbird/41.0beta/releasenotes/')
|
||||
|
@ -384,31 +384,31 @@ class TestSysreqRedirect(TestCase):
|
|||
eq_(response['Location'], 'http://testserver/en-US' + url_to)
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Firefox', version='22.0', channel='Release'))))
|
||||
Mock(return_value=ProductRelease(product='Firefox', version='22.0', channel='Release')))
|
||||
def test_desktop_release_version(self):
|
||||
self._test('/firefox/system-requirements/',
|
||||
'/firefox/22.0/system-requirements/')
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Firefox', version='23.0beta', channel='Beta'))))
|
||||
Mock(return_value=ProductRelease(product='Firefox', version='23.0beta', channel='Beta')))
|
||||
def test_desktop_beta_version(self):
|
||||
self._test('/firefox/beta/system-requirements/',
|
||||
'/firefox/23.0beta/system-requirements/')
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Firefox', version='23.0beta', channel='Beta'))))
|
||||
Mock(return_value=ProductRelease(product='Firefox', version='23.0beta', channel='Beta')))
|
||||
def test_desktop_developer_version(self):
|
||||
self._test('/firefox/developer/system-requirements/',
|
||||
'/firefox/23.0beta/system-requirements/')
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Firefox', version='24.2.0', channel='ESR'))))
|
||||
Mock(return_value=ProductRelease(product='Firefox', version='24.2.0', channel='ESR')))
|
||||
def test_desktop_esr_version(self):
|
||||
self._test('/firefox/organizations/system-requirements/',
|
||||
'/firefox/24.2.0/system-requirements/')
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Thunderbird', version='22.0', channel='Release'))))
|
||||
Mock(return_value=ProductRelease(product='Thunderbird', version='22.0', channel='Release')))
|
||||
def test_thunderbird_release_version(self):
|
||||
self._test('/thunderbird/system-requirements/',
|
||||
'/thunderbird/22.0/system-requirements/')
|
||||
|
@ -416,7 +416,7 @@ class TestSysreqRedirect(TestCase):
|
|||
'/thunderbird/22.0/system-requirements/')
|
||||
|
||||
@patch('bedrock.releasenotes.views.get_latest_release_or_404',
|
||||
Mock(return_value=Release(dict(product='Thunderbird', version='41.0beta', channel='Beta'))))
|
||||
Mock(return_value=ProductRelease(product='Thunderbird', version='41.0beta', channel='Beta')))
|
||||
def test_thunderbird_beta_version(self):
|
||||
self._test('/thunderbird/beta/system-requirements/',
|
||||
'/thunderbird/41.0beta/system-requirements/')
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
from itertools import chain
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import caches
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from mock import call, patch, Mock
|
||||
from mock import call, patch
|
||||
from pathlib2 import Path
|
||||
|
||||
from bedrock.mozorg.tests import TestCase
|
||||
|
@ -25,8 +24,7 @@ class TestReleaseNotesURL(TestCase):
|
|||
"""
|
||||
Should return the results of reverse with the correct args
|
||||
"""
|
||||
release = models.Release(dict(
|
||||
channel='Aurora', version='42.0a2', product='Firefox for Android'))
|
||||
release = models.ProductRelease(channel='Aurora', version='42.0a2', product='Firefox for Android')
|
||||
assert release.get_absolute_url() == mock_reverse.return_value
|
||||
mock_reverse.assert_called_with('firefox.android.releasenotes', args=['42.0a2', 'aurora'])
|
||||
|
||||
|
@ -34,7 +32,7 @@ class TestReleaseNotesURL(TestCase):
|
|||
"""
|
||||
Should return the results of reverse with the correct args
|
||||
"""
|
||||
release = models.Release(dict(version='42.0', product='Firefox'))
|
||||
release = models.ProductRelease(version='42.0', product='Firefox')
|
||||
assert release.get_absolute_url() == mock_reverse.return_value
|
||||
mock_reverse.assert_called_with('firefox.desktop.releasenotes', args=['42.0', 'release'])
|
||||
|
||||
|
@ -42,6 +40,7 @@ class TestReleaseNotesURL(TestCase):
|
|||
@override_settings(RELEASE_NOTES_PATH=RELEASES_PATH, DEV=False)
|
||||
class TestReleaseModel(TestCase):
|
||||
def setUp(self):
|
||||
models.ProductRelease.objects.refresh()
|
||||
release_cache.clear()
|
||||
|
||||
def test_release_major_version(self):
|
||||
|
@ -64,14 +63,7 @@ class TestReleaseModel(TestCase):
|
|||
assert android.product == 'Firefox for Android'
|
||||
|
||||
def test_equivalent_release_for_product_none_match(self):
|
||||
rel = models.get_release('firefox', '45.0esr', 'esr')
|
||||
android = rel.equivalent_release_for_product('Firefox for Android')
|
||||
assert android is None
|
||||
|
||||
@patch.object(models, 'get_release_from_file')
|
||||
def test_equivalent_release_for_product_no_public_match(self, grff_mock):
|
||||
rel = models.Release(dict(product='Firefox', version='56.0', is_public=True, channel='Release'))
|
||||
grff_mock.return_value = models.Release(dict(product='Firefox for Android', is_public=False, version='56.0.2', channel='Release'))
|
||||
rel = models.get_release('firefox', '45.0esr')
|
||||
android = rel.equivalent_release_for_product('Firefox for Android')
|
||||
assert android is None
|
||||
|
||||
|
@ -105,258 +97,52 @@ class TestReleaseModel(TestCase):
|
|||
assert note.id == 787203
|
||||
|
||||
@override_settings(DEV=False)
|
||||
def test_is_public_field_processor(self):
|
||||
"""Should return the real value when DEV is false."""
|
||||
rel = models.get_release('firefox for android', '56.0.3')
|
||||
assert not rel.is_public
|
||||
def test_is_public_query(self):
|
||||
"""Should not return the release value when DEV is false.
|
||||
|
||||
Should also only include public notes."""
|
||||
assert models.get_release('firefox for android', '56.0.3') is None
|
||||
rel = models.get_release('firefox', '57.0a1')
|
||||
assert len(rel.notes) == 4
|
||||
|
||||
@override_settings(DEV=True)
|
||||
def test_is_public_field_processor_dev_true(self):
|
||||
"""Should always be true when DEV is true."""
|
||||
rel = models.get_release('firefox for android', '56.0.3')
|
||||
assert rel.is_public
|
||||
models.get_release('firefox for android', '56.0.3')
|
||||
rel = models.get_release('firefox', '57.0a1')
|
||||
assert len(rel.notes) == 6
|
||||
|
||||
|
||||
@patch.object(models, 'get_release_file_name')
|
||||
@patch.object(models, 'get_release_from_file')
|
||||
@patch.object(models.ProductRelease, 'objects')
|
||||
class TestGetRelease(TestCase):
|
||||
def test_get_release(self, grff_mock, grfn_mock):
|
||||
grfn_mock.return_value = 'dude'
|
||||
grff_mock.return_value = 'dude is released'
|
||||
ret = models.get_release('Firefox', '57.0')
|
||||
grfn_mock.assert_called_with('Firefox', models.Release.CHANNELS[0], '57.0')
|
||||
grff_mock.assert_called_with('dude')
|
||||
assert ret == 'dude is released'
|
||||
def setUp(self):
|
||||
release_cache.clear()
|
||||
|
||||
def test_get_release_esr(self, grff_mock, grfn_mock):
|
||||
grfn_mock.return_value = 'dude'
|
||||
grff_mock.return_value = 'dude is released'
|
||||
ret = models.get_release('Firefox Extended Support Release', '51.0')
|
||||
grfn_mock.assert_called_with('firefox', 'esr', '51.0')
|
||||
grff_mock.assert_called_with('dude')
|
||||
assert ret == 'dude is released'
|
||||
def test_get_release(self, manager_mock):
|
||||
manager_mock.product().get.return_value = 'dude is released'
|
||||
assert models.get_release('Firefox', '57.0') == 'dude is released'
|
||||
manager_mock.product.assert_called_with('Firefox', models.ProductRelease.CHANNELS[0], '57.0')
|
||||
|
||||
def test_get_release_none_match(self, grff_mock, grfn_mock):
|
||||
def test_get_release_esr(self, manager_mock):
|
||||
manager_mock.product().get.return_value = 'dude is released'
|
||||
assert models.get_release('Firefox Extended Support Release', '51.0') == 'dude is released'
|
||||
manager_mock.product.assert_called_with('Firefox Extended Support Release', 'esr', '51.0')
|
||||
|
||||
def test_get_release_none_match(self, manager_mock):
|
||||
"""Make sure the proper exception is raised if no file matches the query"""
|
||||
grfn_mock.return_value = None
|
||||
with self.assertRaises(models.ReleaseNotFound):
|
||||
models.get_release('Firefox', '57.0')
|
||||
manager_mock.product().get.side_effect = models.ProductRelease.DoesNotExist
|
||||
assert models.get_release('Firefox', '57.0') is None
|
||||
|
||||
expected_calls = [call('Firefox', ch, '57.0') for ch in models.Release.CHANNELS]
|
||||
grfn_mock.assert_has_calls(expected_calls)
|
||||
|
||||
def test_get_release_none_load(self, grff_mock, grfn_mock):
|
||||
"""Make sure the proper exception is raised if no file successfully loads"""
|
||||
grfn_mock.return_value = 'dude'
|
||||
grff_mock.return_value = None
|
||||
with self.assertRaises(models.ReleaseNotFound):
|
||||
models.get_release('Firefox', '57.0')
|
||||
|
||||
expected_calls = [call('Firefox', ch, '57.0') for ch in models.Release.CHANNELS]
|
||||
grfn_mock.assert_has_calls(expected_calls)
|
||||
expected_calls = chain.from_iterable((call('Firefox', ch, '57.0'), call().get()) for ch in models.ProductRelease.CHANNELS)
|
||||
manager_mock.product.assert_has_calls(expected_calls)
|
||||
|
||||
|
||||
@override_settings(RELEASE_NOTES_PATH=RELEASES_PATH)
|
||||
@patch.object(models, 'cache')
|
||||
@patch.object(models, 'get_cache_key')
|
||||
@patch.object(models, 'get_release_from_file_system')
|
||||
class TestGetReleaseFromFile(TestCase):
|
||||
def test_get_release_from_file(self, grffs_mock, cache_key_mock, cache_mock):
|
||||
cache_mock.get.return_value = 'dude'
|
||||
assert models.get_release_from_file('walter') == 'dude'
|
||||
cache_key_mock.assert_called_with('walter')
|
||||
grffs_mock.assert_not_called()
|
||||
|
||||
def test_get_release_from_file_no_cache(self, grffs_mock, cache_key_mock, cache_mock):
|
||||
cache_mock.get.return_value = None
|
||||
grffs_mock.return_value = 'donnie'
|
||||
assert models.get_release_from_file('walter') == 'donnie'
|
||||
cache_key_mock.assert_called_with('walter')
|
||||
grffs_mock.assert_called_with('walter')
|
||||
cache_mock.set.assert_called_with(cache_key_mock(), 'donnie', models.LONG_RN_CACHE_TIMEOUT)
|
||||
|
||||
|
||||
@override_settings(RELEASE_NOTES_PATH=RELEASES_PATH)
|
||||
class TestGetReleaseFromFileSystem(TestCase):
|
||||
def test_get_release_from_file_system(self):
|
||||
filename = models.get_release_file_name('firefox', 'nightly', '57.0a1')
|
||||
rel = models.get_release_from_file_system(filename)
|
||||
assert rel.product == 'Firefox'
|
||||
assert rel.channel == 'Nightly'
|
||||
assert rel.version == '57.0a1'
|
||||
|
||||
@patch.object(models, 'codecs')
|
||||
def test_get_release_from_file_system_exception(self, codecs_mock):
|
||||
codecs_mock.open.side_effect = IOError()
|
||||
assert models.get_release_from_file_system('does-not-exist') is None
|
||||
|
||||
|
||||
@patch('os.path.exists')
|
||||
@patch.object(models, 'get_file_id')
|
||||
class TestGetReleaseFileName(TestCase):
|
||||
def test_get_release_file_name(self, gfi_mock, exists_mock):
|
||||
gfi_mock.return_value = 'dude'
|
||||
exists_mock.return_value = True
|
||||
file_name = os.path.join(settings.RELEASE_NOTES_PATH, 'releases', 'dude.json')
|
||||
assert models.get_release_file_name('firefox', 'nightly', '57.0a1') == file_name
|
||||
gfi_mock.assert_called_with('firefox', 'nightly', '57.0a1')
|
||||
exists_mock.assert_called_with(file_name)
|
||||
|
||||
def test_get_release_file_name_no_exists(self, gfi_mock, exists_mock):
|
||||
gfi_mock.return_value = 'dude'
|
||||
exists_mock.return_value = False
|
||||
file_name = os.path.join(settings.RELEASE_NOTES_PATH, 'releases', 'dude.json')
|
||||
assert models.get_release_file_name('firefox', 'nightly', '57.0a1') is None
|
||||
gfi_mock.assert_called_with('firefox', 'nightly', '57.0a1')
|
||||
exists_mock.assert_called_with(file_name)
|
||||
|
||||
|
||||
class TestGetFileID(TestCase):
|
||||
def test_get_file_id(self):
|
||||
assert models.get_file_id('Firefox', 'Nightly', '57.0a1') == 'firefox-57.0a1-nightly'
|
||||
assert models.get_file_id('Firefox', 'Release', '57.0') == 'firefox-57.0-release'
|
||||
assert models.get_file_id('Firefox Extended Support Release', 'ESR', '52.0') == 'firefox-52.0-esr'
|
||||
assert models.get_file_id('Firefox for Android', 'Beta', '57.0b2') == 'firefox-for-android-57.0b2-beta'
|
||||
|
||||
|
||||
@override_settings(RELEASE_NOTES_PATH=RELEASES_PATH)
|
||||
@patch.object(models, 'cache')
|
||||
class TestGetDataVersion(TestCase):
|
||||
def test_get_data_version(self, cache_mock):
|
||||
cache_mock.get.return_value = None
|
||||
# value from the test data in .latest-update-etag
|
||||
assert models.get_data_version() == '"bae656422b8d046543540f42b1658938f"'
|
||||
cache_mock.set.assert_called_with('releasenotes:repo:etag', '"bae656422b8d046543540f42b1658938f"', 60)
|
||||
|
||||
def test_get_data_version_cache_hit(self, cache_mock):
|
||||
cache_mock.get.return_value = 'dude'
|
||||
assert models.get_data_version() == 'dude'
|
||||
cache_mock.set.assert_not_called()
|
||||
|
||||
@patch.object(models, 'os')
|
||||
def test_get_data_version_file_not_found(self, os_mock, cache_mock):
|
||||
cache_mock.get.return_value = None
|
||||
os_mock.path.exists.return_value = False
|
||||
assert models.get_data_version() == 'default'
|
||||
cache_mock.set.assert_not_called()
|
||||
|
||||
@patch.object(models, 'codecs')
|
||||
def test_get_data_version_io_error(self, open_mock, cache_mock):
|
||||
cache_mock.get.return_value = None
|
||||
open_mock.open.side_effect = IOError
|
||||
assert models.get_data_version() == 'default'
|
||||
cache_mock.set.assert_not_called()
|
||||
|
||||
|
||||
@patch.object(models, 'get_data_version')
|
||||
@patch.object(models, 'sha256')
|
||||
class TestGetCacheKey(TestCase):
|
||||
def test_get_cache_key(self, sha_mock, gdv_mock):
|
||||
gdv_mock.return_value = 'dude'
|
||||
assert models.get_cache_key('abide') == sha_mock.return_value.hexdigest()
|
||||
sha_mock.assert_called_with('dude:abide')
|
||||
|
||||
|
||||
@override_settings(DEV=False)
|
||||
@patch.object(models, 'cache')
|
||||
@patch.object(models, 'glob')
|
||||
@patch.object(models, 'get_release_from_file')
|
||||
@patch.object(models, 'get_cache_key', Mock(return_value='dude'))
|
||||
class TestGetAllReleases(TestCase):
|
||||
def test_get_all_releases(self, grff_mock, glob_mock, cache_mock):
|
||||
releases = []
|
||||
globs = []
|
||||
calls = []
|
||||
for i in range(5):
|
||||
globs.append('firefox-%s.json' % (i + 1))
|
||||
calls.append(call(globs[-1]))
|
||||
releases.append(models.Release(dict(product='Firefox',
|
||||
channel='Release',
|
||||
is_public=True,
|
||||
version='56.0.%s' % (i + 1),
|
||||
release_date='2017-01-0%s' % (i + 1))))
|
||||
|
||||
grff_mock.side_effect = releases
|
||||
glob_mock.return_value = globs
|
||||
cache_mock.get.return_value = None
|
||||
reversed_releases = list(reversed(releases))
|
||||
assert models.get_all_releases('firefox', 'release') == reversed_releases
|
||||
grff_mock.assert_has_calls(calls)
|
||||
cache_mock.get.assert_called_with('dude')
|
||||
cache_mock.set.assert_called_with('dude', reversed_releases, models.LONG_RN_CACHE_TIMEOUT)
|
||||
|
||||
def test_get_all_releases_only_product(self, grff_mock, glob_mock, cache_mock):
|
||||
"""Should only return the specific product asked for"""
|
||||
releases = []
|
||||
globs = []
|
||||
calls = []
|
||||
for i in range(2):
|
||||
globs.append('firefox-%s.json' % (i + 1))
|
||||
calls.append(call(globs[-1]))
|
||||
releases.append(models.Release(dict(product='Firefox',
|
||||
channel='Release',
|
||||
is_public=True,
|
||||
version='56.0.%s' % (i + 1),
|
||||
release_date='2017-01-0%s' % (i + 1))))
|
||||
# android ones are newer
|
||||
for i in range(2):
|
||||
globs.append('firefox-for-android-%s.json' % (i + 1))
|
||||
releases.append(models.Release(dict(product='Firefox For Android',
|
||||
channel='Release',
|
||||
is_public=True,
|
||||
version='56.0.%s' % (i + 1),
|
||||
release_date='2017-01-1%s' % (i + 1))))
|
||||
|
||||
grff_mock.side_effect = releases
|
||||
glob_mock.return_value = globs
|
||||
cache_mock.get.return_value = None
|
||||
reversed_releases = list(reversed(releases[:2]))
|
||||
assert models.get_all_releases('firefox', 'release') == reversed_releases
|
||||
grff_mock.assert_has_calls(calls)
|
||||
cache_mock.get.assert_called_with('dude')
|
||||
cache_mock.set.assert_called_with('dude', reversed_releases, models.LONG_RN_CACHE_TIMEOUT)
|
||||
|
||||
def test_get_all_releases_no_files(self, grff_mock, glob_mock, cache_mock):
|
||||
glob_mock.return_value = []
|
||||
cache_mock.get.return_value = None
|
||||
assert models.get_all_releases('firefox', 'release') == []
|
||||
cache_mock.get.assert_called_with('dude')
|
||||
cache_mock.set.assert_not_called()
|
||||
|
||||
def test_get_all_releases_none_public(self, grff_mock, glob_mock, cache_mock):
|
||||
releases = []
|
||||
globs = []
|
||||
calls = []
|
||||
for i in range(5):
|
||||
globs.append('firefox-%s.json' % (i + 1))
|
||||
calls.append(call(globs[-1]))
|
||||
releases.append(models.Release(dict(product='Firefox',
|
||||
channel='Release',
|
||||
is_public=False,
|
||||
version='56.0.%s' % (i + 1),
|
||||
release_date='2017-01-0%s' % (i + 1))))
|
||||
grff_mock.side_effect = releases
|
||||
glob_mock.return_value = globs
|
||||
cache_mock.get.return_value = None
|
||||
assert models.get_all_releases('firefox', 'release') == []
|
||||
grff_mock.assert_has_calls(calls)
|
||||
cache_mock.get.assert_called_with('dude')
|
||||
print cache_mock.set.mock_calls
|
||||
cache_mock.set.assert_not_called()
|
||||
|
||||
|
||||
@override_settings(RELEASE_NOTES_PATH=RELEASES_PATH)
|
||||
@patch.object(models, 'cache')
|
||||
@patch.object(models, 'get_all_releases')
|
||||
@override_settings(RELEASE_NOTES_PATH=RELEASES_PATH, DEV=False)
|
||||
class TestGetLatestRelease(TestCase):
|
||||
def test_latest_release(self, gar_mock, cache_mock):
|
||||
releases = [Mock(), Mock()]
|
||||
gar_mock.return_value = releases
|
||||
cache_mock.get.return_value = None
|
||||
assert models.get_latest_release('firefox', 'release') == releases[0]
|
||||
cache_mock.set.assert_called_with('cd1252c0b9e7db652d816a45c7c813696456bd124a64fc54452a73220831f081',
|
||||
releases[0], models.LONG_RN_CACHE_TIMEOUT)
|
||||
def setUp(self):
|
||||
models.ProductRelease.objects.refresh()
|
||||
release_cache.clear()
|
||||
|
||||
def test_latest_release(self):
|
||||
correct_release = models.get_release('firefox for android', '56.0.2')
|
||||
assert models.get_latest_release('firefox for android', 'release') == correct_release
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
from django.conf import settings
|
||||
from django.core.cache import caches
|
||||
|
||||
from memoize import Memoizer
|
||||
|
||||
from bedrock.utils.git import GitRepo
|
||||
|
||||
|
||||
def get_data_version():
|
||||
"""Add the git ref from the repo to the cache keys.
|
||||
|
||||
This will ensure that the cache is invalidated when the repo is updated.
|
||||
"""
|
||||
repo = GitRepo(settings.RELEASE_NOTES_PATH,
|
||||
settings.RELEASE_NOTES_REPO,
|
||||
branch_name=settings.RELEASE_NOTES_BRANCH)
|
||||
git_ref = repo.get_db_latest()
|
||||
if git_ref is None:
|
||||
git_ref = 'default'
|
||||
|
||||
return git_ref
|
||||
|
||||
|
||||
class ReleaseMemoizer(Memoizer):
|
||||
"""A memoizer class that uses the git hash as the version"""
|
||||
def __init__(self, version_timeout=300):
|
||||
self.version_timeout = version_timeout
|
||||
return super(ReleaseMemoizer, self).__init__(cache=caches['release-notes'])
|
||||
|
||||
def _memoize_make_version_hash(self):
|
||||
return get_data_version()
|
||||
|
||||
def _memoize_version(self, f, args=None, reset=False, delete=False, timeout=None):
|
||||
"""Use a shorter timeout for the version so that we can refresh based on git hash"""
|
||||
return super(ReleaseMemoizer, self)._memoize_version(f, args, reset, delete, self.version_timeout)
|
||||
|
||||
|
||||
memoizer = ReleaseMemoizer()
|
||||
memoize = memoizer.memoize
|
|
@ -180,7 +180,7 @@ def releases_index(request, product):
|
|||
def nightly_feed(request):
|
||||
"""Serve an Atom feed with the latest changes in Firefox Nightly"""
|
||||
notes = {}
|
||||
releases = get_releases_or_404('firefox', 'nightly')[0:5]
|
||||
releases = get_releases_or_404('firefox', 'nightly', 5)
|
||||
|
||||
for release in releases:
|
||||
link = reverse('firefox.desktop.releasenotes',
|
||||
|
@ -191,7 +191,7 @@ def nightly_feed(request):
|
|||
continue
|
||||
|
||||
if note.is_public and note.tag:
|
||||
note.link = link + '#note-' + str(note.id)
|
||||
note.link = '%s#note-%s' % (link, note.id)
|
||||
note.version = release.version
|
||||
notes[note.id] = note
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ CACHES['product-details'] = {
|
|||
CACHES['release-notes'] = {
|
||||
'BACKEND': 'bedrock.base.cache.SimpleDictCache',
|
||||
'LOCATION': 'release-notes',
|
||||
'TIMEOUT': 5,
|
||||
'OPTIONS': {
|
||||
'MAX_ENTRIES': 300, # currently 564 json files but most are rarely accessed
|
||||
'CULL_FREQUENCY': 4, # 1/4 entries deleted if max reached
|
||||
|
|
|
@ -99,8 +99,8 @@ def schedule_database_jobs():
|
|||
call_command('update_wordpress --database bedrock')
|
||||
|
||||
@scheduled_job('interval', minutes=5)
|
||||
def update_release_notes_data():
|
||||
call_command('update_release_notes_data --quiet')
|
||||
def update_release_notes():
|
||||
call_command('update_release_notes --quiet')
|
||||
|
||||
|
||||
def schedul_l10n_jobs():
|
||||
|
@ -108,10 +108,6 @@ def schedul_l10n_jobs():
|
|||
def update_locales():
|
||||
call_command('l10n_update')
|
||||
|
||||
@scheduled_job('interval', minutes=5)
|
||||
def update_release_notes():
|
||||
call_command('update_release_notes --quiet')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = sys.argv[1:]
|
||||
|
|
|
@ -13,7 +13,6 @@ fi
|
|||
./manage.py update_security_advisories
|
||||
./manage.py l10n_update
|
||||
./manage.py update_release_notes
|
||||
./manage.py update_release_notes_data
|
||||
./manage.py update_sitemaps
|
||||
#requires twitter api credentials not distributed publicly
|
||||
./manage.py cron update_tweets
|
||||
|
|
|
@ -88,7 +88,6 @@ if $PROD_MODE && ! imageExists "l10n"; then
|
|||
ENVFILE="master";
|
||||
fi
|
||||
dockerRun $ENVFILE code "python manage.py l10n_update"
|
||||
dockerRun $ENVFILE code "python manage.py update_release_notes"
|
||||
dockerRun $ENVFILE code "python manage.py update_sitemaps"
|
||||
docker/bin/docker_build.sh "l10n"
|
||||
fi
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
FROM mozorg/bedrock_code:${GIT_COMMIT}
|
||||
|
||||
COPY ./locale ./locale
|
||||
COPY ./release_notes ./release_notes
|
||||
COPY ./root_files/sitemap.xml ./root_files/
|
||||
COPY ./root_files/default-urls.json ./root_files/
|
||||
COPY ./bedrock.db ./
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
FROM mozorg/bedrock_code:${GIT_COMMIT}
|
||||
|
||||
COPY ./locale ./locale
|
||||
COPY ./release_notes ./release_notes
|
||||
COPY ./root_files/sitemap.xml ./root_files/
|
||||
COPY ./root_files/default-urls.json ./root_files/
|
||||
|
||||
# Change User
|
||||
USER root
|
||||
RUN chown webdev.webdev -R locale
|
||||
RUN chown webdev.webdev -R release_notes
|
||||
RUN chown webdev.webdev -R root_files
|
||||
USER webdev
|
||||
|
|
|
@ -139,3 +139,5 @@ pbr==1.10.0 \
|
|||
mock==2.0.0 \
|
||||
--hash=sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1 \
|
||||
--hash=sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba
|
||||
django-memoize==2.1.0 \
|
||||
--hash=sha256:ad969e02f25dab6484626cc3b023c2ff17004b0e3bbd8386467865826bc2bc6c
|
||||
|
|
Загрузка…
Ссылка в новой задаче