You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

241 lines
8.5 KiB

6 years ago
9 years ago
9 years ago
6 years ago
6 years ago
6 years ago
5 years ago
  1. # coding: utf-8
  2. from datetime import timedelta as td
  3. import json
  4. from unittest.mock import patch
  5. from django.core import mail
  6. from django.utils.timezone import now
  7. from hc.api.models import Channel, Check, Notification, TokenBucket
  8. from hc.test import BaseTestCase
  9. from django.test.utils import override_settings
  10. class NotifyTestCase(BaseTestCase):
  11. def _setup_data(self, kind, value, status="down", email_verified=True):
  12. self.check = Check(project=self.project)
  13. self.check.status = status
  14. self.check.last_ping = now() - td(minutes=61)
  15. self.check.save()
  16. self.channel = Channel(project=self.project)
  17. self.channel.kind = kind
  18. self.channel.value = value
  19. self.channel.email_verified = email_verified
  20. self.channel.save()
  21. self.channel.checks.add(self.check)
  22. @patch("hc.api.transports.requests.request")
  23. def test_pagerteam(self, mock_post):
  24. self._setup_data("pagerteam", "123")
  25. self.channel.notify(self.check)
  26. self.assertFalse(mock_post.called)
  27. self.assertEqual(Notification.objects.count(), 0)
  28. @patch("hc.api.transports.requests.request")
  29. def test_hipchat(self, mock_post):
  30. self._setup_data("hipchat", "123")
  31. self.channel.notify(self.check)
  32. self.assertFalse(mock_post.called)
  33. self.assertEqual(Notification.objects.count(), 0)
  34. @patch("hc.api.transports.requests.request")
  35. def test_victorops(self, mock_post):
  36. self._setup_data("victorops", "123")
  37. mock_post.return_value.status_code = 200
  38. self.channel.notify(self.check)
  39. assert Notification.objects.count() == 1
  40. args, kwargs = mock_post.call_args
  41. payload = kwargs["json"]
  42. self.assertEqual(payload["message_type"], "CRITICAL")
  43. @patch("hc.api.transports.requests.request")
  44. def test_discord(self, mock_post):
  45. v = json.dumps({"webhook": {"url": "123"}})
  46. self._setup_data("discord", v)
  47. mock_post.return_value.status_code = 200
  48. self.channel.notify(self.check)
  49. assert Notification.objects.count() == 1
  50. args, kwargs = mock_post.call_args
  51. payload = kwargs["json"]
  52. attachment = payload["attachments"][0]
  53. fields = {f["title"]: f["value"] for f in attachment["fields"]}
  54. self.assertEqual(fields["Last Ping"], "an hour ago")
  55. @patch("hc.api.transports.requests.request")
  56. def test_discord_rewrites_discordapp_com(self, mock_post):
  57. v = json.dumps({"webhook": {"url": "https://discordapp.com/foo"}})
  58. self._setup_data("discord", v)
  59. mock_post.return_value.status_code = 200
  60. self.channel.notify(self.check)
  61. assert Notification.objects.count() == 1
  62. args, kwargs = mock_post.call_args
  63. url = args[1]
  64. # discordapp.com is deprecated. For existing webhook URLs, wwe should
  65. # rewrite discordapp.com to discord.com:
  66. self.assertEqual(url, "https://discord.com/foo/slack")
  67. @patch("hc.api.transports.requests.request")
  68. def test_pushbullet(self, mock_post):
  69. self._setup_data("pushbullet", "fake-token")
  70. mock_post.return_value.status_code = 200
  71. self.channel.notify(self.check)
  72. assert Notification.objects.count() == 1
  73. _, kwargs = mock_post.call_args
  74. self.assertEqual(kwargs["json"]["type"], "note")
  75. self.assertEqual(kwargs["headers"]["Access-Token"], "fake-token")
  76. @patch("hc.api.transports.requests.request")
  77. def test_telegram(self, mock_post):
  78. v = json.dumps({"id": 123})
  79. self._setup_data("telegram", v)
  80. mock_post.return_value.status_code = 200
  81. self.channel.notify(self.check)
  82. assert Notification.objects.count() == 1
  83. args, kwargs = mock_post.call_args
  84. payload = kwargs["json"]
  85. self.assertEqual(payload["chat_id"], 123)
  86. self.assertTrue("The check" in payload["text"])
  87. @patch("hc.api.transports.requests.request")
  88. def test_telegram_returns_error(self, mock_post):
  89. self._setup_data("telegram", json.dumps({"id": 123}))
  90. mock_post.return_value.status_code = 400
  91. mock_post.return_value.json.return_value = {"description": "Hi"}
  92. self.channel.notify(self.check)
  93. n = Notification.objects.first()
  94. self.assertEqual(n.error, 'Received status code 400 with a message: "Hi"')
  95. def test_telegram_obeys_rate_limit(self):
  96. self._setup_data("telegram", json.dumps({"id": 123}))
  97. TokenBucket.objects.create(value="tg-123", tokens=0)
  98. self.channel.notify(self.check)
  99. n = Notification.objects.first()
  100. self.assertEqual(n.error, "Rate limit exceeded")
  101. @patch("hc.api.transports.requests.request")
  102. def test_call(self, mock_post):
  103. self.profile.call_limit = 1
  104. self.profile.save()
  105. value = {"label": "foo", "value": "+1234567890"}
  106. self._setup_data("call", json.dumps(value))
  107. self.check.last_ping = now() - td(hours=2)
  108. mock_post.return_value.status_code = 200
  109. self.channel.notify(self.check)
  110. args, kwargs = mock_post.call_args
  111. payload = kwargs["data"]
  112. self.assertEqual(payload["To"], "+1234567890")
  113. n = Notification.objects.get()
  114. callback_path = f"/api/v1/notifications/{n.code}/status"
  115. self.assertTrue(payload["StatusCallback"].endswith(callback_path))
  116. @patch("hc.api.transports.requests.request")
  117. def test_call_limit(self, mock_post):
  118. # At limit already:
  119. self.profile.last_call_date = now()
  120. self.profile.calls_sent = 50
  121. self.profile.save()
  122. definition = {"value": "+1234567890"}
  123. self._setup_data("call", json.dumps(definition))
  124. self.channel.notify(self.check)
  125. self.assertFalse(mock_post.called)
  126. n = Notification.objects.get()
  127. self.assertTrue("Monthly phone call limit exceeded" in n.error)
  128. # And email should have been sent
  129. self.assertEqual(len(mail.outbox), 1)
  130. email = mail.outbox[0]
  131. self.assertEqual(email.to[0], "[email protected]")
  132. self.assertEqual(email.subject, "Monthly Phone Call Limit Reached")
  133. @patch("hc.api.transports.requests.request")
  134. def test_call_limit_reset(self, mock_post):
  135. # At limit, but also into a new month
  136. self.profile.calls_sent = 50
  137. self.profile.last_call_date = now() - td(days=100)
  138. self.profile.save()
  139. self._setup_data("sms", "+1234567890")
  140. mock_post.return_value.status_code = 200
  141. self.channel.notify(self.check)
  142. self.assertTrue(mock_post.called)
  143. def test_not_implimented(self):
  144. self._setup_data("webhook", "http://example")
  145. self.channel.kind = "invalid"
  146. with self.assertRaises(NotImplementedError):
  147. self.channel.notify(self.check)
  148. @patch("hc.api.transports.os.system")
  149. @override_settings(SHELL_ENABLED=True)
  150. def test_shell(self, mock_system):
  151. definition = {"cmd_down": "logger hello", "cmd_up": ""}
  152. self._setup_data("shell", json.dumps(definition))
  153. mock_system.return_value = 0
  154. self.channel.notify(self.check)
  155. mock_system.assert_called_with("logger hello")
  156. @patch("hc.api.transports.os.system")
  157. @override_settings(SHELL_ENABLED=True)
  158. def test_shell_handles_nonzero_exit_code(self, mock_system):
  159. definition = {"cmd_down": "logger hello", "cmd_up": ""}
  160. self._setup_data("shell", json.dumps(definition))
  161. mock_system.return_value = 123
  162. self.channel.notify(self.check)
  163. n = Notification.objects.get()
  164. self.assertEqual(n.error, "Command returned exit code 123")
  165. @patch("hc.api.transports.os.system")
  166. @override_settings(SHELL_ENABLED=True)
  167. def test_shell_supports_variables(self, mock_system):
  168. definition = {"cmd_down": "logger $NAME is $STATUS ($TAG1)", "cmd_up": ""}
  169. self._setup_data("shell", json.dumps(definition))
  170. mock_system.return_value = 0
  171. self.check.name = "Database"
  172. self.check.tags = "foo bar"
  173. self.check.save()
  174. self.channel.notify(self.check)
  175. mock_system.assert_called_with("logger Database is down (foo)")
  176. @patch("hc.api.transports.os.system")
  177. @override_settings(SHELL_ENABLED=False)
  178. def test_shell_disabled(self, mock_system):
  179. definition = {"cmd_down": "logger hello", "cmd_up": ""}
  180. self._setup_data("shell", json.dumps(definition))
  181. self.channel.notify(self.check)
  182. self.assertFalse(mock_system.called)
  183. n = Notification.objects.get()
  184. self.assertEqual(n.error, "Shell commands are not enabled")