def create_basic_filter(self, locales=None): if locales: locale_objs = [LocaleFactory(code=code) for code in locales] else: locale_objs = [LocaleFactory()] return filters.LocaleFilter.create( locales=[l.code for l in locale_objs])
def test_filter_by_locale_one(self): locale1 = LocaleFactory() locale2 = LocaleFactory() recipe = RecipeFactory(locales=[locale1]) client1 = ClientFactory(locale=locale1.code) client2 = ClientFactory(locale=locale2.code) assert recipe.matches(client1) assert not recipe.matches(client2)
def test_update_recipe_locale(self, api_client): l1 = LocaleFactory(code='fr-FR') l2 = LocaleFactory(code='en-US') r = RecipeFactory(locales=[l1]) res = api_client.patch(f'/api/v1/recipe/{r.pk}/', {'locales': ['en-US']}) assert res.status_code == 200 r.refresh_from_db() assert list(r.locales.all()) == [l2]
def test_list_filter_locales(self, api_client): r1 = RecipeFactory(locales=[LocaleFactory(code='en-US')]) r2 = RecipeFactory(locales=[LocaleFactory(code='fr-CA')]) res = api_client.get('/api/v1/recipe/?locales=en-US') assert res.status_code == 200 assert len(res.data) == 1 assert res.data[0]['id'] == r1.id res = api_client.get('/api/v1/recipe/?locales=en-US,fr-CA') assert res.status_code == 200 assert len(res.data) == 2 for recipe in res.data: assert recipe['id'] in [r1.id, r2.id]
def test_classify_empty_time(self, admin_client): """ If the request_time isn't provided, default to the current time. """ locale, other_locale = LocaleFactory.create_batch(2) country = CountryFactory() release_channel = ReleaseChannelFactory() matching_recipe = RecipeFactory( enabled=True, locales=[locale], countries=[country], release_channels=[release_channel], start_time=aware_datetime(2016, 1, 1), end_time=aware_datetime(2016, 1, 3), ) RecipeFactory(locales=[other_locale]) # Non-matching with patch('normandy.classifier.middleware.timezone') as timezone: timezone.now.return_value = aware_datetime(2016, 1, 2) response = admin_client.get('/admin/classifier_preview', { 'locale': locale.code, 'release_channel': release_channel.slug, 'country': country.code, 'request_time_0': '', 'request_time_1': '', }) assert response.status_code == 200 assert list(response.context['bundle']) == [matching_recipe]
def test_classify(self, admin_client): locale, other_locale = LocaleFactory.create_batch(2) country = CountryFactory() release_channel = ReleaseChannelFactory() matching_recipe = RecipeFactory( enabled=True, locales=[locale], countries=[country], release_channels=[release_channel], start_time=aware_datetime(2016, 1, 1), end_time=aware_datetime(2016, 1, 3), ) RecipeFactory(locales=[other_locale]) # Non-matching response = admin_client.get('/admin/classifier_preview', { 'locale': locale.code, 'release_channel': release_channel.slug, 'country': country.code, 'request_time_0': '2016-01-02', 'request_time_1': '00:00:00', }) assert response.status_code == 200 assert list(response.context['bundle']) == [matching_recipe]
def test_classify_empty_time(self, admin_client): """ If the request_time isn't provided, default to the current time. """ locale, other_locale = LocaleFactory.create_batch(2) country = CountryFactory() release_channel = ReleaseChannelFactory() matching_recipe = RecipeFactory( enabled=True, locales=[locale], countries=[country], release_channels=[release_channel], start_time=aware_datetime(2016, 1, 1), end_time=aware_datetime(2016, 1, 3), ) RecipeFactory(locales=[other_locale]) # Non-matching with patch('normandy.classifier.middleware.timezone') as timezone: timezone.now.return_value = aware_datetime(2016, 1, 2) response = admin_client.get( '/admin/classifier_preview', { 'locale': locale.code, 'release_channel': release_channel.slug, 'country': country.code, 'request_time_0': '', 'request_time_1': '', }) assert response.status_code == 200 assert list(response.context['bundle']) == [matching_recipe]
def test_classify(self, admin_client): locale, other_locale = LocaleFactory.create_batch(2) country = CountryFactory() release_channel = ReleaseChannelFactory() matching_recipe = RecipeFactory( enabled=True, locales=[locale], countries=[country], release_channels=[release_channel], start_time=aware_datetime(2016, 1, 1), end_time=aware_datetime(2016, 1, 3), ) RecipeFactory(locales=[other_locale]) # Non-matching response = admin_client.get( '/admin/classifier_preview', { 'locale': locale.code, 'release_channel': release_channel.slug, 'country': country.code, 'request_time_0': '2016-01-02', 'request_time_1': '00:00:00', }) assert response.status_code == 200 assert list(response.context['bundle']) == [matching_recipe]
def test_canonical_json(self): recipe = RecipeFactory( action=ActionFactory(name='action'), arguments_json='{"foo": 1, "bar": 2}', channels=[ChannelFactory(slug='beta')], countries=[CountryFactory(code='CA')], enabled=False, extra_filter_expression='2 + 2 == 4', locales=[LocaleFactory(code='en-US')], name='canonical', ) # Yes, this is really ugly, but we really do need to compare an exact # byte sequence, since this is used for hashing and signing filter_expression = ( "(normandy.locale in ['en-US']) && (normandy.country in ['CA']) && " "(normandy.channel in ['beta']) && (2 + 2 == 4)") expected = ('{' '"action":"action",' '"arguments":{"bar":2,"foo":1},' '"enabled":false,' '"filter_expression":"%(filter_expression)s",' '"id":%(id)s,' '"is_approved":false,' '"last_updated":"%(last_updated)s",' '"name":"canonical",' '"revision_id":"%(revision_id)s"' '}') % { 'id': recipe.id, 'revision_id': recipe.revision_id, 'last_updated': recipe.last_updated.strftime('%Y-%m-%dT%H:%M:%S.%fZ'), 'filter_expression': filter_expression } expected = expected.encode() assert recipe.canonical_json() == expected
def test_it_works(self, rf): channel = ChannelFactory() country = CountryFactory() locale = LocaleFactory() recipe = RecipeFactory(arguments={'foo': 'bar'}, channels=[channel], countries=[country], locales=[locale]) action = recipe.action serializer = RecipeSerializer(recipe, context={'request': rf.get('/')}) assert serializer.data == { 'name': recipe.name, 'id': recipe.id, 'last_updated': Whatever(), 'enabled': recipe.enabled, 'extra_filter_expression': recipe.extra_filter_expression, 'filter_expression': recipe.filter_expression, 'revision_id': recipe.revision_id, 'action': action.name, 'arguments': { 'foo': 'bar', }, 'channels': [channel.slug], 'countries': [country.code], 'locales': [locale.code] }
def test_filter_exclude_many(self): locale_match1, locale_match2, locale_not = LocaleFactory.create_batch(3) recipe = RecipeFactory(locales=[locale_match1, locale_match2]) client = ClientFactory(locale=locale_not.code) assert not recipe.matches(client) assert recipe.matches(client, exclude=[get_locales])
def test_it_filters_by_locale(self, client): english = LocaleFactory(code='en-US') german = LocaleFactory(code='de') RecipeFactory(name='english', enabled=True, locales=[english]) RecipeFactory(name='german', enabled=True, locales=[german]) RecipeFactory(name='any', enabled=True, locales=[]) RecipeFactory(name='both', enabled=True, locales=[english, german]) RecipeFactory(name='disabled', enabled=False, locales=[german]) data = ClientParametersFactory(locale='de') res = client.post('/api/v1/fetch_bundle/', data) assert res.status_code == 200 recipe_names = set(r['name'] for r in res.data['recipes']) assert recipe_names == {'german', 'any', 'both'}
def test_recipe_revise_locales(self): l1 = LocaleFactory(code='en-US') recipe = RecipeFactory(locales=[l1]) l2 = LocaleFactory(code='fr-CA') recipe.revise(locales=[l2]) assert recipe.locales.count() == 1 assert list(recipe.locales.all()) == [l2] recipe.revise(locales=[l1, l2]) locales = list(recipe.locales.all()) assert recipe.locales.count() == 2 assert l1 in locales assert l2 in locales recipe.revise(locales=[]) assert recipe.locales.count() == 0
def test_it_filters_by_locale_with_json(self, api_client): """ Ensure that we correctly pull data from the request such that both form-encoded and JSON-encoded requests work. """ english = LocaleFactory(code='en-US') german = LocaleFactory(code='de') RecipeFactory(name='english', enabled=True, locales=[english]) RecipeFactory(name='german', enabled=True, locales=[german]) RecipeFactory(name='any', enabled=True, locales=[]) RecipeFactory(name='both', enabled=True, locales=[english, german]) RecipeFactory(name='disabled', enabled=False, locales=[german]) data = ClientParametersFactory(locale='de') res = api_client.post('/api/v1/fetch_bundle/', data, format='json') assert res.status_code == 200 recipe_names = set(r['name'] for r in res.data['recipes']) assert recipe_names == {'german', 'any', 'both'}
def test_validation_with_valid_data(self): mockAction = ActionFactory(name='show-heartbeat', arguments_schema=ARGUMENTS_SCHEMA) channel = ChannelFactory(slug='release') country = CountryFactory(code='CA') locale = LocaleFactory(code='en-US') serializer = RecipeSerializer( data={ 'name': 'bar', 'enabled': True, 'extra_filter_expression': '[]', 'action': 'show-heartbeat', 'channels': ['release'], 'countries': ['CA'], 'locales': ['en-US'], 'arguments': { 'surveyId': 'lorem-ipsum-dolor', 'surveys': [{ 'title': 'adipscing', 'weight': 1 }, { 'title': 'consequetar', 'weight': 1 }] } }) assert serializer.is_valid() assert serializer.validated_data == { 'name': 'bar', 'enabled': True, 'extra_filter_expression': '[]', 'action': mockAction, 'arguments': { 'surveyId': 'lorem-ipsum-dolor', 'surveys': [{ 'title': 'adipscing', 'weight': 1 }, { 'title': 'consequetar', 'weight': 1 }] }, 'channels': [channel], 'countries': [country], 'locales': [locale], } assert serializer.errors == {}
def test_filter_expression(self): channel1 = ChannelFactory(slug='beta', name='Beta') channel2 = ChannelFactory(slug='release', name='Release') country1 = CountryFactory(code='US', name='USA') country2 = CountryFactory(code='CA', name='Canada') locale1 = LocaleFactory(code='en-US', name='English (US)') locale2 = LocaleFactory(code='fr-CA', name='French (CA)') r = RecipeFactory() assert r.filter_expression == '' r = RecipeFactory(channels=[channel1]) assert r.filter_expression == "normandy.channel in ['beta']" r.revise(channels=[channel1, channel2]) assert r.filter_expression == "normandy.channel in ['beta', 'release']" r = RecipeFactory(countries=[country1]) assert r.filter_expression == "normandy.country in ['US']" r.revise(countries=[country1, country2]) assert r.filter_expression == "normandy.country in ['CA', 'US']" r = RecipeFactory(locales=[locale1]) assert r.filter_expression == "normandy.locale in ['en-US']" r.revise(locales=[locale1, locale2]) assert r.filter_expression == "normandy.locale in ['en-US', 'fr-CA']" r = RecipeFactory(extra_filter_expression='2 + 2 == 4') assert r.filter_expression == '2 + 2 == 4' r.revise(channels=[channel1], countries=[country1], locales=[locale1]) assert r.filter_expression == ("(normandy.locale in ['en-US']) && " "(normandy.country in ['US']) && " "(normandy.channel in ['beta']) && " "(2 + 2 == 4)")
def test_classify_no_sample(self, admin_client): """The classify view should ignore sampling.""" locale = LocaleFactory() country = CountryFactory() release_channel = ReleaseChannelFactory() recipe = RecipeFactory(sample_rate=0) response = admin_client.get( '/admin/classifier_preview', { 'locale': locale.code, 'release_channel': release_channel.slug, 'country': country.code }) assert response.status_code == 200 assert not recipe.matches(response.context['client']) assert list(response.context['bundle']) == [recipe]
def test_it_works(self, rf): channel = ChannelFactory() country = CountryFactory() locale = LocaleFactory() recipe = RecipeFactory(arguments={'foo': 'bar'}, channels=[channel], countries=[country], locales=[locale]) approval = ApprovalRequestFactory(revision=recipe.latest_revision) action = recipe.action serializer = RecipeSerializer(recipe, context={'request': rf.get('/')}) assert serializer.data == { 'name': recipe.name, 'id': recipe.id, 'last_updated': Whatever(), 'enabled': recipe.enabled, 'extra_filter_expression': recipe.extra_filter_expression, 'filter_expression': recipe.filter_expression, 'action': { 'arguments_schema': {}, 'id': action.id, 'implementation_url': Whatever(), 'name': action.name, }, 'arguments': { 'foo': 'bar', }, 'channels': [channel.slug], 'countries': [country.code], 'locales': [locale.code], 'is_approved': False, 'latest_revision': RecipeRevisionSerializer(recipe.latest_revision).data, 'approved_revision': None, 'approval_request': { 'id': approval.id, 'created': Whatever(), 'creator': Whatever(), 'approved': None, 'approver': None, 'comment': None, }, }
def test_matches_deals_with_none(self): country = LocaleFactory(code='US') client = ClientFactory(country=None) assert not country.matches(client)
def test_matches_deals_with_none(self): locale = LocaleFactory(code='en-US') client = ClientFactory(locale=None) assert not locale.matches(client)
def test_matches_works(self): locale = LocaleFactory(code='en-US') client1 = ClientFactory(locale='en-US') client2 = ClientFactory(locale='de') assert locale.matches(client1) assert not locale.matches(client2)