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={})
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
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)
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()
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
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
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
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'])
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'
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
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"
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]
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'}
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]
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
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, )
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, )
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]
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'])
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):
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]
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
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" )
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]