Пример #1
0
class CliTestCase(TestCase):
    runner = fixture(CliRunner)
    command = None
    default_args = []

    def invoke(self, *args):
        args += tuple(self.default_args)
        return self.runner.invoke(self.command, args, obj={})
Пример #2
0
class HealthCheckTest(TestCase):
    middleware = fixture(HealthCheck)
    factory = fixture(RequestFactory)

    @patch('sentry.status_checks.check_all')
    def test_other_url(self, check_all):
        req = self.factory.get('/')
        resp = self.middleware.process_request(req)
        assert resp is None, resp
        assert check_all.call_count == 0

    @patch('sentry.status_checks.check_all')
    def test_basic_health(self, check_all):
        req = self.factory.get('/_health/')
        resp = self.middleware.process_request(req)
        assert resp.status_code == 200, resp
        assert check_all.call_count == 0

    @patch('sentry.status_checks.check_all')
    def test_full_health_ok(self, check_all):
        check_all.return_value = {
            object(): [],
        }
        req = self.factory.get('/_health/?full')
        resp = self.middleware.process_request(req)
        assert resp.status_code == 200, resp
        body = json.loads(resp.content)
        assert 'problems' in body
        assert 'healthy' in body
        assert check_all.call_count == 1

    @patch('sentry.status_checks.check_all')
    def test_full_health_bad(self, check_all):
        check_all.return_value = {
            object(): [
                Problem('the system is down'),
            ],
        }
        req = self.factory.get('/_health/?full')
        resp = self.middleware.process_request(req)
        assert resp.status_code == 500, resp
        body = json.loads(resp.content)
        assert 'problems' in body
        assert 'healthy' in body
        assert check_all.call_count == 1
Пример #3
0
class UserActiveMiddlewareTest(TestCase):
    middleware = fixture(UserActiveMiddleware)
    factory = fixture(RequestFactory)

    def test_simple(self):
        self.view = lambda x: None

        user = self.user
        req = self.factory.get('/')
        req.user = user

        resp = self.middleware.process_view(req, self.view, [], {})
        assert resp is None
        assert timezone.now() - user.last_active < timedelta(minutes=1)

        user.last_active = None
        resp = self.middleware.process_view(req, self.view, [], {})
        assert resp is None
        assert timezone.now() - user.last_active < timedelta(minutes=1)
Пример #4
0
class AuthenticationMiddlewareTestCase(TestCase):
    middleware = fixture(AuthenticationMiddleware)

    @fixture
    def request(self):
        rv = RequestFactory().get('/')
        rv.session = self.session
        return rv

    def test_process_request_anon(self):
        self.middleware.process_request(self.request)
        assert self.request.user.is_anonymous()

    def test_process_request_user(self):
        request = self.request
        assert login(request, self.user)
        self.middleware.process_request(request)
        assert request.user.is_authenticated()
        assert request.user == self.user
        assert '_nonce' not in request.session
        assert UserIP.objects.filter(
            user=self.user,
            ip_address='127.0.0.1',
        ).exists()

    def test_process_request_good_nonce(self):
        request = self.request
        user = self.user
        user.session_nonce = 'xxx'
        user.save()
        assert login(request, user)
        self.middleware.process_request(request)
        assert request.user.is_authenticated()
        assert request.user == self.user
        assert request.session['_nonce'] == 'xxx'

    def test_process_request_missing_nonce(self):
        request = self.request
        user = self.user
        user.session_nonce = 'xxx'
        user.save()
        assert login(request, user)
        del request.session['_nonce']
        self.middleware.process_request(request)
        assert request.user.is_anonymous()

    def test_process_request_bad_nonce(self):
        request = self.request
        user = self.user
        user.session_nonce = 'xxx'
        user.save()
        assert login(request, user)
        request.session['_nonce'] = 'gtfo'
        self.middleware.process_request(request)
        assert request.user.is_anonymous()
Пример #5
0
class ContentLengthHeaderMiddlewareTest(TestCase):
    middleware = fixture(ContentLengthHeaderMiddleware)

    def test_simple(self):
        response = self.middleware.process_response(None, HttpResponse('lol'))
        assert response['Content-Length'] == '3'
        assert 'Transfer-Encoding' not in response

    def test_streaming(self):
        response = self.middleware.process_response(None, StreamingHttpResponse())
        assert 'Transfer-Encoding' not in response
        assert 'Content-Length' not in response
Пример #6
0
class AuthenticationMiddlewareTestCase(TestCase):
    middleware = fixture(AuthenticationMiddleware)

    @fixture
    def request(self):
        rv = RequestFactory().get("/")
        rv.session = self.session
        return rv

    def test_process_request_anon(self):
        self.middleware.process_request(self.request)
        assert self.request.user.is_anonymous

    def test_process_request_user(self):
        request = self.request
        assert login(request, self.user)
        self.middleware.process_request(request)
        assert request.user.is_authenticated
        assert request.user == self.user
        assert "_nonce" not in request.session
        assert UserIP.objects.filter(user=self.user,
                                     ip_address="127.0.0.1").exists()

    def test_process_request_good_nonce(self):
        request = self.request
        user = self.user
        user.session_nonce = "xxx"
        user.save()
        assert login(request, user)
        self.middleware.process_request(request)
        assert request.user.is_authenticated
        assert request.user == self.user
        assert request.session["_nonce"] == "xxx"

    def test_process_request_missing_nonce(self):
        request = self.request
        user = self.user
        user.session_nonce = "xxx"
        user.save()
        assert login(request, user)
        del request.session["_nonce"]
        self.middleware.process_request(request)
        assert request.user.is_anonymous

    def test_process_request_bad_nonce(self):
        request = self.request
        user = self.user
        user.session_nonce = "xxx"
        user.save()
        assert login(request, user)
        request.session["_nonce"] = "gtfo"
        self.middleware.process_request(request)
        assert request.user.is_anonymous
Пример #7
0
class ContentLengthHeaderMiddlewareTest(TestCase):
    middleware = fixture(ContentLengthHeaderMiddleware)

    def test_simple(self):
        response = self.middleware.process_response(None, HttpResponse("lol"))
        assert response["Content-Length"] == "3"
        assert "Transfer-Encoding" not in response

    def test_streaming(self):
        response = self.middleware.process_response(None,
                                                    StreamingHttpResponse())
        assert "Transfer-Encoding" not in response
        assert "Content-Length" not in response
Пример #8
0
class SwitchFormTest(Exam, unittest2.TestCase):

    mock_switch = fixture(Mock, conditions=[1, 2, 3])
    condition_form = patcher('gutter.django.forms.ConditionForm')
    form = fixture(SwitchForm)

    @fixture
    def switch_from_object(self):
        return SwitchForm.from_object(self.mock_switch)

    @patch('gutter.django.forms.ConditionFormSet')
    def test_from_object_returns_dict_of_properties(self, _):
        eq_(
            self.switch_from_object.initial,
            dict(
                label=self.mock_switch.label,
                name=self.mock_switch.name,
                description=self.mock_switch.description,
                state=self.mock_switch.state,
                compounded=self.mock_switch.compounded,
                concent=self.mock_switch.concent,
            ))

    @patch('gutter.django.forms.ConditionFormSet')
    def test_from_object_sets_conditions_as_form_set(self, ConditionFormSet):
        eq_(self.switch_from_object.conditions, ConditionFormSet.return_value)

        # Called with a map() over ConditionForm.to_dict
        expected = [self.condition_form.to_dict.return_value] * 3
        ConditionFormSet.assert_called_once_with(initial=expected)

        # Assert that the calls it did receive are correct
        self.condition_form.to_dict.assert_any_call(1)
        self.condition_form.to_dict.assert_any_call(2)
        self.condition_form.to_dict.assert_any_call(3)

    def test_from_object_marks_the_name_field_as_readonly(self):
        self.assertTrue(
            self.switch_from_object.fields['name'].widget.attrs['readonly'])
Пример #9
0
class SetRemoteAddrFromForwardedForTestCase(TestCase):
    middleware = fixture(SetRemoteAddrFromForwardedFor)

    def test_ipv4(self):
        request = HttpRequest()
        request.META['HTTP_X_FORWARDED_FOR'] = '8.8.8.8:80,8.8.4.4'
        self.middleware.process_request(request)
        assert request.META['REMOTE_ADDR'] == '8.8.8.8'

    def test_ipv6(self):
        request = HttpRequest()
        request.META[
            'HTTP_X_FORWARDED_FOR'] = '2001:4860:4860::8888,2001:4860:4860::8844'
        self.middleware.process_request(request)
        assert request.META['REMOTE_ADDR'] == '2001:4860:4860::8888'
Пример #10
0
class RedisQuotaTest(TestCase):
    quota = fixture(RedisQuota)

    @patcher.object(RedisQuota, 'get_project_quota')
    def get_project_quota(self):
        inst = mock.MagicMock()
        inst.return_value = (0, 60)
        return inst

    @patcher.object(RedisQuota, 'get_organization_quota')
    def get_organization_quota(self):
        inst = mock.MagicMock()
        inst.return_value = (0, 60)
        return inst

    def test_uses_defined_quotas(self):
        self.get_project_quota.return_value = (200, 60)
        self.get_organization_quota.return_value = (300, 60)
        quotas = self.quota.get_quotas(self.project)
        assert quotas[0].key == 'p:{}'.format(self.project.id)
        assert quotas[0].limit == 200
        assert quotas[0].window == 60
        assert quotas[1].key == 'o:{}'.format(self.project.organization.id)
        assert quotas[1].limit == 300
        assert quotas[1].window == 60

    @mock.patch('sentry.quotas.redis.is_rate_limited')
    @mock.patch.object(RedisQuota, 'get_quotas', return_value=[])
    def test_bails_immediately_without_any_quota(self, get_quotas,
                                                 is_rate_limited):
        result = self.quota.is_rate_limited(self.project)
        assert not is_rate_limited.called
        assert not result.is_limited

    @mock.patch('sentry.quotas.redis.is_rate_limited',
                return_value=(False, False))
    def test_is_not_limited_without_rejections(self, is_rate_limited):
        self.get_organization_quota.return_value = (100, 60)
        self.get_project_quota.return_value = (200, 60)
        assert not self.quota.is_rate_limited(self.project).is_limited

    @mock.patch('sentry.quotas.redis.is_rate_limited',
                return_value=(True, False))
    def test_is_limited_on_rejections(self, is_rate_limited):
        self.get_organization_quota.return_value = (100, 60)
        self.get_project_quota.return_value = (200, 60)
        assert self.quota.is_rate_limited(self.project).is_limited
Пример #11
0
class SetRemoteAddrFromForwardedForTestCase(TestCase):
    middleware = fixture(SetRemoteAddrFromForwardedFor)

    def test_ipv4(self):
        request = HttpRequest()
        request.META["HTTP_X_FORWARDED_FOR"] = "8.8.8.8:80,8.8.4.4"
        self.middleware.process_request(request)
        assert request.META["REMOTE_ADDR"] == "8.8.8.8"

    def test_ipv4_whitespace(self):
        request = HttpRequest()
        request.META["HTTP_X_FORWARDED_FOR"] = "8.8.8.8:80 "
        self.middleware.process_request(request)
        assert request.META["REMOTE_ADDR"] == "8.8.8.8"

    def test_ipv6(self):
        request = HttpRequest()
        request.META[
            "HTTP_X_FORWARDED_FOR"] = "2001:4860:4860::8888,2001:4860:4860::8844"
        self.middleware.process_request(request)
        assert request.META["REMOTE_ADDR"] == "2001:4860:4860::8888"
Пример #12
0
class RedisQuotaTest(TestCase):
    quota = fixture(RedisQuota)

    @patcher.object(RedisQuota, 'get_project_quota')
    def get_project_quota(self):
        inst = mock.MagicMock()
        inst.return_value = (0, 60)
        return inst

    @patcher.object(RedisQuota, 'get_organization_quota')
    def get_organization_quota(self):
        inst = mock.MagicMock()
        inst.return_value = (0, 60)
        return inst

    def test_uses_defined_quotas(self):
        self.get_project_quota.return_value = (200, 60)
        self.get_organization_quota.return_value = (300, 60)
        quotas = self.quota.get_quotas(self.project)
        assert quotas[0].key == 'p:{}'.format(self.project.id)
        assert quotas[0].limit == 200
        assert quotas[0].window == 60
        assert quotas[1].key == 'o:{}'.format(self.project.organization.id)
        assert quotas[1].limit == 300
        assert quotas[1].window == 60

    @mock.patch('sentry.quotas.redis.is_rate_limited')
    @mock.patch.object(RedisQuota, 'get_quotas', return_value=[])
    def test_bails_immediately_without_any_quota(self, get_quotas,
                                                 is_rate_limited):
        result = self.quota.is_rate_limited(self.project)
        assert not is_rate_limited.called
        assert not result.is_limited

    @mock.patch('sentry.quotas.redis.is_rate_limited',
                return_value=(False, False))
    def test_is_not_limited_without_rejections(self, is_rate_limited):
        self.get_organization_quota.return_value = (100, 60)
        self.get_project_quota.return_value = (200, 60)
        assert not self.quota.is_rate_limited(self.project).is_limited

    @mock.patch('sentry.quotas.redis.is_rate_limited',
                return_value=(True, False))
    def test_is_limited_on_rejections(self, is_rate_limited):
        self.get_organization_quota.return_value = (100, 60)
        self.get_project_quota.return_value = (200, 60)
        assert self.quota.is_rate_limited(self.project).is_limited

    @mock.patch.object(RedisQuota, 'get_quotas')
    @mock.patch('sentry.quotas.redis.is_rate_limited',
                return_value=(True, False))
    def test_not_limited_without_enforce(self, mock_is_rate_limited,
                                         mock_get_quotas):
        mock_get_quotas.return_value = (
            BasicRedisQuota(
                key='p:1',
                limit=1,
                window=1,
                reason_code='project_quota',
                enforce=False,
            ),
            BasicRedisQuota(
                key='p:2',
                limit=1,
                window=1,
                reason_code='project_quota',
                enforce=True,
            ),
        )

        assert not self.quota.is_rate_limited(self.project).is_limited

    @mock.patch.object(RedisQuota, 'get_quotas')
    @mock.patch('sentry.quotas.redis.is_rate_limited',
                return_value=(True, True))
    def test_limited_without_enforce(self, mock_is_rate_limited,
                                     mock_get_quotas):
        mock_get_quotas.return_value = (
            BasicRedisQuota(
                key='p:1',
                limit=1,
                window=1,
                reason_code='project_quota',
                enforce=False,
            ),
            BasicRedisQuota(
                key='p:2',
                limit=1,
                window=1,
                reason_code='project_quota',
                enforce=True,
            ),
        )

        assert self.quota.is_rate_limited(self.project).is_limited

    def test_get_usage(self):
        timestamp = time.time()

        self.get_project_quota.return_value = (200, 60)
        self.get_organization_quota.return_value = (300, 60)

        n = 10
        for _ in xrange(n):
            self.quota.is_rate_limited(self.project, timestamp=timestamp)

        quotas = self.quota.get_quotas(self.project)

        assert self.quota.get_usage(
            self.project,
            quotas + [
                BasicRedisQuota(
                    key='unlimited',
                    limit=0,
                    window=60,
                    reason_code='unlimited',
                ),
                BasicRedisQuota(
                    key='dummy',
                    limit=10,
                    window=60,
                    reason_code='dummy',
                ),
            ],
            timestamp=timestamp,
        ) == [n for _ in quotas] + [None, 0]
Пример #13
0
class OptionsManagerTest(TestCase):
    store = fixture(OptionsStore)

    @fixture
    def manager(self):
        return OptionsManager(store=self.store)

    @around
    def register(self):
        self.store.flush_local_cache()
        self.manager.register('foo')
        yield
        self.manager.unregister('foo')

    def test_simple(self):
        assert self.manager.get('foo') == ''

        with self.settings(SENTRY_OPTIONS={'foo': 'bar'}):
            assert self.manager.get('foo') == 'bar'

        self.manager.set('foo', 'bar')

        assert self.manager.get('foo') == 'bar'

        self.manager.delete('foo')

        assert self.manager.get('foo') == ''

    def test_register(self):
        with self.assertRaises(UnknownOption):
            self.manager.get('does-not-exit')

        with self.assertRaises(UnknownOption):
            self.manager.set('does-not-exist', 'bar')

        self.manager.register('does-not-exist')
        self.manager.get('does-not-exist')  # Just shouldn't raise
        self.manager.unregister('does-not-exist')

        with self.assertRaises(UnknownOption):
            self.manager.get('does-not-exist')

        with self.assertRaises(AssertionError):
            # This key should already exist, and we can't re-register
            self.manager.register('foo')

        with self.assertRaises(TypeError):
            self.manager.register('wrong-type', default=1, type=String)

        with self.assertRaises(TypeError):
            self.manager.register('none-type', default=None, type=type(None))

    def test_coerce(self):
        self.manager.register('some-int', type=Int)

        self.manager.set('some-int', 0)
        assert self.manager.get('some-int') == 0
        self.manager.set('some-int', '0')
        assert self.manager.get('some-int') == 0

        with self.assertRaises(TypeError):
            self.manager.set('some-int', 'foo')

        with self.assertRaises(TypeError):
            self.manager.set('some-int', '0', coerce=False)

    def test_legacy_key(self):
        """
        Allow sentry: prefixed keys without any registration
        """
        # These just shouldn't blow up since they are implicitly registered
        assert self.manager.get('sentry:foo') == ''
        self.manager.set('sentry:foo', 'bar')
        assert self.manager.get('sentry:foo') == 'bar'
        assert self.manager.delete('sentry:foo')
        assert self.manager.get('sentry:foo') == ''

    def test_types(self):
        self.manager.register('some-int', type=Int, default=0)
        with self.assertRaises(TypeError):
            self.manager.set('some-int', 'foo')
        self.manager.set('some-int', 1)
        assert self.manager.get('some-int') == 1

    def test_default(self):
        self.manager.register('awesome', default='lol')
        assert self.manager.get('awesome') == 'lol'
        self.manager.set('awesome', 'bar')
        assert self.manager.get('awesome') == 'bar'
        self.manager.delete('awesome')
        assert self.manager.get('awesome') == 'lol'
        self.manager.register('callback', default=lambda: True)
        assert self.manager.get('callback') is True
        self.manager.register('default-type', type=Int)
        assert self.manager.get('default-type') == 0

    def test_flag_immutable(self):
        self.manager.register('immutable', flags=FLAG_IMMUTABLE)
        with self.assertRaises(AssertionError):
            self.manager.set('immutable', 'thing')
        with self.assertRaises(AssertionError):
            self.manager.delete('immutable')

    def test_flag_nostore(self):
        self.manager.register('nostore', flags=FLAG_NOSTORE)
        with self.assertRaises(AssertionError):
            self.manager.set('nostore', 'thing')

        # Make sure that we don't touch either of the stores
        with patch.object(self.store.cache, 'get', side_effect=Exception()):
            with patch.object(Option.objects,
                              'get_queryset',
                              side_effect=Exception()):
                assert self.manager.get('nostore') == ''
                self.store.flush_local_cache()

                with self.settings(SENTRY_OPTIONS={'nostore': 'foo'}):
                    assert self.manager.get('nostore') == 'foo'
                    self.store.flush_local_cache()

        with self.assertRaises(AssertionError):
            self.manager.delete('nostore')

    def test_validate(self):
        with self.assertRaises(UnknownOption):
            self.manager.validate({'unknown': ''})

        self.manager.register('unknown')
        self.manager.register('storeonly', flags=FLAG_STOREONLY)
        self.manager.validate({'unknown': ''})

        with self.assertRaises(AssertionError):
            self.manager.validate({'storeonly': ''})

        with self.assertRaises(TypeError):
            self.manager.validate({'unknown': True})

    def test_flag_storeonly(self):
        self.manager.register('storeonly', flags=FLAG_STOREONLY)
        assert self.manager.get('storeonly') == ''

        with self.settings(SENTRY_OPTIONS={'storeonly': 'something-else!'}):
            assert self.manager.get('storeonly') == ''

    def test_flag_prioritize_disk(self):
        self.manager.register('prioritize_disk', flags=FLAG_PRIORITIZE_DISK)
        assert self.manager.get('prioritize_disk') == ''

        with self.settings(
                SENTRY_OPTIONS={'prioritize_disk': 'something-else!'}):
            with self.assertRaises(AssertionError):
                assert self.manager.set('prioritize_disk', 'foo')
            assert self.manager.get('prioritize_disk') == 'something-else!'

        self.manager.set('prioritize_disk', 'foo')
        assert self.manager.get('prioritize_disk') == 'foo'

        # Make sure the database value is overridden if defined
        with self.settings(
                SENTRY_OPTIONS={'prioritize_disk': 'something-else!'}):
            assert self.manager.get('prioritize_disk') == 'something-else!'

    def test_db_unavailable(self):
        with patch.object(Option.objects,
                          'get_queryset',
                          side_effect=Exception()):
            # we can't update options if the db is unavailable
            with self.assertRaises(Exception):
                self.manager.set('foo', 'bar')

        self.manager.set('foo', 'bar')
        self.store.flush_local_cache()

        with patch.object(Option.objects,
                          'get_queryset',
                          side_effect=Exception()):
            assert self.manager.get('foo') == 'bar'
            self.store.flush_local_cache()

            with patch.object(self.store.cache, 'get',
                              side_effect=Exception()):
                assert self.manager.get('foo') == ''
                self.store.flush_local_cache()

                with patch.object(self.store.cache,
                                  'set',
                                  side_effect=Exception()):
                    assert self.manager.get('foo') == ''
                    self.store.flush_local_cache()

    def test_db_and_cache_unavailable(self):
        self.manager.set('foo', 'bar')
        self.store.flush_local_cache()

        with self.settings(SENTRY_OPTIONS={'foo': 'baz'}):
            with patch.object(Option.objects,
                              'get_queryset',
                              side_effect=Exception()):
                with patch.object(self.store.cache,
                                  'get',
                                  side_effect=Exception()):
                    assert self.manager.get('foo') == 'baz'
                    self.store.flush_local_cache()

                    with patch.object(self.store.cache,
                                      'set',
                                      side_effect=Exception()):
                        assert self.manager.get('foo') == 'baz'
                        self.store.flush_local_cache()

    def test_cache_unavailable(self):
        self.manager.set('foo', 'bar')
        self.store.flush_local_cache()

        with patch.object(self.store.cache, 'get', side_effect=Exception()):
            assert self.manager.get('foo') == 'bar'
            self.store.flush_local_cache()

            with patch.object(self.store.cache, 'set',
                              side_effect=Exception()):
                assert self.manager.get('foo') == 'bar'
                self.store.flush_local_cache()

                # we should still be able to write a new value
                self.manager.set('foo', 'baz')
                self.store.flush_local_cache()

        # the cache should be incorrect now, but sync_options will eventually
        # correct the state
        assert self.manager.get('foo') == 'bar'
        self.store.flush_local_cache()

        # when the cache poofs, the db will be return the most-true answer
        with patch.object(self.store.cache, 'get', side_effect=Exception()):
            assert self.manager.get('foo') == 'baz'
            self.store.flush_local_cache()

            with patch.object(self.store.cache, 'set',
                              side_effect=Exception()):
                assert self.manager.get('foo') == 'baz'
                self.store.flush_local_cache()

    def test_unregister(self):
        with self.assertRaises(UnknownOption):
            self.manager.unregister('does-not-exist')

    def test_all(self):
        self.manager.register('bar')

        keys = list(self.manager.all())
        assert {k.name for k in keys} == {'foo', 'bar'}

    def test_filter(self):
        self.manager.register('nostore', flags=FLAG_NOSTORE)
        self.manager.register('required', flags=FLAG_REQUIRED)
        self.manager.register('nostorerequired',
                              flags=FLAG_NOSTORE | FLAG_REQUIRED)

        assert list(self.manager.filter()) == list(self.manager.all())

        keys = list(self.manager.filter())
        assert {k.name
                for k in keys
                } == {'foo', 'nostore', 'required', 'nostorerequired'}

        keys = list(self.manager.filter(flag=DEFAULT_FLAGS))
        assert {k.name for k in keys} == {'foo'}

        keys = list(self.manager.filter(flag=FLAG_NOSTORE))
        assert {k.name for k in keys} == {'nostore', 'nostorerequired'}

        keys = list(self.manager.filter(flag=FLAG_REQUIRED))
        assert {k.name for k in keys} == {'required', 'nostorerequired'}
Пример #14
0
class RedisQuotaTest(TestCase):
    quota = fixture(RedisQuota)

    @patcher.object(RedisQuota, "get_project_quota")
    def get_project_quota(self):
        inst = mock.MagicMock()
        inst.return_value = (0, 60)
        return inst

    @patcher.object(RedisQuota, "get_organization_quota")
    def get_organization_quota(self):
        inst = mock.MagicMock()
        inst.return_value = (0, 60)
        return inst

    def test_uses_defined_quotas(self):
        self.get_project_quota.return_value = (200, 60)
        self.get_organization_quota.return_value = (300, 60)
        quotas = self.quota.get_quotas(self.project)

        assert quotas[0].id == "p"
        assert quotas[0].scope == QuotaScope.PROJECT
        assert quotas[0].scope_id == str(self.project.id)
        assert quotas[0].limit == 200
        assert quotas[0].window == 60
        assert quotas[1].id == "o"
        assert quotas[1].scope == QuotaScope.ORGANIZATION
        assert quotas[1].scope_id == str(self.organization.id)
        assert quotas[1].limit == 300
        assert quotas[1].window == 60

    @mock.patch("sentry.quotas.redis.is_rate_limited")
    @mock.patch.object(RedisQuota, "get_quotas", return_value=[])
    def test_bails_immediately_without_any_quota(self, get_quotas,
                                                 is_rate_limited):
        result = self.quota.is_rate_limited(self.project)
        assert not is_rate_limited.called
        assert not result.is_limited

    @mock.patch("sentry.quotas.redis.is_rate_limited",
                return_value=(False, False))
    def test_is_not_limited_without_rejections(self, is_rate_limited):
        self.get_organization_quota.return_value = (100, 60)
        self.get_project_quota.return_value = (200, 60)
        assert not self.quota.is_rate_limited(self.project).is_limited

    @mock.patch("sentry.quotas.redis.is_rate_limited",
                return_value=(True, False))
    def test_is_limited_on_rejections(self, is_rate_limited):
        self.get_organization_quota.return_value = (100, 60)
        self.get_project_quota.return_value = (200, 60)
        assert self.quota.is_rate_limited(self.project).is_limited

    @mock.patch.object(RedisQuota, "get_quotas")
    @mock.patch("sentry.quotas.redis.is_rate_limited",
                return_value=(False, False))
    def test_not_limited_with_unlimited_quota(self, mock_is_rate_limited,
                                              mock_get_quotas):
        mock_get_quotas.return_value = (
            QuotaConfig(
                id="p",
                scope=QuotaScope.PROJECT,
                scope_id=1,
                limit=None,
                window=1,
                reason_code="project_quota",
            ),
            QuotaConfig(
                id="p",
                scope=QuotaScope.PROJECT,
                scope_id=2,
                limit=1,
                window=1,
                reason_code="project_quota",
            ),
        )

        assert not self.quota.is_rate_limited(self.project).is_limited

    @mock.patch.object(RedisQuota, "get_quotas")
    @mock.patch("sentry.quotas.redis.is_rate_limited",
                return_value=(False, True))
    def test_limited_with_unlimited_quota(self, mock_is_rate_limited,
                                          mock_get_quotas):
        mock_get_quotas.return_value = (
            QuotaConfig(
                id="p",
                scope=QuotaScope.PROJECT,
                scope_id=1,
                limit=None,
                window=1,
                reason_code="project_quota",
            ),
            QuotaConfig(
                id="p",
                scope=QuotaScope.PROJECT,
                scope_id=2,
                limit=1,
                window=1,
                reason_code="project_quota",
            ),
        )

        assert self.quota.is_rate_limited(self.project).is_limited

    def test_get_usage(self):
        timestamp = time.time()

        self.get_project_quota.return_value = (200, 60)
        self.get_organization_quota.return_value = (300, 60)

        n = 10
        for _ in range(n):
            self.quota.is_rate_limited(self.project, timestamp=timestamp)

        quotas = self.quota.get_quotas(self.project)
        all_quotas = quotas + [
            QuotaConfig(
                id="unlimited", limit=None, window=60,
                reason_code="unlimited"),
            QuotaConfig(id="dummy", limit=10, window=60, reason_code="dummy"),
        ]

        usage = self.quota.get_usage(self.project.organization_id,
                                     all_quotas,
                                     timestamp=timestamp)

        # Only quotas with an ID are counted in Redis (via this ID). Assume the
        # count for these quotas and None for the others.
        assert usage == [n if q.id else None for q in quotas] + [0, 0]

    @mock.patch.object(RedisQuota, "get_quotas")
    def test_refund_defaults(self, mock_get_quotas):
        timestamp = time.time()

        mock_get_quotas.return_value = (
            QuotaConfig(
                id="p",
                scope=QuotaScope.PROJECT,
                scope_id=1,
                limit=None,
                window=1,
                reason_code="project_quota",
                categories=[DataCategory.ERROR],
            ),
            QuotaConfig(
                id="p",
                scope=QuotaScope.PROJECT,
                scope_id=2,
                limit=1,
                window=1,
                reason_code="project_quota",
                categories=[DataCategory.ERROR],
            ),
            # Should be ignored
            QuotaConfig(
                id="a",
                scope=QuotaScope.PROJECT,
                scope_id=1,
                limit=1**6,
                window=1,
                reason_code="attachment_quota",
                categories=[DataCategory.ATTACHMENT],
            ),
        )

        self.quota.refund(self.project, timestamp=timestamp)
        client = self.quota.cluster.get_local_client_for_key(
            str(self.project.organization.pk))

        error_keys = client.keys("r:quota:p:?:*")
        assert len(error_keys) == 2

        for key in error_keys:
            assert client.get(key) == b"1"

        attachment_keys = client.keys("r:quota:a:*")
        assert len(attachment_keys) == 0

    @mock.patch.object(RedisQuota, "get_quotas")
    def test_refund_categories(self, mock_get_quotas):
        timestamp = time.time()

        mock_get_quotas.return_value = (
            QuotaConfig(
                id="p",
                scope=QuotaScope.PROJECT,
                scope_id=1,
                limit=None,
                window=1,
                reason_code="project_quota",
                categories=[DataCategory.ERROR],
            ),
            QuotaConfig(
                id="p",
                scope=QuotaScope.PROJECT,
                scope_id=2,
                limit=1,
                window=1,
                reason_code="project_quota",
                categories=[DataCategory.ERROR],
            ),
            # Should be ignored
            QuotaConfig(
                id="a",
                scope=QuotaScope.PROJECT,
                scope_id=1,
                limit=1**6,
                window=1,
                reason_code="attachment_quota",
                categories=[DataCategory.ATTACHMENT],
            ),
        )

        self.quota.refund(self.project,
                          timestamp=timestamp,
                          category=DataCategory.ATTACHMENT,
                          quantity=100)
        client = self.quota.cluster.get_local_client_for_key(
            str(self.project.organization.pk))

        error_keys = client.keys("r:quota:p:?:*")
        assert len(error_keys) == 0

        attachment_keys = client.keys("r:quota:a:*")
        assert len(attachment_keys) == 1

        for key in attachment_keys:
            assert client.get(key) == b"100"

    def test_get_usage_uses_refund(self):
        timestamp = time.time()

        self.get_project_quota.return_value = (200, 60)
        self.get_organization_quota.return_value = (300, 60)

        n = 10
        for _ in range(n):
            self.quota.is_rate_limited(self.project, timestamp=timestamp)

        self.quota.refund(self.project, timestamp=timestamp)

        quotas = self.quota.get_quotas(self.project)
        all_quotas = quotas + [
            QuotaConfig(
                id="unlimited", limit=None, window=60,
                reason_code="unlimited"),
            QuotaConfig(id="dummy", limit=10, window=60, reason_code="dummy"),
        ]

        usage = self.quota.get_usage(self.project.organization_id,
                                     all_quotas,
                                     timestamp=timestamp)

        # Only quotas with an ID are counted in Redis (via this ID). Assume the
        # count for these quotas and None for the others.
        # The ``- 1`` is because we refunded once.
        assert usage == [n - 1 if q.id else None for q in quotas] + [0, 0]
Пример #15
0
class OptionsStoreTest(TestCase):
    store = fixture(OptionsStore)

    @fixture
    def key(self):
        return self.make_key()

    @before
    def flush_local_cache(self):
        self.store.flush_local_cache()

    def make_key(self, ttl=10, grace=10):
        return self.store.make_key(uuid1().hex, '', object, 0, ttl, grace)

    def test_simple(self):
        store, key = self.store, self.key
        assert store.get(key) is None
        assert store.set(key, 'bar')
        assert store.get(key) == 'bar'
        assert store.delete(key)
        assert store.get(key) is None

    def test_db_and_cache_unavailable(self):
        store, key = self.store, self.key
        with patch.object(Option.objects, 'get_queryset', side_effect=Exception()):
            # we can't update options if the db is unavailable
            with self.assertRaises(Exception):
                store.set(key, 'bar')

        # Assert nothing was written to the local_cache
        assert not store._local_cache

        store.set(key, 'bar')

        with patch.object(Option.objects, 'get_queryset', side_effect=Exception()):
            assert store.get(key) == 'bar'

            with patch.object(store.cache, 'get', side_effect=Exception()):
                assert store.get(key) == 'bar'
                store.flush_local_cache()
                assert store.get(key) is None

    @patch('sentry.options.store.time')
    def test_key_with_grace(self, mocked_time):
        store, key = self.store, self.make_key(10, 10)

        mocked_time.return_value = 0
        store.set(key, 'bar')

        with patch.object(Option.objects, 'get_queryset', side_effect=Exception()):
            with patch.object(store.cache, 'get', side_effect=Exception()):
                # Serves the value beyond TTL
                mocked_time.return_value = 15
                assert store.get(key) == 'bar'

                mocked_time.return_value = 21
                assert store.get(key) is None

                # It should have also been evicted
                assert not store._local_cache

    @patch('sentry.options.store.time')
    def test_key_ttl(self, mocked_time):
        store, key = self.store, self.make_key(10, 0)

        mocked_time.return_value = 0
        store.set(key, 'bar')

        with patch.object(Option.objects, 'get_queryset', side_effect=Exception()):
            with patch.object(store.cache, 'get', side_effect=Exception()):
                assert store.get(key) == 'bar'

        Option.objects.filter(key=key.name).update(value='lol')
        store.cache.delete(key.cache_key)
        # Still within TTL, so don't check database
        assert store.get(key) == 'bar'

        mocked_time.return_value = 15

        with patch.object(Option.objects, 'get_queryset', side_effect=Exception()):
            with patch.object(store.cache, 'get', side_effect=Exception()):
                assert store.get(key) is None

        assert store.get(key) == 'lol'

    @patch('sentry.options.store.time')
    def test_clean_local_cache(self, mocked_time):
        store = self.store

        mocked_time.return_value = 0

        key1 = self.make_key(10, 0)  # should expire after 10
        key2 = self.make_key(10, 5)  # should expire after 15
        key3 = self.make_key(10, 10)  # should expire after 20
        key4 = self.make_key(10, 15)  # should expire after 25

        store.set(key1, 'x')
        store.set(key2, 'x')
        store.set(key3, 'x')
        store.set(key4, 'x')

        assert len(store._local_cache) == 4

        mocked_time.return_value = 0
        store.clean_local_cache()
        assert len(store._local_cache) == 4

        mocked_time.return_value = 11
        store.clean_local_cache()
        assert len(store._local_cache) == 3
        assert key1.cache_key not in store._local_cache

        mocked_time.return_value = 21
        store.clean_local_cache()
        assert len(store._local_cache) == 1
        assert key1.cache_key not in store._local_cache
        assert key2.cache_key not in store._local_cache
        assert key3.cache_key not in store._local_cache

        mocked_time.return_value = 26
        store.clean_local_cache()
        assert not store._local_cache
Пример #16
0
class RequestTimingMiddlewareTest(TestCase):
    middleware = fixture(RequestTimingMiddleware)
    factory = fixture(RequestFactory)

    @patch("sentry.utils.metrics.incr")
    def test_records_default_api_metrics(self, incr):
        request = self.factory.get("/")
        request._view_path = "/"
        response = Mock(status_code=200)

        self.middleware.process_response(request, response)

        incr.assert_called_with(
            "view.response",
            instance=request._view_path,
            tags={
                "method": "GET",
                "status_code": 200
            },
            skip_internal=False,
        )

    @patch("sentry.utils.metrics.incr")
    def test_records_endpoint_specific_metrics(self, incr):
        request = self.factory.get("/")
        request._view_path = "/"
        request._metric_tags = {"a": "b"}

        response = Mock(status_code=200)

        self.middleware.process_response(request, response)

        incr.assert_called_with(
            "view.response",
            instance=request._view_path,
            tags={
                "method": "GET",
                "status_code": 200,
                "a": "b"
            },
            skip_internal=False,
        )

    @patch("sentry.utils.metrics.incr")
    def test_add_request_metric_tags(self, incr):
        request = self.factory.get("/")
        request._view_path = "/"

        add_request_metric_tags(request, foo="bar")

        response = Mock(status_code=200)

        self.middleware.process_response(request, response)

        incr.assert_called_with(
            "view.response",
            instance=request._view_path,
            tags={
                "method": "GET",
                "status_code": 200,
                "foo": "bar"
            },
            skip_internal=False,
        )
Пример #17
0
class RequestTimingMiddlewareTest(TestCase):
    middleware = fixture(RequestTimingMiddleware)
    factory = fixture(RequestFactory)

    @patch('sentry.utils.metrics.incr')
    def test_records_default_api_metrics(self, incr):
        request = self.factory.get('/')
        request._view_path = '/'
        response = Mock(status_code=200)

        self.middleware.process_response(request, response)

        incr.assert_called_with(
            'view.response',
            instance=request._view_path,
            tags={
                'method': 'GET',
                'status_code': 200,
            },
            skip_internal=False,
        )

    @patch('sentry.utils.metrics.incr')
    def test_records_endpoint_specific_metrics(self, incr):
        request = self.factory.get('/')
        request._view_path = '/'
        request._metric_tags = {'a': 'b'}

        response = Mock(status_code=200)

        self.middleware.process_response(request, response)

        incr.assert_called_with(
            'view.response',
            instance=request._view_path,
            tags={
                'method': 'GET',
                'status_code': 200,
                'a': 'b',
            },
            skip_internal=False,
        )

    @patch('sentry.utils.metrics.incr')
    def test_add_request_metric_tags(self, incr):
        request = self.factory.get('/')
        request._view_path = '/'

        add_request_metric_tags(request, foo='bar')

        response = Mock(status_code=200)

        self.middleware.process_response(request, response)

        incr.assert_called_with(
            'view.response',
            instance=request._view_path,
            tags={
                'method': 'GET',
                'status_code': 200,
                'foo': 'bar',
            },
            skip_internal=False,
        )
Пример #18
0
class RedisQuotaTest(TestCase):
    quota = fixture(RedisQuota)

    @patcher.object(RedisQuota, "get_project_quota")
    def get_project_quota(self):
        inst = mock.MagicMock()
        inst.return_value = (0, 60)
        return inst

    @patcher.object(RedisQuota, "get_organization_quota")
    def get_organization_quota(self):
        inst = mock.MagicMock()
        inst.return_value = (0, 60)
        return inst

    def test_uses_defined_quotas(self):
        self.get_project_quota.return_value = (200, 60)
        self.get_organization_quota.return_value = (300, 60)
        quotas = self.quota.get_quotas(self.project)
        assert quotas[0].prefix == u"p"
        assert quotas[0].subscope == self.project.id
        assert quotas[0].limit == 200
        assert quotas[0].window == 60
        assert quotas[1].prefix == u"o"
        assert quotas[1].subscope is None
        assert quotas[1].limit == 300
        assert quotas[1].window == 60

    @mock.patch("sentry.quotas.redis.is_rate_limited")
    @mock.patch.object(RedisQuota, "get_quotas", return_value=[])
    def test_bails_immediately_without_any_quota(self, get_quotas,
                                                 is_rate_limited):
        result = self.quota.is_rate_limited(self.project)
        assert not is_rate_limited.called
        assert not result.is_limited

    @mock.patch("sentry.quotas.redis.is_rate_limited",
                return_value=(False, False))
    def test_is_not_limited_without_rejections(self, is_rate_limited):
        self.get_organization_quota.return_value = (100, 60)
        self.get_project_quota.return_value = (200, 60)
        assert not self.quota.is_rate_limited(self.project).is_limited

    @mock.patch("sentry.quotas.redis.is_rate_limited",
                return_value=(True, False))
    def test_is_limited_on_rejections(self, is_rate_limited):
        self.get_organization_quota.return_value = (100, 60)
        self.get_project_quota.return_value = (200, 60)
        assert self.quota.is_rate_limited(self.project).is_limited

    @mock.patch.object(RedisQuota, "get_quotas")
    @mock.patch("sentry.quotas.redis.is_rate_limited",
                return_value=(False, False))
    def test_not_limited_with_unlimited_quota(self, mock_is_rate_limited,
                                              mock_get_quotas):
        mock_get_quotas.return_value = (
            BasicRedisQuota(prefix="p",
                            subscope=1,
                            limit=None,
                            window=1,
                            reason_code="project_quota"),
            BasicRedisQuota(prefix="p",
                            subscope=2,
                            limit=1,
                            window=1,
                            reason_code="project_quota"),
        )

        assert not self.quota.is_rate_limited(self.project).is_limited

    @mock.patch.object(RedisQuota, "get_quotas")
    @mock.patch("sentry.quotas.redis.is_rate_limited",
                return_value=(False, True))
    def test_limited_with_unlimited_quota(self, mock_is_rate_limited,
                                          mock_get_quotas):
        mock_get_quotas.return_value = (
            BasicRedisQuota(prefix="p",
                            subscope=1,
                            limit=None,
                            window=1,
                            reason_code="project_quota"),
            BasicRedisQuota(prefix="p",
                            subscope=2,
                            limit=1,
                            window=1,
                            reason_code="project_quota"),
        )

        assert self.quota.is_rate_limited(self.project).is_limited

    def test_get_usage(self):
        timestamp = time.time()

        self.get_project_quota.return_value = (200, 60)
        self.get_organization_quota.return_value = (300, 60)

        n = 10
        for _ in xrange(n):
            self.quota.is_rate_limited(self.project, timestamp=timestamp)

        quotas = self.quota.get_quotas(self.project)

        assert self.quota.get_usage(
            self.project.organization_id,
            quotas + [
                BasicRedisQuota(prefix="unlimited",
                                limit=None,
                                window=60,
                                reason_code="unlimited"),
                BasicRedisQuota(
                    prefix="dummy", limit=10, window=60, reason_code="dummy"),
            ],
            timestamp=timestamp,
        ) == [n for _ in quotas] + [0, 0]

    @mock.patch.object(RedisQuota, "get_quotas")
    def test_refund(self, mock_get_quotas):
        timestamp = time.time()

        mock_get_quotas.return_value = (
            BasicRedisQuota(prefix="p",
                            subscope=1,
                            limit=None,
                            window=1,
                            reason_code="project_quota"),
            BasicRedisQuota(prefix="p",
                            subscope=2,
                            limit=1,
                            window=1,
                            reason_code="project_quota"),
        )

        self.quota.refund(self.project, timestamp=timestamp)
        client = self.quota.cluster.get_local_client_for_key(
            six.text_type(self.project.organization.pk))

        keys = client.keys("r:quota:p:?:*")

        assert len(keys) == 2

        for key in keys:
            assert client.get(key) == "1"

    def test_get_usage_uses_refund(self):
        timestamp = time.time()

        self.get_project_quota.return_value = (200, 60)
        self.get_organization_quota.return_value = (300, 60)

        n = 10
        for _ in xrange(n):
            self.quota.is_rate_limited(self.project, timestamp=timestamp)

        self.quota.refund(self.project, timestamp=timestamp)

        quotas = self.quota.get_quotas(self.project)

        assert self.quota.get_usage(
            self.project.organization_id,
            quotas + [
                BasicRedisQuota(prefix="unlimited",
                                limit=None,
                                window=60,
                                reason_code="unlimited"),
                BasicRedisQuota(
                    prefix="dummy", limit=10, window=60, reason_code="dummy"),
            ],
            timestamp=timestamp,
            # the - 1 is because we refunded once
        ) == [n - 1 for _ in quotas] + [0, 0]
Пример #19
0
class SwitchFormManagerTest(Exam, unittest2.TestCase):
    @fixture
    def manager(self):
        return SwitchFormManager(MagicMock(), MagicMock())

    gutter_manager = fixture(Mock)

    def test_init_constructs_with_switch_and_conditionset_forms(self):
        manager = SwitchFormManager(sentinel.switch, sentinel.conditionset)
        eq_(manager.switch, sentinel.switch)
        eq_(manager.conditions, sentinel.conditionset)

    @patch('gutter.django.forms.ConditionFormSet')
    @patch('gutter.django.forms.SwitchForm')
    def test_from_post_constructs_switch_and_conditions_then_self(
            self, s, cfs):
        manager = SwitchFormManager.from_post(sentinel.POST)

        s.assert_called_once_with(sentinel.POST)
        cfs.assert_called_once_with(sentinel.POST)

        eq_(manager.switch, s.return_value)
        eq_(manager.conditions, cfs.return_value)

    def test_is_valid_asks_switch_and_conditions(self):
        self.manager.switch.is_valid.return_value = True
        self.manager.conditions.is_valid.return_value = True
        eq_(self.manager.is_valid(), True)

        self.manager.switch.is_valid.return_value = False
        self.manager.conditions.is_valid.return_value = True
        eq_(self.manager.is_valid(), False)

        self.manager.switch.is_valid.return_value = True
        self.manager.conditions.is_valid.return_value = False
        eq_(self.manager.is_valid(), False)

        self.manager.switch.is_valid.return_value = False
        self.manager.conditions.is_valid.return_value = False
        eq_(self.manager.is_valid(), False)

    def test_save_updates_manager_switch_with_switch_to_object(self):
        self.manager.conditions.__iter__ = [Mock(), Mock()]
        self.manager.save(self.gutter_manager)
        self.gutter_manager.register.assert_called_once_with(
            self.manager.switch.to_object)

    def test_save_sets_conditions_on_switch_before_registering(self):
        self.manager.save(self.gutter_manager)
        args, kwargs = self.gutter_manager.register.call_args
        switch = args[0]

        eq_(switch.conditions, self.manager.conditions.to_objects)

    def test_add_to_switch_list_adds_switch_at_name_from_forms(self):
        switches = []
        self.manager.add_to_switch_list(switches)
        eq_(switches[0], self.manager.switch)
        eq_(switches[0].conditions, self.manager.conditions)

    def test_delete_unregisters_switch_with_manager(self):
        self.manager.delete(self.gutter_manager)
        self.gutter_manager.unregister.assert_called_once_with(
            self.manager.switch.data['name'])
Пример #20
0
from tests import TestCase

from exam import Exam, fixture
from exam.asserts import AssertsMixin


class AssertChangesMixin(Exam, TestCase):

    case = fixture(AssertsMixin)
    thing = fixture(list)

    def no_op_context(self, *args, **kwargs):
        with self.case.assertChanges(len, self.thing, *args, **kwargs):
            pass

    def test_checks_change_on_callable_passed(self):
        with self.case.assertChanges(len, self.thing, before=0, after=1):
            self.thing.append(1)

    def test_after_check_asserts_ends_on_after_value(self):
        self.thing.append(1)
        with self.case.assertChanges(len, self.thing, after=2):
            self.thing.append(1)

    def test_before_check_asserts_starts_on_before_value(self):
        self.thing.append(1)
        with self.case.assertChanges(len, self.thing, before=1):
            self.thing.append(1)
            self.thing.append(2)

    def test_verifies_value_must_change_no_matter_what(self):
Пример #21
0
class RedisQuotaTest(TestCase):
    quota = fixture(RedisQuota)

    @patcher.object(RedisQuota, 'get_project_quota')
    def get_project_quota(self):
        inst = mock.MagicMock()
        inst.return_value = (0, 60)
        return inst

    @patcher.object(RedisQuota, 'get_organization_quota')
    def get_organization_quota(self):
        inst = mock.MagicMock()
        inst.return_value = (0, 60)
        return inst

    def test_uses_defined_quotas(self):
        self.get_project_quota.return_value = (200, 60)
        self.get_organization_quota.return_value = (300, 60)
        quotas = self.quota.get_quotas(self.project)
        assert quotas[0].key == u'p:{}'.format(self.project.id)
        assert quotas[0].limit == 200
        assert quotas[0].window == 60
        assert quotas[1].key == u'o:{}'.format(self.project.organization.id)
        assert quotas[1].limit == 300
        assert quotas[1].window == 60

    @mock.patch('sentry.quotas.redis.is_rate_limited')
    @mock.patch.object(RedisQuota, 'get_quotas', return_value=[])
    def test_bails_immediately_without_any_quota(self, get_quotas,
                                                 is_rate_limited):
        result = self.quota.is_rate_limited(self.project)
        assert not is_rate_limited.called
        assert not result.is_limited

    @mock.patch('sentry.quotas.redis.is_rate_limited',
                return_value=(False, False))
    def test_is_not_limited_without_rejections(self, is_rate_limited):
        self.get_organization_quota.return_value = (100, 60)
        self.get_project_quota.return_value = (200, 60)
        assert not self.quota.is_rate_limited(self.project).is_limited

    @mock.patch('sentry.quotas.redis.is_rate_limited',
                return_value=(True, False))
    def test_is_limited_on_rejections(self, is_rate_limited):
        self.get_organization_quota.return_value = (100, 60)
        self.get_project_quota.return_value = (200, 60)
        assert self.quota.is_rate_limited(self.project).is_limited

    @mock.patch.object(RedisQuota, 'get_quotas')
    @mock.patch('sentry.quotas.redis.is_rate_limited',
                return_value=(True, False))
    def test_not_limited_without_enforce(self, mock_is_rate_limited,
                                         mock_get_quotas):
        mock_get_quotas.return_value = (
            BasicRedisQuota(
                key='p:1',
                limit=1,
                window=1,
                reason_code='project_quota',
                enforce=False,
            ),
            BasicRedisQuota(
                key='p:2',
                limit=1,
                window=1,
                reason_code='project_quota',
                enforce=True,
            ),
        )

        assert not self.quota.is_rate_limited(self.project).is_limited

    @mock.patch.object(RedisQuota, 'get_quotas')
    @mock.patch('sentry.quotas.redis.is_rate_limited',
                return_value=(True, True))
    def test_limited_without_enforce(self, mock_is_rate_limited,
                                     mock_get_quotas):
        mock_get_quotas.return_value = (
            BasicRedisQuota(
                key='p:1',
                limit=1,
                window=1,
                reason_code='project_quota',
                enforce=False,
            ),
            BasicRedisQuota(
                key='p:2',
                limit=1,
                window=1,
                reason_code='project_quota',
                enforce=True,
            ),
        )

        assert self.quota.is_rate_limited(self.project).is_limited

    def test_get_usage(self):
        timestamp = time.time()

        self.get_project_quota.return_value = (200, 60)
        self.get_organization_quota.return_value = (300, 60)

        n = 10
        for _ in xrange(n):
            self.quota.is_rate_limited(self.project, timestamp=timestamp)

        quotas = self.quota.get_quotas(self.project)

        assert self.quota.get_usage(
            self.project.organization_id,
            quotas + [
                BasicRedisQuota(
                    key='unlimited',
                    limit=0,
                    window=60,
                    reason_code='unlimited',
                ),
                BasicRedisQuota(
                    key='dummy',
                    limit=10,
                    window=60,
                    reason_code='dummy',
                ),
            ],
            timestamp=timestamp,
        ) == [n for _ in quotas] + [None, 0]

    @mock.patch.object(RedisQuota, 'get_quotas')
    def test_refund(self, mock_get_quotas):
        timestamp = time.time()

        mock_get_quotas.return_value = (
            BasicRedisQuota(
                key='p:1',
                limit=1,
                window=1,
                reason_code='project_quota',
                enforce=False,
            ),
            BasicRedisQuota(
                key='p:2',
                limit=1,
                window=1,
                reason_code='project_quota',
                enforce=True,
            ),
        )

        self.quota.refund(self.project, timestamp=timestamp)
        client = self.quota.cluster.get_local_client_for_key(
            six.text_type(self.project.organization.pk))

        keys = client.keys('r:quota:p:?:*')

        assert len(keys) == 2

        for key in keys:
            assert client.get(key) == '1'

    def test_get_usage_uses_refund(self):
        timestamp = time.time()

        self.get_project_quota.return_value = (200, 60)
        self.get_organization_quota.return_value = (300, 60)

        n = 10
        for _ in xrange(n):
            self.quota.is_rate_limited(self.project, timestamp=timestamp)

        self.quota.refund(self.project, timestamp=timestamp)

        quotas = self.quota.get_quotas(self.project)

        assert self.quota.get_usage(
            self.project.organization_id,
            quotas + [
                BasicRedisQuota(
                    key='unlimited',
                    limit=0,
                    window=60,
                    reason_code='unlimited',
                ),
                BasicRedisQuota(
                    key='dummy',
                    limit=10,
                    window=60,
                    reason_code='dummy',
                ),
            ],
            timestamp=timestamp,
            # the - 1 is because we refunded once
        ) == [n - 1 for _ in quotas] + [None, 0]
Пример #22
0
class AuthenticationMiddlewareTestCase(TestCase):
    middleware = fixture(AuthenticationMiddleware)

    @fixture
    def request(self):
        rv = RequestFactory().get("/")
        rv.session = self.session
        return rv

    def test_process_request_anon(self):
        self.middleware.process_request(self.request)
        assert self.request.user.is_anonymous

    def test_process_request_user(self):
        request = self.request
        assert login(request, self.user)
        self.middleware.process_request(request)
        assert request.user.is_authenticated
        assert request.user == self.user
        assert "_nonce" not in request.session
        assert UserIP.objects.filter(user=self.user,
                                     ip_address="127.0.0.1").exists()

    def test_process_request_good_nonce(self):
        request = self.request
        user = self.user
        user.session_nonce = "xxx"
        user.save()
        assert login(request, user)
        self.middleware.process_request(request)
        assert request.user.is_authenticated
        assert request.user == self.user
        assert request.session["_nonce"] == "xxx"

    def test_process_request_missing_nonce(self):
        request = self.request
        user = self.user
        user.session_nonce = "xxx"
        user.save()
        assert login(request, user)
        del request.session["_nonce"]
        self.middleware.process_request(request)
        assert request.user.is_anonymous

    def test_process_request_bad_nonce(self):
        request = self.request
        user = self.user
        user.session_nonce = "xxx"
        user.save()
        assert login(request, user)
        request.session["_nonce"] = "gtfo"
        self.middleware.process_request(request)
        assert request.user.is_anonymous

    def test_process_request_valid_authtoken(self):
        token = ApiToken.objects.create(user=self.user,
                                        scope_list=["event:read", "org:read"])
        request = self.make_request(method="GET")
        request.META["HTTP_AUTHORIZATION"] = f"Bearer {token.token}"
        self.middleware.process_request(request)
        assert request.user == self.user
        assert request.auth == token

    def test_process_request_invalid_authtoken(self):
        request = self.make_request(method="GET")
        request.META["HTTP_AUTHORIZATION"] = "Bearer absadadafdf"
        self.middleware.process_request(request)
        # Should swallow errors and pass on
        assert request.user.is_anonymous
        assert request.auth is None

    def test_process_request_valid_apikey(self):
        apikey = ApiKey.objects.create(organization=self.organization,
                                       allowed_origins="*")

        request = self.make_request(method="GET")
        request.META["HTTP_AUTHORIZATION"] = b"Basic " + base64.b64encode(
            apikey.key.encode("utf-8"))

        self.middleware.process_request(request)
        # ApiKey is tied to an organization not user
        assert request.user.is_anonymous
        assert request.auth == apikey

    def test_process_request_invalid_apikey(self):
        request = self.make_request(method="GET")
        request.META["HTTP_AUTHORIZATION"] = b"Basic adfasdfasdfsadfsaf"

        self.middleware.process_request(request)
        # Should swallow errors and pass on
        assert request.user.is_anonymous
        assert request.auth is None
Пример #23
0
class RatelimitMiddlewareTest(TestCase):
    middleware = fixture(RatelimitMiddleware)
    factory = fixture(RequestFactory)

    class TestEndpoint(Endpoint):
        def get(self):
            return Response({"ok": True})

    _test_endpoint = TestEndpoint.as_view()

    def populate_sentry_app_request(self, request):
        sentry_app = self.create_sentry_app(
            name="Bubbly Webhook",
            organization=self.organization,
            webhook_url="https://example.com",
            scopes=["event:write"],
        )

        internal_integration = self.create_internal_integration(
            name="my_app",
            organization=self.organization,
            scopes=("project:read",),
            webhook_url="http://example.com",
        )
        # there should only be one record created so just grab the first one
        install = SentryAppInstallation.objects.get(
            sentry_app=internal_integration.id, organization=self.organization
        )
        token = install.api_token

        request.user = User.objects.get(id=sentry_app.proxy_user_id)
        request.auth = token

    @patch("sentry.middleware.ratelimit.get_rate_limit_value")
    def test_positive_rate_limit_check(self, default_rate_limit_mock):
        request = self.factory.get("/")
        with freeze_time("2000-01-01"):
            default_rate_limit_mock.return_value = RateLimit(0, 100)
            self.middleware.process_view(request, self._test_endpoint, [], {})
            assert request.will_be_rate_limited

        with freeze_time("2000-01-02"):
            # 10th request in a 10 request window should get rate limited
            default_rate_limit_mock.return_value = RateLimit(10, 100)
            for _ in range(10):
                self.middleware.process_view(request, self._test_endpoint, [], {})
                assert not request.will_be_rate_limited

            self.middleware.process_view(request, self._test_endpoint, [], {})
            assert request.will_be_rate_limited

    @patch("sentry.middleware.ratelimit.get_rate_limit_value")
    def test_negative_rate_limit_check(self, default_rate_limit_mock):
        request = self.factory.get("/")
        default_rate_limit_mock.return_value = RateLimit(10, 100)
        self.middleware.process_view(request, self._test_endpoint, [], {})
        assert not request.will_be_rate_limited

        # Requests outside the current window should not be rate limited
        default_rate_limit_mock.return_value = RateLimit(1, 1)
        with freeze_time("2000-01-01") as frozen_time:
            self.middleware.process_view(request, self._test_endpoint, [], {})
            assert not request.will_be_rate_limited
            frozen_time.tick(1)
            self.middleware.process_view(request, self._test_endpoint, [], {})
            assert not request.will_be_rate_limited

    def test_rate_limit_category(self):
        request = self.factory.get("/")
        request.META["REMOTE_ADDR"] = None
        self.middleware.process_view(request, self._test_endpoint, [], {})
        assert request.rate_limit_category is None

        request = self.factory.get("/")
        self.middleware.process_view(request, self._test_endpoint, [], {})
        assert request.rate_limit_category == "ip"

        request.session = {}
        request.user = self.user
        self.middleware.process_view(request, self._test_endpoint, [], {})
        assert request.rate_limit_category == "user"

        self.populate_sentry_app_request(request)
        self.middleware.process_view(request, self._test_endpoint, [], {})
        assert request.rate_limit_category == "org"

    def test_get_rate_limit_key(self):
        # Import an endpoint

        view = OrganizationGroupIndexEndpoint.as_view()

        # Test for default IP
        request = self.factory.get("/")
        assert (
            get_rate_limit_key(view, request)
            == "ip:default:OrganizationGroupIndexEndpoint:GET:127.0.0.1"
        )
        # Test when IP address is missing
        request.META["REMOTE_ADDR"] = None
        assert get_rate_limit_key(view, request) is None

        # Test when IP addess is IPv6
        request.META["REMOTE_ADDR"] = "684D:1111:222:3333:4444:5555:6:77"
        assert (
            get_rate_limit_key(view, request)
            == "ip:default:OrganizationGroupIndexEndpoint:GET:684D:1111:222:3333:4444:5555:6:77"
        )

        # Test for users
        request.session = {}
        request.user = self.user
        assert (
            get_rate_limit_key(view, request)
            == f"user:default:OrganizationGroupIndexEndpoint:GET:{self.user.id}"
        )

        # Test for user auth tokens
        token = ApiToken.objects.create(user=self.user, scope_list=["event:read", "org:read"])
        request.auth = token
        request.user = self.user
        assert (
            get_rate_limit_key(view, request)
            == f"user:default:OrganizationGroupIndexEndpoint:GET:{self.user.id}"
        )

        # Test for sentryapp auth tokens:
        self.populate_sentry_app_request(request)
        assert (
            get_rate_limit_key(view, request)
            == f"org:default:OrganizationGroupIndexEndpoint:GET:{self.organization.id}"
        )

        # Test for apikey
        api_key = ApiKey.objects.create(
            organization=self.organization, scope_list=["project:write"]
        )
        request.user = AnonymousUser()
        request.auth = api_key
        assert (
            get_rate_limit_key(view, request)
            == "ip:default:OrganizationGroupIndexEndpoint:GET:684D:1111:222:3333:4444:5555:6:77"
        )
Пример #24
0
class RedisQuotaTest(TestCase):
    quota = fixture(RedisQuota)

    @patcher.object(RedisQuota, "get_project_quota")
    def get_project_quota(self):
        inst = mock.MagicMock()
        inst.return_value = (0, 60)
        return inst

    @patcher.object(RedisQuota, "get_organization_quota")
    def get_organization_quota(self):
        inst = mock.MagicMock()
        inst.return_value = (0, 60)
        return inst

    def test_uses_defined_quotas(self):
        self.get_project_quota.return_value = (200, 60)
        self.get_organization_quota.return_value = (300, 60)
        with self.feature("organizations:releases-v2"):
            quotas = self.quota.get_quotas(self.project)

        assert quotas[0].id == u"p"
        assert quotas[0].scope == QuotaScope.PROJECT
        assert quotas[0].scope_id == six.text_type(self.project.id)
        assert quotas[0].limit == 200
        assert quotas[0].window == 60
        assert quotas[1].id == u"o"
        assert quotas[1].scope == QuotaScope.ORGANIZATION
        assert quotas[1].scope_id == six.text_type(self.organization.id)
        assert quotas[1].limit == 300
        assert quotas[1].window == 60

    def test_sessions_quota(self):
        self.get_project_quota.return_value = (200, 60)
        self.get_organization_quota.return_value = (300, 60)
        with self.feature({"organizations:releases-v2": False}):
            quotas = self.quota.get_quotas(self.project)

        assert quotas[0].id is None  # should not be counted
        assert quotas[0].categories == set([DataCategory.SESSION])
        assert quotas[0].scope == QuotaScope.ORGANIZATION
        assert quotas[0].limit == 0

    @mock.patch("sentry.quotas.redis.is_rate_limited")
    @mock.patch.object(RedisQuota, "get_quotas", return_value=[])
    def test_bails_immediately_without_any_quota(self, get_quotas, is_rate_limited):
        result = self.quota.is_rate_limited(self.project)
        assert not is_rate_limited.called
        assert not result.is_limited

    @mock.patch("sentry.quotas.redis.is_rate_limited", return_value=(False, False))
    def test_is_not_limited_without_rejections(self, is_rate_limited):
        self.get_organization_quota.return_value = (100, 60)
        self.get_project_quota.return_value = (200, 60)
        assert not self.quota.is_rate_limited(self.project).is_limited

    @mock.patch("sentry.quotas.redis.is_rate_limited", return_value=(True, False))
    def test_is_limited_on_rejections(self, is_rate_limited):
        self.get_organization_quota.return_value = (100, 60)
        self.get_project_quota.return_value = (200, 60)
        assert self.quota.is_rate_limited(self.project).is_limited

    @mock.patch.object(RedisQuota, "get_quotas")
    @mock.patch("sentry.quotas.redis.is_rate_limited", return_value=(False, False))
    def test_not_limited_with_unlimited_quota(self, mock_is_rate_limited, mock_get_quotas):
        mock_get_quotas.return_value = (
            QuotaConfig(
                id="p",
                scope=QuotaScope.PROJECT,
                scope_id=1,
                limit=None,
                window=1,
                reason_code="project_quota",
            ),
            QuotaConfig(
                id="p",
                scope=QuotaScope.PROJECT,
                scope_id=2,
                limit=1,
                window=1,
                reason_code="project_quota",
            ),
        )

        assert not self.quota.is_rate_limited(self.project).is_limited

    @mock.patch.object(RedisQuota, "get_quotas")
    @mock.patch("sentry.quotas.redis.is_rate_limited", return_value=(False, True))
    def test_limited_with_unlimited_quota(self, mock_is_rate_limited, mock_get_quotas):
        mock_get_quotas.return_value = (
            QuotaConfig(
                id="p",
                scope=QuotaScope.PROJECT,
                scope_id=1,
                limit=None,
                window=1,
                reason_code="project_quota",
            ),
            QuotaConfig(
                id="p",
                scope=QuotaScope.PROJECT,
                scope_id=2,
                limit=1,
                window=1,
                reason_code="project_quota",
            ),
        )

        assert self.quota.is_rate_limited(self.project).is_limited

    def test_get_usage(self):
        timestamp = time.time()

        self.get_project_quota.return_value = (200, 60)
        self.get_organization_quota.return_value = (300, 60)

        n = 10
        for _ in xrange(n):
            self.quota.is_rate_limited(self.project, timestamp=timestamp)

        quotas = self.quota.get_quotas(self.project)
        all_quotas = quotas + [
            QuotaConfig(id="unlimited", limit=None, window=60, reason_code="unlimited"),
            QuotaConfig(id="dummy", limit=10, window=60, reason_code="dummy"),
        ]

        usage = self.quota.get_usage(self.project.organization_id, all_quotas, timestamp=timestamp)

        # Only quotas with an ID are counted in Redis (via this ID). Assume the
        # count for these quotas and None for the others.
        assert usage == [n if q.id else None for q in quotas] + [0, 0]

    @mock.patch.object(RedisQuota, "get_quotas")
    def test_refund(self, mock_get_quotas):
        timestamp = time.time()

        mock_get_quotas.return_value = (
            QuotaConfig(
                id="p",
                scope=QuotaScope.PROJECT,
                scope_id=1,
                limit=None,
                window=1,
                reason_code="project_quota",
            ),
            QuotaConfig(
                id="p",
                scope=QuotaScope.PROJECT,
                scope_id=2,
                limit=1,
                window=1,
                reason_code="project_quota",
            ),
        )

        self.quota.refund(self.project, timestamp=timestamp)
        client = self.quota.cluster.get_local_client_for_key(
            six.text_type(self.project.organization.pk)
        )

        keys = client.keys("r:quota:p:?:*")

        assert len(keys) == 2

        for key in keys:
            assert client.get(key) == "1"

    def test_get_usage_uses_refund(self):
        timestamp = time.time()

        self.get_project_quota.return_value = (200, 60)
        self.get_organization_quota.return_value = (300, 60)

        n = 10
        for _ in xrange(n):
            self.quota.is_rate_limited(self.project, timestamp=timestamp)

        self.quota.refund(self.project, timestamp=timestamp)

        quotas = self.quota.get_quotas(self.project)
        all_quotas = quotas + [
            QuotaConfig(id="unlimited", limit=None, window=60, reason_code="unlimited"),
            QuotaConfig(id="dummy", limit=10, window=60, reason_code="dummy"),
        ]

        usage = self.quota.get_usage(self.project.organization_id, all_quotas, timestamp=timestamp)

        # Only quotas with an ID are counted in Redis (via this ID). Assume the
        # count for these quotas and None for the others.
        # The ``- 1`` is because we refunded once.
        assert usage == [n - 1 if q.id else None for q in quotas] + [0, 0]