test_commit_context.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795
  1. from datetime import timedelta
  2. from unittest.mock import Mock, patch
  3. import pytest
  4. import responses
  5. from celery.exceptions import MaxRetriesExceededError
  6. from django.utils import timezone
  7. from sentry.integrations.github.integration import GitHubIntegrationProvider
  8. from sentry.models import PullRequest, PullRequestComment, Repository
  9. from sentry.models.commit import Commit
  10. from sentry.models.groupowner import GroupOwner, GroupOwnerType
  11. from sentry.models.options.organization_option import OrganizationOption
  12. from sentry.models.pullrequest import PullRequestCommit
  13. from sentry.shared_integrations.exceptions.base import ApiError
  14. from sentry.snuba.sessions_v2 import isoformat_z
  15. from sentry.tasks.commit_context import process_commit_context
  16. from sentry.testutils import TestCase
  17. from sentry.testutils.cases import IntegrationTestCase
  18. from sentry.testutils.helpers import with_feature
  19. from sentry.testutils.helpers.datetime import before_now, iso_format
  20. from sentry.testutils.silo import region_silo_test
  21. from sentry.utils.committers import get_frame_paths
  22. class TestCommitContextMixin(TestCase):
  23. def setUp(self):
  24. self.project = self.create_project()
  25. self.repo = Repository.objects.create(
  26. organization_id=self.organization.id,
  27. name="example",
  28. integration_id=self.integration.id,
  29. )
  30. self.code_mapping = self.create_code_mapping(
  31. repo=self.repo,
  32. project=self.project,
  33. )
  34. self.commit_author = self.create_commit_author(project=self.project, user=self.user)
  35. self.commit = self.create_commit(
  36. project=self.project,
  37. repo=self.repo,
  38. author=self.commit_author,
  39. key="asdfwreqr",
  40. message="placeholder commit message",
  41. )
  42. self.group = self.create_group(
  43. project=self.project, message="Kaboom!", first_release=self.release
  44. )
  45. self.event = self.store_event(
  46. data={
  47. "message": "Kaboom!",
  48. "platform": "python",
  49. "timestamp": iso_format(before_now(seconds=10)),
  50. "stacktrace": {
  51. "frames": [
  52. {
  53. "function": "handle_set_commits",
  54. "abs_path": "/usr/src/sentry/src/sentry/tasks.py",
  55. "module": "sentry.tasks",
  56. "in_app": False,
  57. "lineno": 30,
  58. "filename": "sentry/tasks.py",
  59. },
  60. {
  61. "function": "set_commits",
  62. "abs_path": "/usr/src/sentry/src/sentry/models/release.py",
  63. "module": "sentry.models.release",
  64. "in_app": True,
  65. "lineno": 39,
  66. "filename": "sentry/models/release.py",
  67. },
  68. ]
  69. },
  70. "tags": {"sentry:release": self.release.version},
  71. "fingerprint": ["put-me-in-the-control-group"],
  72. },
  73. project_id=self.project.id,
  74. )
  75. @region_silo_test(stable=True)
  76. class TestCommitContext(TestCommitContextMixin):
  77. @patch(
  78. "sentry.integrations.github.GitHubIntegration.get_commit_context",
  79. return_value={
  80. "commitId": "asdfwreqr",
  81. "committedDate": "2023-02-14T11:11Z",
  82. "commitMessage": "placeholder commit message",
  83. "commitAuthorName": "",
  84. "commitAuthorEmail": "admin@localhost",
  85. },
  86. )
  87. def test_simple(self, mock_get_commit_context):
  88. with self.tasks():
  89. assert not GroupOwner.objects.filter(group=self.event.group).exists()
  90. event_frames = get_frame_paths(self.event)
  91. process_commit_context(
  92. event_id=self.event.event_id,
  93. event_platform=self.event.platform,
  94. event_frames=event_frames,
  95. group_id=self.event.group_id,
  96. project_id=self.event.project_id,
  97. )
  98. assert GroupOwner.objects.get(
  99. group=self.event.group,
  100. project=self.event.project,
  101. organization=self.event.project.organization,
  102. type=GroupOwnerType.SUSPECT_COMMIT.value,
  103. )
  104. assert GroupOwner.objects.get(
  105. group=self.event.group,
  106. project=self.event.project,
  107. organization=self.event.project.organization,
  108. type=GroupOwnerType.SUSPECT_COMMIT.value,
  109. ).context == {"commitId": self.commit.id}
  110. @patch("sentry.analytics.record")
  111. @patch(
  112. "sentry.integrations.github.GitHubIntegration.get_commit_context",
  113. side_effect=ApiError(text="integration_failed"),
  114. )
  115. def test_failed_to_fetch_commit_context_record(self, mock_get_commit_context, mock_record):
  116. with self.tasks():
  117. assert not GroupOwner.objects.filter(group=self.event.group).exists()
  118. event_frames = get_frame_paths(self.event)
  119. process_commit_context(
  120. event_id=self.event.event_id,
  121. event_platform=self.event.platform,
  122. event_frames=event_frames,
  123. group_id=self.event.group_id,
  124. project_id=self.event.project_id,
  125. )
  126. mock_record.assert_called_with(
  127. "integrations.failed_to_fetch_commit_context",
  128. organization_id=self.organization.id,
  129. project_id=self.project.id,
  130. code_mapping_id=self.code_mapping.id,
  131. group_id=self.event.group_id,
  132. provider="github",
  133. error_message="integration_failed",
  134. )
  135. @patch(
  136. "sentry.integrations.github.GitHubIntegration.get_commit_context",
  137. return_value={
  138. "commitId": "asdfasdf",
  139. "committedDate": "2023-02-14T11:11Z",
  140. "commitMessage": "placeholder commit message",
  141. "commitAuthorName": "",
  142. "commitAuthorEmail": "admin@localhost",
  143. },
  144. )
  145. def test_no_matching_commit_in_db(self, mock_get_commit_context):
  146. with self.tasks():
  147. assert not GroupOwner.objects.filter(group=self.event.group).exists()
  148. assert not Commit.objects.filter(key="asdfasdf").exists()
  149. event_frames = get_frame_paths(self.event)
  150. process_commit_context(
  151. event_id=self.event.event_id,
  152. event_platform=self.event.platform,
  153. event_frames=event_frames,
  154. group_id=self.event.group_id,
  155. project_id=self.event.project_id,
  156. )
  157. assert Commit.objects.filter(key="asdfasdf").exists()
  158. assert GroupOwner.objects.filter(group=self.event.group).exists()
  159. @patch(
  160. "sentry.integrations.github.GitHubIntegration.get_commit_context",
  161. return_value={
  162. "commitId": "asdfwreqr",
  163. "committedDate": "2023-02-14T11:11Z",
  164. "commitMessage": "placeholder commit message",
  165. "commitAuthorName": "",
  166. "commitAuthorEmail": "admin@localhost",
  167. },
  168. )
  169. def test_delete_old_entries(self, mock_get_commit_context):
  170. # As new events come in associated with new owners, we should delete old ones.
  171. user_2 = self.create_user("another@user.com", is_superuser=True)
  172. self.create_member(teams=[self.team], user=user_2, organization=self.organization)
  173. owner = GroupOwner.objects.create(
  174. group=self.event.group,
  175. user_id=user_2.id,
  176. project=self.project,
  177. organization=self.organization,
  178. type=GroupOwnerType.SUSPECT_COMMIT.value,
  179. date_added=timezone.now() - timedelta(days=8),
  180. )
  181. with self.tasks():
  182. event_frames = get_frame_paths(self.event)
  183. process_commit_context(
  184. event_id=self.event.event_id,
  185. event_platform=self.event.platform,
  186. event_frames=event_frames,
  187. group_id=self.event.group_id,
  188. project_id=self.event.project_id,
  189. )
  190. assert not GroupOwner.objects.filter(id=owner.id).exists()
  191. assert GroupOwner.objects.filter(group=self.event.group).count() == 1
  192. assert GroupOwner.objects.filter(group=self.event.group, user_id=self.user.id).exists()
  193. @patch("sentry.tasks.groupowner.process_suspect_commits.delay")
  194. def test_no_inapp_frame_in_stacktrace(self, mock_process_suspect_commits):
  195. with self.tasks():
  196. assert not GroupOwner.objects.filter(group=self.event.group).exists()
  197. self.event_2 = self.store_event(
  198. data={
  199. "message": "Kaboom!",
  200. "platform": "python",
  201. "timestamp": iso_format(before_now(seconds=10)),
  202. "stacktrace": {
  203. "frames": [
  204. {
  205. "function": "handle_set_commits",
  206. "abs_path": "/usr/src/sentry/src/sentry/tasks.py",
  207. "module": "sentry.tasks",
  208. "in_app": False,
  209. "lineno": 30,
  210. "filename": "sentry/tasks.py",
  211. },
  212. {
  213. "function": "set_commits",
  214. "abs_path": "/usr/src/sentry/src/sentry/models/release.py",
  215. "module": "sentry.models.release",
  216. "in_app": False,
  217. "lineno": 39,
  218. "filename": "sentry/models/release.py",
  219. },
  220. ]
  221. },
  222. "tags": {"sentry:release": self.release.version},
  223. "fingerprint": ["put-me-in-the-control-group"],
  224. },
  225. project_id=self.project.id,
  226. )
  227. event_frames = get_frame_paths(self.event_2)
  228. process_commit_context(
  229. event_id=self.event.event_id,
  230. event_platform=self.event.platform,
  231. event_frames=event_frames,
  232. group_id=self.event.group_id,
  233. project_id=self.event.project_id,
  234. )
  235. assert mock_process_suspect_commits.call_count == 1
  236. assert not GroupOwner.objects.filter(
  237. group=self.event.group,
  238. project=self.event.project,
  239. organization=self.event.project.organization,
  240. type=GroupOwnerType.SUSPECT_COMMIT.value,
  241. ).exists()
  242. @patch(
  243. "sentry.integrations.github.GitHubIntegration.get_commit_context",
  244. return_value={
  245. "commitId": "somekey",
  246. "committedDate": "2023-02-14T11:11Z",
  247. "commitMessage": "placeholder commit message",
  248. "commitAuthorName": "",
  249. "commitAuthorEmail": "randomuser@sentry.io",
  250. },
  251. )
  252. def test_commit_author_not_in_sentry(self, mock_get_commit_context):
  253. self.commit_author_2 = self.create_commit_author(
  254. project=self.project,
  255. )
  256. self.commit_2 = self.create_commit(
  257. project=self.project,
  258. repo=self.repo,
  259. author=self.commit_author_2,
  260. key="somekey",
  261. message="placeholder commit message",
  262. )
  263. with self.tasks():
  264. assert not GroupOwner.objects.filter(group=self.event.group).exists()
  265. event_frames = get_frame_paths(self.event)
  266. process_commit_context(
  267. event_id=self.event.event_id,
  268. event_platform=self.event.platform,
  269. event_frames=event_frames,
  270. group_id=self.event.group_id,
  271. project_id=self.event.project_id,
  272. )
  273. assert GroupOwner.objects.filter(group=self.event.group).exists()
  274. assert len(GroupOwner.objects.filter(group=self.event.group)) == 1
  275. owner = GroupOwner.objects.get(group=self.event.group)
  276. assert owner.type == GroupOwnerType.SUSPECT_COMMIT.value
  277. assert owner.user_id is None
  278. assert owner.team is None
  279. assert owner.context == {"commitId": self.commit_2.id}
  280. @patch("sentry.tasks.commit_context.get_users_for_authors", return_value={})
  281. @patch(
  282. "sentry.integrations.github.GitHubIntegration.get_commit_context",
  283. return_value={
  284. "commitId": "somekey",
  285. "committedDate": "2023-02-14T11:11Z",
  286. "commitMessage": "placeholder commit message",
  287. "commitAuthorName": "",
  288. "commitAuthorEmail": "randomuser@sentry.io",
  289. },
  290. )
  291. def test_commit_author_no_user(self, mock_get_commit_context, mock_get_users_for_author):
  292. self.commit_author_2 = self.create_commit_author(
  293. project=self.project,
  294. )
  295. self.commit_2 = self.create_commit(
  296. project=self.project,
  297. repo=self.repo,
  298. author=self.commit_author_2,
  299. key="somekey",
  300. message="placeholder commit message",
  301. )
  302. with self.tasks(), patch(
  303. "sentry.tasks.commit_context.get_users_for_authors", return_value={}
  304. ):
  305. event_frames = get_frame_paths(self.event)
  306. process_commit_context(
  307. event_id=self.event.event_id,
  308. event_platform=self.event.platform,
  309. event_frames=event_frames,
  310. group_id=self.event.group_id,
  311. project_id=self.event.project_id,
  312. )
  313. assert GroupOwner.objects.filter(group=self.event.group).exists()
  314. assert len(GroupOwner.objects.filter(group=self.event.group)) == 1
  315. owner = GroupOwner.objects.get(group=self.event.group)
  316. assert owner.type == GroupOwnerType.SUSPECT_COMMIT.value
  317. assert owner.user_id is None
  318. assert owner.team is None
  319. assert owner.context == {"commitId": self.commit_2.id}
  320. @patch(
  321. "sentry.integrations.github.GitHubIntegration.get_commit_context",
  322. return_value={
  323. "commitId": "somekey",
  324. "committedDate": "2023-02-14T11:11Z",
  325. "commitMessage": "placeholder commit message",
  326. "commitAuthorName": "",
  327. "commitAuthorEmail": "randomuser@sentry.io",
  328. },
  329. )
  330. def test_multiple_matching_code_mappings_but_only_1_repository_has_the_commit_in_db(
  331. self, mock_get_commit_context
  332. ):
  333. self.integration_2 = self.create_integration(
  334. organization=self.organization,
  335. provider="github",
  336. name="GitHub",
  337. external_id="github:2",
  338. )
  339. self.repo_2 = Repository.objects.create(
  340. organization_id=self.organization.id,
  341. name="another/example",
  342. integration_id=self.integration_2.id,
  343. )
  344. self.code_mapping_2 = self.create_code_mapping(
  345. repo=self.repo_2, project=self.project, stack_root="src", source_root="src"
  346. )
  347. self.commit_author_2 = self.create_commit_author(
  348. project=self.project,
  349. )
  350. self.commit_2 = self.create_commit(
  351. project=self.project,
  352. repo=self.repo_2,
  353. author=self.commit_author_2,
  354. key="somekey",
  355. message="placeholder commit message",
  356. )
  357. with self.tasks():
  358. assert not GroupOwner.objects.filter(group=self.event.group).exists()
  359. event_frames = get_frame_paths(self.event)
  360. process_commit_context(
  361. event_id=self.event.event_id,
  362. event_platform=self.event.platform,
  363. event_frames=event_frames,
  364. group_id=self.event.group_id,
  365. project_id=self.event.project_id,
  366. )
  367. assert GroupOwner.objects.filter(group=self.event.group).exists()
  368. assert len(GroupOwner.objects.filter(group=self.event.group)) == 1
  369. owner = GroupOwner.objects.get(group=self.event.group)
  370. assert owner.type == GroupOwnerType.SUSPECT_COMMIT.value
  371. assert owner.user_id is None
  372. assert owner.team is None
  373. assert owner.context == {"commitId": self.commit_2.id}
  374. @patch(
  375. "sentry.integrations.github.GitHubIntegration.get_commit_context",
  376. side_effect=ApiError(text="integration_failed"),
  377. )
  378. @patch("sentry.tasks.groupowner.process_suspect_commits.delay")
  379. def test_fallback_if_max_retries_exceeded(self, mock_suspect_commits, mock_get_commit_context):
  380. def after_return(self, status, retval, task_id, args, kwargs, einfo):
  381. raise MaxRetriesExceededError()
  382. with self.tasks() and pytest.raises(MaxRetriesExceededError):
  383. with patch("celery.app.task.Task.after_return", after_return):
  384. process_commit_context.apply(
  385. kwargs={
  386. "event_id": self.event.event_id,
  387. "event_platform": self.event.platform,
  388. "event_frames": get_frame_paths(self.event),
  389. "group_id": self.event.group_id,
  390. "project_id": self.event.project_id,
  391. },
  392. retries=1,
  393. )
  394. assert mock_suspect_commits.called
  395. @region_silo_test(stable=True)
  396. @patch(
  397. "sentry.integrations.github.GitHubIntegration.get_commit_context",
  398. Mock(
  399. return_value={
  400. "commitId": "asdfwreqr",
  401. "committedDate": "2023-02-14T11:11Z",
  402. "commitMessage": "placeholder commit message",
  403. "commitAuthorName": "",
  404. "commitAuthorEmail": "admin@localhost",
  405. }
  406. ),
  407. )
  408. @patch("sentry.tasks.integrations.github.pr_comment.github_comment_workflow.delay")
  409. class TestGHCommentQueuing(IntegrationTestCase, TestCommitContextMixin):
  410. provider = GitHubIntegrationProvider
  411. base_url = "https://api.github.com"
  412. def setUp(self):
  413. super().setUp()
  414. self.pull_request = PullRequest.objects.create(
  415. organization_id=self.commit.organization_id,
  416. repository_id=self.repo.id,
  417. key="99",
  418. author=self.commit.author,
  419. message="foo",
  420. title="bar",
  421. merge_commit_sha=self.commit.key,
  422. date_added=iso_format(before_now(days=1)),
  423. )
  424. self.repo.provider = "integrations:github"
  425. self.repo.save()
  426. self.pull_request_comment = PullRequestComment.objects.create(
  427. pull_request=self.pull_request,
  428. external_id=1,
  429. created_at=iso_format(before_now(days=1)),
  430. updated_at=iso_format(before_now(days=1)),
  431. group_ids=[],
  432. )
  433. self.installation_id = "github:1"
  434. self.user_id = "user_1"
  435. self.app_id = "app_1"
  436. self.access_token = "xxxxx-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx"
  437. self.expires_at = isoformat_z(timezone.now() + timedelta(days=365))
  438. def add_responses(self):
  439. responses.add(
  440. responses.POST,
  441. self.base_url + f"/app/installations/{self.installation_id}/access_tokens",
  442. json={"token": self.access_token, "expires_at": self.expires_at},
  443. )
  444. responses.add(
  445. responses.GET,
  446. self.base_url + f"/repos/example/commits/{self.commit.key}/pulls",
  447. status=200,
  448. json=[{"merge_commit_sha": self.pull_request.merge_commit_sha}],
  449. )
  450. @with_feature("organizations:pr-comment-bot")
  451. def test_gh_comment_not_github(self, mock_comment_workflow):
  452. """Non github repos shouldn't be commented on"""
  453. self.repo.provider = "integrations:gitlab"
  454. self.repo.save()
  455. with self.tasks():
  456. event_frames = get_frame_paths(self.event)
  457. process_commit_context(
  458. event_id=self.event.event_id,
  459. event_platform=self.event.platform,
  460. event_frames=event_frames,
  461. group_id=self.event.group_id,
  462. project_id=self.event.project_id,
  463. )
  464. assert not mock_comment_workflow.called
  465. def test_gh_comment_feature_flag(self, mock_comment_workflow):
  466. """No comments on org with feature flag disabled"""
  467. with self.tasks():
  468. event_frames = get_frame_paths(self.event)
  469. process_commit_context(
  470. event_id=self.event.event_id,
  471. event_platform=self.event.platform,
  472. event_frames=event_frames,
  473. group_id=self.event.group_id,
  474. project_id=self.event.project_id,
  475. )
  476. assert not mock_comment_workflow.called
  477. @with_feature("organizations:pr-comment-bot")
  478. def test_gh_comment_org_option(self, mock_comment_workflow):
  479. """No comments on org with organization option disabled"""
  480. OrganizationOption.objects.set_value(
  481. organization=self.project.organization, key="sentry:github_pr_bot", value=False
  482. )
  483. with self.tasks():
  484. event_frames = get_frame_paths(self.event)
  485. process_commit_context(
  486. event_id=self.event.event_id,
  487. event_platform=self.event.platform,
  488. event_frames=event_frames,
  489. group_id=self.event.group_id,
  490. project_id=self.event.project_id,
  491. )
  492. assert not mock_comment_workflow.called
  493. @with_feature("organizations:pr-comment-bot")
  494. @patch("sentry.integrations.github.client.get_jwt", return_value=b"jwt_token_1")
  495. @responses.activate
  496. def test_gh_comment_no_pr_from_api(self, get_jwt, mock_comment_workflow):
  497. """No comments on suspect commit with no pr returned from API response"""
  498. self.pull_request.delete()
  499. responses.add(
  500. responses.POST,
  501. self.base_url + f"/app/installations/{self.installation_id}/access_tokens",
  502. json={"token": self.access_token, "expires_at": self.expires_at},
  503. )
  504. responses.add(
  505. responses.GET,
  506. self.base_url + f"/repos/example/commits/{self.commit.key}/pulls",
  507. status=200,
  508. json={"message": "No commit found for SHA"},
  509. )
  510. with self.tasks():
  511. event_frames = get_frame_paths(self.event)
  512. process_commit_context(
  513. event_id=self.event.event_id,
  514. event_platform=self.event.platform,
  515. event_frames=event_frames,
  516. group_id=self.event.group_id,
  517. project_id=self.event.project_id,
  518. )
  519. assert not mock_comment_workflow.called
  520. @with_feature("organizations:pr-comment-bot")
  521. @patch("sentry.integrations.github.client.get_jwt", return_value=b"jwt_token_1")
  522. @patch("sentry_sdk.capture_exception")
  523. @responses.activate
  524. def test_gh_comment_api_error(self, mock_capture_exception, get_jwt, mock_comment_workflow):
  525. """Captures exception if Github API call errors"""
  526. responses.add(
  527. responses.POST,
  528. self.base_url + f"/app/installations/{self.installation_id}/access_tokens",
  529. json={"token": self.access_token, "expires_at": self.expires_at},
  530. )
  531. responses.add(
  532. responses.GET,
  533. self.base_url + f"/repos/example/commits/{self.commit.key}/pulls",
  534. status=400,
  535. json={"message": "error"},
  536. )
  537. with self.tasks():
  538. event_frames = get_frame_paths(self.event)
  539. process_commit_context(
  540. event_id=self.event.event_id,
  541. event_platform=self.event.platform,
  542. event_frames=event_frames,
  543. group_id=self.event.group_id,
  544. project_id=self.event.project_id,
  545. )
  546. assert mock_capture_exception.called
  547. assert not mock_comment_workflow.called
  548. @with_feature("organizations:pr-comment-bot")
  549. @patch("sentry.integrations.github.client.get_jwt", return_value=b"jwt_token_1")
  550. @responses.activate
  551. def test_gh_comment_commit_not_in_default_branch(self, get_jwt, mock_comment_workflow):
  552. """No comments on commit not in default branch"""
  553. responses.add(
  554. responses.POST,
  555. self.base_url + f"/app/installations/{self.installation_id}/access_tokens",
  556. json={"token": self.access_token, "expires_at": self.expires_at},
  557. )
  558. responses.add(
  559. responses.GET,
  560. self.base_url + f"/repos/example/commits/{self.commit.key}/pulls",
  561. status=200,
  562. json=[{"merge_commit_sha": "abcd"}, {"merge_commit_sha": "efgh"}],
  563. )
  564. with self.tasks():
  565. event_frames = get_frame_paths(self.event)
  566. process_commit_context(
  567. event_id=self.event.event_id,
  568. event_platform=self.event.platform,
  569. event_frames=event_frames,
  570. group_id=self.event.group_id,
  571. project_id=self.event.project_id,
  572. )
  573. assert not mock_comment_workflow.called
  574. @with_feature("organizations:pr-comment-bot")
  575. @patch("sentry.integrations.github.client.get_jwt", return_value=b"jwt_token_1")
  576. @responses.activate
  577. def test_gh_comment_no_pr_from_query(self, get_jwt, mock_comment_workflow):
  578. """No comments on suspect commit with no pr row in table"""
  579. self.pull_request.delete()
  580. self.add_responses()
  581. with self.tasks():
  582. event_frames = get_frame_paths(self.event)
  583. process_commit_context(
  584. event_id=self.event.event_id,
  585. event_platform=self.event.platform,
  586. event_frames=event_frames,
  587. group_id=self.event.group_id,
  588. project_id=self.event.project_id,
  589. )
  590. assert not mock_comment_workflow.called
  591. @with_feature("organizations:pr-comment-bot")
  592. @patch("sentry.integrations.github.client.get_jwt", return_value=b"jwt_token_1")
  593. @responses.activate
  594. def test_gh_comment_pr_too_old(self, get_jwt, mock_comment_workflow):
  595. """No comment on pr that's older than 30 days"""
  596. self.pull_request.date_added = iso_format(before_now(days=8))
  597. self.pull_request.save()
  598. self.add_responses()
  599. with self.tasks():
  600. event_frames = get_frame_paths(self.event)
  601. process_commit_context(
  602. event_id=self.event.event_id,
  603. event_platform=self.event.platform,
  604. event_frames=event_frames,
  605. group_id=self.event.group_id,
  606. project_id=self.event.project_id,
  607. )
  608. assert not mock_comment_workflow.called
  609. assert len(PullRequestCommit.objects.all()) == 0
  610. @with_feature("organizations:pr-comment-bot")
  611. @patch("sentry.integrations.github.client.get_jwt", return_value=b"jwt_token_1")
  612. @responses.activate
  613. def test_gh_comment_repeat_issue(self, get_jwt, mock_comment_workflow):
  614. """No comment on a pr that has a comment with the issue in the same pr list"""
  615. self.pull_request_comment.group_ids.append(self.event.group_id)
  616. self.pull_request_comment.save()
  617. self.add_responses()
  618. with self.tasks():
  619. event_frames = get_frame_paths(self.event)
  620. process_commit_context(
  621. event_id=self.event.event_id,
  622. event_platform=self.event.platform,
  623. event_frames=event_frames,
  624. group_id=self.event.group_id,
  625. project_id=self.event.project_id,
  626. )
  627. assert not mock_comment_workflow.called
  628. assert len(PullRequestCommit.objects.all()) == 0
  629. @with_feature("organizations:pr-comment-bot")
  630. @patch("sentry.integrations.github.client.get_jwt", return_value=b"jwt_token_1")
  631. @responses.activate
  632. def test_gh_comment_create_queued(self, get_jwt, mock_comment_workflow):
  633. """Task queued if no prior comment exists"""
  634. self.pull_request_comment.delete()
  635. self.add_responses()
  636. with self.tasks():
  637. event_frames = get_frame_paths(self.event)
  638. process_commit_context(
  639. event_id=self.event.event_id,
  640. event_platform=self.event.platform,
  641. event_frames=event_frames,
  642. group_id=self.event.group_id,
  643. project_id=self.event.project_id,
  644. )
  645. assert mock_comment_workflow.called
  646. pr_commits = PullRequestCommit.objects.all()
  647. assert len(pr_commits) == 1
  648. assert pr_commits[0].commit == self.commit
  649. @with_feature("organizations:pr-comment-bot")
  650. @patch("sentry.integrations.github.client.get_jwt", return_value=b"jwt_token_1")
  651. @responses.activate
  652. def test_gh_comment_create_queued_existing_pr_commit(self, get_jwt, mock_comment_workflow):
  653. """Task queued if no prior comment exists"""
  654. pr_commit = PullRequestCommit.objects.create(
  655. commit=self.commit, pull_request=self.pull_request
  656. )
  657. self.pull_request_comment.delete()
  658. self.add_responses()
  659. with self.tasks():
  660. event_frames = get_frame_paths(self.event)
  661. process_commit_context(
  662. event_id=self.event.event_id,
  663. event_platform=self.event.platform,
  664. event_frames=event_frames,
  665. group_id=self.event.group_id,
  666. project_id=self.event.project_id,
  667. )
  668. assert mock_comment_workflow.called
  669. pr_commits = PullRequestCommit.objects.all()
  670. assert len(pr_commits) == 1
  671. assert pr_commits[0] == pr_commit
  672. @with_feature("organizations:pr-comment-bot")
  673. @patch("sentry.integrations.github.client.get_jwt", return_value=b"jwt_token_1")
  674. @responses.activate
  675. def test_gh_comment_update_queue(self, get_jwt, mock_comment_workflow):
  676. """Task queued if new issue for prior comment"""
  677. self.add_responses()
  678. with self.tasks():
  679. assert not GroupOwner.objects.filter(group=self.event.group).exists()
  680. event_frames = get_frame_paths(self.event)
  681. process_commit_context(
  682. event_id=self.event.event_id,
  683. event_platform=self.event.platform,
  684. event_frames=event_frames,
  685. group_id=self.event.group_id,
  686. project_id=self.event.project_id,
  687. )
  688. assert mock_comment_workflow.called
  689. pr_commits = PullRequestCommit.objects.all()
  690. assert len(pr_commits) == 1
  691. assert pr_commits[0].commit == self.commit
  692. @with_feature("organizations:pr-comment-bot")
  693. def test_gh_comment_no_repo(self, mock_comment_workflow):
  694. """No comments on suspect commit if no repo row exists"""
  695. self.repo.delete()
  696. with self.tasks():
  697. event_frames = get_frame_paths(self.event)
  698. process_commit_context(
  699. event_id=self.event.event_id,
  700. event_platform=self.event.platform,
  701. event_frames=event_frames,
  702. group_id=self.event.group_id,
  703. project_id=self.event.project_id,
  704. )
  705. assert not mock_comment_workflow.called
  706. assert len(PullRequestCommit.objects.all()) == 0
  707. @with_feature("organizations:pr-comment-bot")
  708. @patch("sentry.integrations.github.client.get_jwt", return_value=b"jwt_token_1")
  709. @responses.activate
  710. def test_gh_comment_debounces(self, get_jwt, mock_comment_workflow):
  711. self.add_responses()
  712. with self.tasks():
  713. assert not GroupOwner.objects.filter(group=self.event.group).exists()
  714. event_frames = get_frame_paths(self.event)
  715. process_commit_context(
  716. event_id=self.event.event_id,
  717. event_platform=self.event.platform,
  718. event_frames=event_frames,
  719. group_id=self.event.group_id,
  720. project_id=self.event.project_id,
  721. )
  722. process_commit_context(
  723. event_id=self.event.event_id,
  724. event_platform=self.event.platform,
  725. event_frames=event_frames,
  726. group_id=self.event.group_id,
  727. project_id=self.event.project_id,
  728. )
  729. assert mock_comment_workflow.call_count == 1