test_webhooks.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. from datetime import datetime, timedelta
  2. from unittest.mock import patch
  3. from uuid import uuid4
  4. from django.utils import timezone
  5. from fixtures.github import (
  6. PULL_REQUEST_CLOSED_EVENT_EXAMPLE,
  7. PULL_REQUEST_EDITED_EVENT_EXAMPLE,
  8. PULL_REQUEST_OPENED_EVENT_EXAMPLE,
  9. PUSH_EVENT_EXAMPLE_INSTALLATION,
  10. )
  11. from sentry import options
  12. from sentry.models import Commit, CommitAuthor, GroupLink, Integration, PullRequest, Repository
  13. from sentry.testutils import APITestCase
  14. from sentry.testutils.silo import exempt_from_silo_limits, region_silo_test
  15. @region_silo_test(stable=True)
  16. class WebhookTest(APITestCase):
  17. def test_get(self):
  18. url = "/extensions/github/webhook/"
  19. response = self.client.get(url)
  20. assert response.status_code == 405
  21. def test_unregistered_event(self):
  22. project = self.project # noqa force creation
  23. url = "/extensions/github/webhook/"
  24. secret = "b3002c3e321d4b7880360d397db2ccfd"
  25. options.set("github-app.webhook-secret", secret)
  26. response = self.client.post(
  27. path=url,
  28. data=PUSH_EVENT_EXAMPLE_INSTALLATION,
  29. content_type="application/json",
  30. HTTP_X_GITHUB_EVENT="UnregisteredEvent",
  31. HTTP_X_HUB_SIGNATURE="sha1=56a3df597e02adbc17fb617502c70e19d96a6136",
  32. HTTP_X_GITHUB_DELIVERY=str(uuid4()),
  33. )
  34. assert response.status_code == 204
  35. def test_invalid_signature_event(self):
  36. url = "/extensions/github/webhook/"
  37. secret = "2d7565c3537847b789d6995dca8d9f84"
  38. options.set("github-app.webhook-secret", secret)
  39. response = self.client.post(
  40. path=url,
  41. data=PUSH_EVENT_EXAMPLE_INSTALLATION,
  42. content_type="application/json",
  43. HTTP_X_GITHUB_EVENT="push",
  44. HTTP_X_HUB_SIGNATURE="sha1=33521abeaaf9a57c2abf486e0ccd54d23cf36fec",
  45. HTTP_X_GITHUB_DELIVERY=str(uuid4()),
  46. )
  47. assert response.status_code == 401
  48. @region_silo_test(stable=True)
  49. class PushEventWebhookTest(APITestCase):
  50. @patch("sentry.integrations.github.client.get_jwt")
  51. def test_simple(self, mock_get_jwt):
  52. mock_get_jwt.return_value = ""
  53. project = self.project # force creation
  54. url = "/extensions/github/webhook/"
  55. secret = "b3002c3e321d4b7880360d397db2ccfd"
  56. options.set("github-app.webhook-secret", secret)
  57. Repository.objects.create(
  58. organization_id=project.organization.id,
  59. external_id="35129377",
  60. provider="integrations:github",
  61. name="baxterthehacker/public-repo",
  62. )
  63. future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5)
  64. with exempt_from_silo_limits():
  65. integration = Integration.objects.create(
  66. external_id="12345",
  67. provider="github",
  68. metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
  69. )
  70. integration.add_organization(project.organization, self.user)
  71. response = self.client.post(
  72. path=url,
  73. data=PUSH_EVENT_EXAMPLE_INSTALLATION,
  74. content_type="application/json",
  75. HTTP_X_GITHUB_EVENT="push",
  76. HTTP_X_HUB_SIGNATURE="sha1=56a3df597e02adbc17fb617502c70e19d96a6136",
  77. HTTP_X_GITHUB_DELIVERY=str(uuid4()),
  78. )
  79. assert response.status_code == 204
  80. commit_list = list(
  81. Commit.objects.filter(
  82. # organization_id=project.organization_id,
  83. )
  84. .select_related("author")
  85. .order_by("-date_added")
  86. )
  87. assert len(commit_list) == 2
  88. commit = commit_list[0]
  89. assert commit.key == "133d60480286590a610a0eb7352ff6e02b9674c4"
  90. assert commit.message == "Update README.md (àgain)"
  91. assert commit.author.name == "bàxterthehacker"
  92. assert commit.author.email == "baxterthehacker@users.noreply.github.com"
  93. assert commit.author.external_id is None
  94. assert commit.date_added == datetime(2015, 5, 5, 23, 45, 15, tzinfo=timezone.utc)
  95. commit = commit_list[1]
  96. assert commit.key == "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c"
  97. assert commit.message == "Update README.md"
  98. assert commit.author.name == "bàxterthehacker"
  99. assert commit.author.email == "baxterthehacker@users.noreply.github.com"
  100. assert commit.author.external_id is None
  101. assert commit.date_added == datetime(2015, 5, 5, 23, 40, 15, tzinfo=timezone.utc)
  102. def test_anonymous_lookup(self):
  103. project = self.project # force creation
  104. url = "/extensions/github/webhook/"
  105. secret = "b3002c3e321d4b7880360d397db2ccfd"
  106. options.set("github-app.webhook-secret", secret)
  107. future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5)
  108. with exempt_from_silo_limits():
  109. integration = Integration.objects.create(
  110. provider="github",
  111. external_id="12345",
  112. name="octocat",
  113. metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
  114. )
  115. integration.add_organization(project.organization, self.user)
  116. Repository.objects.create(
  117. organization_id=project.organization.id,
  118. external_id="35129377",
  119. provider="integrations:github",
  120. name="baxterthehacker/public-repo",
  121. )
  122. CommitAuthor.objects.create(
  123. external_id="github:baxterthehacker",
  124. organization_id=project.organization_id,
  125. email="baxterthehacker@example.com",
  126. name="bàxterthehacker",
  127. )
  128. response = self.client.post(
  129. path=url,
  130. data=PUSH_EVENT_EXAMPLE_INSTALLATION,
  131. content_type="application/json",
  132. HTTP_X_GITHUB_EVENT="push",
  133. HTTP_X_HUB_SIGNATURE="sha1=56a3df597e02adbc17fb617502c70e19d96a6136",
  134. HTTP_X_GITHUB_DELIVERY=str(uuid4()),
  135. )
  136. assert response.status_code == 204
  137. commit_list = list(
  138. Commit.objects.filter(organization_id=project.organization_id)
  139. .select_related("author")
  140. .order_by("-date_added")
  141. )
  142. # should be skipping the #skipsentry commit
  143. assert len(commit_list) == 2
  144. commit = commit_list[0]
  145. assert commit.key == "133d60480286590a610a0eb7352ff6e02b9674c4"
  146. assert commit.message == "Update README.md (àgain)"
  147. assert commit.author.name == "bàxterthehacker"
  148. assert commit.author.email == "baxterthehacker@example.com"
  149. assert commit.date_added == datetime(2015, 5, 5, 23, 45, 15, tzinfo=timezone.utc)
  150. commit = commit_list[1]
  151. assert commit.key == "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c"
  152. assert commit.message == "Update README.md"
  153. assert commit.author.name == "bàxterthehacker"
  154. assert commit.author.email == "baxterthehacker@example.com"
  155. assert commit.date_added == datetime(2015, 5, 5, 23, 40, 15, tzinfo=timezone.utc)
  156. @patch("sentry.integrations.github.client.get_jwt")
  157. def test_multiple_orgs(self, mock_get_jwt):
  158. mock_get_jwt.return_value = ""
  159. project = self.project # force creation
  160. url = "/extensions/github/webhook/"
  161. secret = "b3002c3e321d4b7880360d397db2ccfd"
  162. options.set("github-app.webhook-secret", secret)
  163. Repository.objects.create(
  164. organization_id=project.organization.id,
  165. external_id="35129377",
  166. provider="integrations:github",
  167. name="baxterthehacker/public-repo",
  168. )
  169. future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5)
  170. with exempt_from_silo_limits():
  171. integration = Integration.objects.create(
  172. external_id="12345",
  173. provider="github",
  174. metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
  175. )
  176. integration.add_organization(project.organization, self.user)
  177. org2 = self.create_organization()
  178. project2 = self.create_project(organization=org2, name="bar")
  179. Repository.objects.create(
  180. organization_id=project2.organization.id,
  181. external_id="77",
  182. provider="integrations:github",
  183. name="another/repo",
  184. )
  185. future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5)
  186. with exempt_from_silo_limits():
  187. integration = Integration.objects.create(
  188. external_id="99",
  189. provider="github",
  190. metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
  191. )
  192. integration.add_organization(org2, self.user)
  193. response = self.client.post(
  194. path=url,
  195. data=PUSH_EVENT_EXAMPLE_INSTALLATION,
  196. content_type="application/json",
  197. HTTP_X_GITHUB_EVENT="push",
  198. HTTP_X_HUB_SIGNATURE="sha1=56a3df597e02adbc17fb617502c70e19d96a6136",
  199. HTTP_X_GITHUB_DELIVERY=str(uuid4()),
  200. )
  201. assert response.status_code == 204
  202. commit_list = list(
  203. Commit.objects.filter(organization_id=project.organization_id)
  204. .select_related("author")
  205. .order_by("-date_added")
  206. )
  207. assert len(commit_list) == 2
  208. commit_list = list(
  209. Commit.objects.filter(organization_id=org2.id)
  210. .select_related("author")
  211. .order_by("-date_added")
  212. )
  213. assert len(commit_list) == 0
  214. @region_silo_test(stable=True)
  215. class PullRequestEventWebhook(APITestCase):
  216. def test_opened(self):
  217. project = self.project # force creation
  218. group = self.create_group(project=project, short_id=7)
  219. url = "/extensions/github/webhook/"
  220. secret = "b3002c3e321d4b7880360d397db2ccfd"
  221. options.set("github-app.webhook-secret", secret)
  222. future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5)
  223. with exempt_from_silo_limits():
  224. integration = Integration.objects.create(
  225. provider="github",
  226. external_id="12345",
  227. name="octocat",
  228. metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
  229. )
  230. integration.add_organization(project.organization, self.user)
  231. repo = Repository.objects.create(
  232. organization_id=project.organization.id,
  233. external_id="35129377",
  234. provider="integrations:github",
  235. name="baxterthehacker/public-repo",
  236. )
  237. response = self.client.post(
  238. path=url,
  239. data=PULL_REQUEST_OPENED_EVENT_EXAMPLE,
  240. content_type="application/json",
  241. HTTP_X_GITHUB_EVENT="pull_request",
  242. HTTP_X_HUB_SIGNATURE="sha1=bc7ce12fc1058a35bf99355e6fc0e6da72c35de3",
  243. HTTP_X_GITHUB_DELIVERY=str(uuid4()),
  244. )
  245. assert response.status_code == 204
  246. prs = PullRequest.objects.filter(
  247. repository_id=repo.id, organization_id=project.organization.id
  248. )
  249. assert len(prs) == 1
  250. pr = prs[0]
  251. assert pr.key == "1"
  252. assert (
  253. pr.message
  254. == "This is a pretty simple change that we need to pull into master. Fixes BAR-7"
  255. )
  256. assert pr.title == "Update the README with new information"
  257. assert pr.author.name == "baxterthehacker"
  258. self.assert_group_link(group, pr)
  259. def test_edited(self):
  260. project = self.project # force creation
  261. group = self.create_group(project=project, short_id=7)
  262. url = "/extensions/github/webhook/"
  263. secret = "b3002c3e321d4b7880360d397db2ccfd"
  264. options.set("github-app.webhook-secret", secret)
  265. future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5)
  266. with exempt_from_silo_limits():
  267. integration = Integration.objects.create(
  268. provider="github",
  269. external_id="12345",
  270. name="octocat",
  271. metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
  272. )
  273. integration.add_organization(project.organization, self.user)
  274. repo = Repository.objects.create(
  275. organization_id=project.organization.id,
  276. external_id="35129377",
  277. provider="integrations:github",
  278. name="baxterthehacker/public-repo",
  279. )
  280. pr = PullRequest.objects.create(
  281. key="1", repository_id=repo.id, organization_id=project.organization.id
  282. )
  283. response = self.client.post(
  284. path=url,
  285. data=PULL_REQUEST_EDITED_EVENT_EXAMPLE,
  286. content_type="application/json",
  287. HTTP_X_GITHUB_EVENT="pull_request",
  288. HTTP_X_HUB_SIGNATURE="sha1=83100642f0cf5d7f6145cf8d04da5d00a09f890f",
  289. HTTP_X_GITHUB_DELIVERY=str(uuid4()),
  290. )
  291. assert response.status_code == 204
  292. pr = PullRequest.objects.get(id=pr.id)
  293. assert pr.key == "1"
  294. assert pr.message == "new edited body. Fixes BAR-7"
  295. assert pr.title == "new edited title"
  296. assert pr.author.name == "baxterthehacker"
  297. self.assert_group_link(group, pr)
  298. def test_closed(self):
  299. project = self.project # force creation
  300. url = "/extensions/github/webhook/"
  301. secret = "b3002c3e321d4b7880360d397db2ccfd"
  302. options.set("github-app.webhook-secret", secret)
  303. future_expires = datetime.now().replace(microsecond=0) + timedelta(minutes=5)
  304. with exempt_from_silo_limits():
  305. integration = Integration.objects.create(
  306. provider="github",
  307. external_id="12345",
  308. name="octocat",
  309. metadata={"access_token": "1234", "expires_at": future_expires.isoformat()},
  310. )
  311. integration.add_organization(project.organization, self.user)
  312. repo = Repository.objects.create(
  313. organization_id=project.organization.id,
  314. external_id="35129377",
  315. provider="integrations:github",
  316. name="baxterthehacker/public-repo",
  317. )
  318. response = self.client.post(
  319. path=url,
  320. data=PULL_REQUEST_CLOSED_EVENT_EXAMPLE,
  321. content_type="application/json",
  322. HTTP_X_GITHUB_EVENT="pull_request",
  323. HTTP_X_HUB_SIGNATURE="sha1=49db856f5658b365b73a2fa73a7cffa543f4d3af",
  324. HTTP_X_GITHUB_DELIVERY=str(uuid4()),
  325. )
  326. assert response.status_code == 204
  327. prs = PullRequest.objects.filter(
  328. repository_id=repo.id, organization_id=project.organization.id
  329. )
  330. assert len(prs) == 1
  331. pr = prs[0]
  332. assert pr.key == "1"
  333. assert pr.message == "new closed body"
  334. assert pr.title == "new closed title"
  335. assert pr.author.name == "baxterthehacker"
  336. assert pr.merge_commit_sha == "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c"
  337. def assert_group_link(self, group, pr):
  338. link = GroupLink.objects.all().first()
  339. assert link
  340. assert link.group_id == group.id
  341. assert link.linked_id == pr.id
  342. assert link.linked_type == GroupLink.LinkedType.pull_request