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])
예제 #2
0
    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)
예제 #3
0
    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]
예제 #4
0
    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]
예제 #5
0
    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]
예제 #6
0
    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]
예제 #7
0
    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]
예제 #8
0
    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]
예제 #9
0
 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
예제 #10
0
    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]
        }
예제 #11
0
    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])
예제 #12
0
    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'}
예제 #13
0
    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
예제 #14
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'}
예제 #15
0
    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 == {}
예제 #16
0
    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)")
예제 #17
0
    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]
예제 #18
0
    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,
            },
        }
예제 #19
0
 def test_matches_deals_with_none(self):
     country = LocaleFactory(code='US')
     client = ClientFactory(country=None)
     assert not country.matches(client)
예제 #20
0
 def test_matches_deals_with_none(self):
     locale = LocaleFactory(code='en-US')
     client = ClientFactory(locale=None)
     assert not locale.matches(client)
예제 #21
0
 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)