tests.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. # -*- coding: utf-8 -*-
  2. from __future__ import absolute_import
  3. import datetime
  4. import mock
  5. from django.conf import settings as django_settings
  6. from django.contrib.auth.models import User
  7. from django.core.urlresolvers import reverse
  8. from django.utils import timezone
  9. from raven import Client
  10. from sentry.models import Group, Event, Project
  11. from sentry.testutils import TestCase
  12. from sentry.utils.settings import validate_settings, ConfigurationError
  13. class RavenIntegrationTest(TestCase):
  14. """
  15. This mocks the test server and specifically tests behavior that would
  16. happen between Raven <--> Sentry over HTTP communication.
  17. """
  18. def setUp(self):
  19. self.user = User.objects.create(username='coreapi')
  20. self.project = Project.objects.create(owner=self.user, name='Foo', slug='bar')
  21. self.pm = self.project.team.member_set.get_or_create(user=self.user)[0]
  22. self.pk = self.project.key_set.get_or_create(user=self.user)[0]
  23. def sendRemote(self, url, data, headers={}):
  24. # TODO: make this install a temporary handler which raises an assertion error
  25. import logging
  26. sentry_errors = logging.getLogger('sentry.errors')
  27. sentry_errors.addHandler(logging.StreamHandler())
  28. sentry_errors.setLevel(logging.DEBUG)
  29. content_type = headers.pop('Content-Type', None)
  30. headers = dict(('HTTP_' + k.replace('-', '_').upper(), v) for k, v in headers.iteritems())
  31. resp = self.client.post(reverse('sentry-api-store', args=[self.pk.project_id]),
  32. data=data,
  33. content_type=content_type,
  34. **headers)
  35. self.assertEquals(resp.status_code, 200, resp.content)
  36. @mock.patch('raven.base.Client.send_remote')
  37. def test_basic(self, send_remote):
  38. send_remote.side_effect = self.sendRemote
  39. client = Client(
  40. project=self.pk.project_id,
  41. servers=['http://localhost:8000%s' % reverse('sentry-api-store', args=[self.pk.project_id])],
  42. public_key=self.pk.public_key,
  43. secret_key=self.pk.secret_key,
  44. )
  45. client.capture('Message', message='foo')
  46. send_remote.assert_called_once()
  47. self.assertEquals(Group.objects.count(), 1)
  48. group = Group.objects.get()
  49. self.assertEquals(group.event_set.count(), 1)
  50. instance = group.event_set.get()
  51. self.assertEquals(instance.message, 'foo')
  52. class SentryRemoteTest(TestCase):
  53. def test_correct_data(self):
  54. kwargs = {'message': 'hello', 'server_name': 'not_dcramer.local', 'level': 40, 'site': 'not_a_real_site'}
  55. resp = self._postWithHeader(kwargs)
  56. self.assertEquals(resp.status_code, 200, resp.content)
  57. instance = Event.objects.get()
  58. self.assertEquals(instance.message, 'hello')
  59. self.assertEquals(instance.server_name, 'not_dcramer.local')
  60. self.assertEquals(instance.level, 40)
  61. self.assertEquals(instance.site, 'not_a_real_site')
  62. def test_unicode_keys(self):
  63. kwargs = {u'message': 'hello', u'server_name': 'not_dcramer.local', u'level': 40, u'site': 'not_a_real_site'}
  64. resp = self._postWithSignature(kwargs)
  65. self.assertEquals(resp.status_code, 200, resp.content)
  66. instance = Event.objects.get()
  67. self.assertEquals(instance.message, 'hello')
  68. self.assertEquals(instance.server_name, 'not_dcramer.local')
  69. self.assertEquals(instance.level, 40)
  70. self.assertEquals(instance.site, 'not_a_real_site')
  71. def test_timestamp(self):
  72. timestamp = timezone.now().replace(microsecond=0, tzinfo=timezone.utc) - datetime.timedelta(hours=1)
  73. kwargs = {u'message': 'hello', 'timestamp': timestamp.strftime('%s.%f')}
  74. resp = self._postWithSignature(kwargs)
  75. self.assertEquals(resp.status_code, 200, resp.content)
  76. instance = Event.objects.get()
  77. self.assertEquals(instance.message, 'hello')
  78. self.assertEquals(instance.datetime, timestamp)
  79. group = instance.group
  80. self.assertEquals(group.first_seen, timestamp)
  81. self.assertEquals(group.last_seen, timestamp)
  82. def test_timestamp_as_iso(self):
  83. timestamp = timezone.now().replace(microsecond=0, tzinfo=timezone.utc) - datetime.timedelta(hours=1)
  84. kwargs = {u'message': 'hello', 'timestamp': timestamp.strftime('%Y-%m-%dT%H:%M:%S.%f')}
  85. resp = self._postWithSignature(kwargs)
  86. self.assertEquals(resp.status_code, 200, resp.content)
  87. instance = Event.objects.get()
  88. self.assertEquals(instance.message, 'hello')
  89. self.assertEquals(instance.datetime, timestamp)
  90. group = instance.group
  91. self.assertEquals(group.first_seen, timestamp)
  92. self.assertEquals(group.last_seen, timestamp)
  93. def test_ungzipped_data(self):
  94. kwargs = {'message': 'hello', 'server_name': 'not_dcramer.local', 'level': 40, 'site': 'not_a_real_site'}
  95. resp = self._postWithSignature(kwargs)
  96. self.assertEquals(resp.status_code, 200)
  97. instance = Event.objects.get()
  98. self.assertEquals(instance.message, 'hello')
  99. self.assertEquals(instance.server_name, 'not_dcramer.local')
  100. self.assertEquals(instance.site, 'not_a_real_site')
  101. self.assertEquals(instance.level, 40)
  102. # def test_byte_sequence(self):
  103. # """
  104. # invalid byte sequence for encoding "UTF8": 0xedb7af
  105. # """
  106. # # TODO:
  107. # # add 'site' to data in fixtures/bad_data.json, then assert it's set correctly below
  108. # fname = os.path.join(os.path.dirname(__file__), 'fixtures/bad_data.json')
  109. # data = open(fname).read()
  110. # resp = self.client.post(reverse('sentry-api-store'), {
  111. # 'data': data,
  112. # 'key': settings.KEY,
  113. # })
  114. # self.assertEquals(resp.status_code, 200)
  115. # self.assertEquals(Event.objects.count(), 1)
  116. # instance = Event.objects.get()
  117. # self.assertEquals(instance.message, 'DatabaseError: invalid byte sequence for encoding "UTF8": 0xeda4ac\nHINT: This error can also happen if the byte sequence does not match the encoding expected by the server, which is controlled by "client_encoding".\n')
  118. # self.assertEquals(instance.server_name, 'shilling.disqus.net')
  119. # self.assertEquals(instance.level, 40)
  120. def test_signature(self):
  121. kwargs = {'message': 'hello', 'server_name': 'not_dcramer.local', 'level': 40, 'site': 'not_a_real_site'}
  122. resp = self._postWithSignature(kwargs)
  123. self.assertEquals(resp.status_code, 200, resp.content)
  124. instance = Event.objects.get()
  125. self.assertEquals(instance.message, 'hello')
  126. self.assertEquals(instance.server_name, 'not_dcramer.local')
  127. self.assertEquals(instance.site, 'not_a_real_site')
  128. self.assertEquals(instance.level, 40)
  129. TEST_DATA = {
  130. "postgresql": ('DATABASES', 'psycopg2.extensions', "database engine", "django.db.backends.postgresql_psycopg2", {
  131. 'default': {
  132. 'ENGINE': "django.db.backends.postgresql_psycopg2",
  133. 'NAME': 'test',
  134. 'USER': 'root',
  135. 'PASSWORD': '',
  136. 'HOST': 'localhost',
  137. 'PORT': ''
  138. }
  139. }),
  140. "mysql": ('DATABASES', 'MySQLdb', "database engine", "django.db.backends.mysql", {
  141. 'default': {
  142. 'ENGINE': "django.db.backends.mysql",
  143. 'NAME': 'test',
  144. 'USER': 'root',
  145. 'PASSWORD': '',
  146. 'HOST': 'localhost',
  147. 'PORT': ''
  148. }
  149. }),
  150. "oracle": ('DATABASES', 'cx_Oracle', "database engine", "django.db.backends.oracle", {
  151. 'default': {
  152. 'ENGINE': "django.db.backends.oracle",
  153. 'NAME': 'test',
  154. 'USER': 'root',
  155. 'PASSWORD': '',
  156. 'HOST': 'localhost',
  157. 'PORT': ''
  158. }
  159. }),
  160. "memcache": ('CACHES', 'memcache', "caching backend", "django.core.cache.backends.memcached.MemcachedCache", {
  161. 'default': {
  162. 'BACKEND': "django.core.cache.backends.memcached.MemcachedCache",
  163. 'LOCATION': '127.0.0.1:11211',
  164. }
  165. }),
  166. "pylibmc": ('CACHES', 'pylibmc', "caching backend", "django.core.cache.backends.memcached.PyLibMCCache", {
  167. 'default': {
  168. 'BACKEND': "django.core.cache.backends.memcached.PyLibMCCache",
  169. 'LOCATION': '127.0.0.1:11211',
  170. }
  171. }),
  172. }
  173. class HttpServiceTest(TestCase):
  174. def raise_import_error(self, package):
  175. def callable(package_name):
  176. if package_name != package:
  177. raise RuntimeError("Package being tested differs from expected in test case")
  178. msg = "No module named %s" % package
  179. raise ImportError(msg)
  180. return callable
  181. @mock.patch('sentry.conf.settings')
  182. @mock.patch('sentry.utils.settings.import_string')
  183. def validate_dependency(self, key, package, dependency_type, dependency,
  184. setting_value, import_string, settings):
  185. import_string.side_effect = self.raise_import_error(package)
  186. with self.Settings(**{key: setting_value}):
  187. with self.assertRaises(ConfigurationError):
  188. validate_settings(django_settings)
  189. def test_validate_fails_on_postgres(self):
  190. self.validate_dependency(*TEST_DATA['postgresql'])
  191. def test_validate_fails_on_mysql(self):
  192. self.validate_dependency(*TEST_DATA['mysql'])
  193. def test_validate_fails_on_oracle(self):
  194. self.validate_dependency(*TEST_DATA['oracle'])
  195. def test_validate_fails_on_memcache(self):
  196. self.validate_dependency(*TEST_DATA['memcache'])
  197. def test_validate_fails_on_pylibmc(self):
  198. self.validate_dependency(*TEST_DATA['pylibmc'])